Skip to content

Commit c478bdd

Browse files
authored
Initial interceptors implementation (#3616)
* Merge v3 * Remove Twitter badge * Improve generated comments * Add support for interceptors in generated examples * Finalize initial interceptor implementation * Fix tests
1 parent 81a4791 commit c478bdd

File tree

128 files changed

+4935
-220
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+4935
-220
lines changed

codegen/cli/cli.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type (
3434
// PkgName is the service HTTP client package import name,
3535
// e.g. "storagec".
3636
PkgName string
37+
// Interceptors contains the data for client interceptors if any.
38+
Interceptors *InterceptorData
3739
}
3840

3941
// SubcommandData contains the data needed to render a sub-command.
@@ -58,6 +60,14 @@ type (
5860
Example string
5961
}
6062

63+
// InterceptorData contains the data needed to generate interceptor code.
64+
InterceptorData struct {
65+
// VarName is the name of the interceptor variable.
66+
VarName string
67+
// PkgName is the package name containing the interceptor type.
68+
PkgName string
69+
}
70+
6171
// FlagData contains the data needed to render a command-line flag.
6272
FlagData struct {
6373
// Name is the name of the flag, e.g. "list-vintage"
@@ -151,11 +161,21 @@ func BuildCommandData(data *service.Data) *CommandData {
151161
if description == "" {
152162
description = fmt.Sprintf("Make requests to the %q service", data.Name)
153163
}
164+
165+
var interceptors *InterceptorData
166+
if len(data.ClientInterceptors) > 0 {
167+
interceptors = &InterceptorData{
168+
VarName: "inter",
169+
PkgName: data.PkgName,
170+
}
171+
}
172+
154173
return &CommandData{
155-
Name: codegen.KebabCase(data.Name),
156-
VarName: codegen.Goify(data.Name, false),
157-
Description: description,
158-
PkgName: data.PkgName + "c",
174+
Name: codegen.KebabCase(data.Name),
175+
VarName: codegen.Goify(data.Name, false),
176+
Description: description,
177+
PkgName: data.PkgName + "c",
178+
Interceptors: interceptors,
159179
}
160180
}
161181

@@ -682,7 +702,8 @@ const parseFlagsT = `var (
682702
`
683703

684704
// input: commandData
685-
const commandUsageT = `{{ printf "%sUsage displays the usage of the %s command and its subcommands." .Name .Name | comment }}
705+
const commandUsageT = `
706+
{{ printf "%sUsage displays the usage of the %s command and its subcommands." .VarName .Name | comment }}
686707
func {{ .VarName }}Usage() {
687708
fmt.Fprintf(os.Stderr, ` + "`" + `{{ printDescription .Description }}
688709
Usage:

codegen/example/example_server.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@ func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *c
5050
// Iterate through services listed in the server expression.
5151
svcData := make([]*service.Data, len(svr.Services))
5252
scope := codegen.NewNameScope()
53+
hasInterceptors := false
5354
for i, svc := range svr.Services {
5455
sd := service.Services.Get(svc)
5556
svcData[i] = sd
5657
specs = append(specs, &codegen.ImportSpec{
5758
Path: path.Join(genpkg, sd.PathName),
58-
Name: scope.Unique(sd.PkgName),
59+
Name: scope.Unique(sd.PkgName, "svc"),
5960
})
61+
hasInterceptors = hasInterceptors || len(sd.ServerInterceptors) > 0
6062
}
63+
interPkg := scope.Unique("interceptors", "ex")
6164

6265
var (
6366
rootPath string
@@ -73,6 +76,9 @@ func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *c
7376
apiPkg = scope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
7477
}
7578
specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: apiPkg})
79+
if hasInterceptors {
80+
specs = append(specs, &codegen.ImportSpec{Path: path.Join(rootPath, "interceptors"), Name: interPkg})
81+
}
7682

7783
sections := []*codegen.SectionTemplate{
7884
codegen.Header("", "main", specs),
@@ -101,6 +107,18 @@ func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *c
101107
FuncMap: map[string]any{
102108
"mustInitServices": mustInitServices,
103109
},
110+
}, {
111+
Name: "server-main-interceptors",
112+
Source: readTemplate("server_interceptors"),
113+
Data: map[string]any{
114+
"APIPkg": apiPkg,
115+
"InterPkg": interPkg,
116+
"Services": svcData,
117+
"HasInterceptors": hasInterceptors,
118+
},
119+
FuncMap: map[string]any{
120+
"mustInitServices": mustInitServices,
121+
},
104122
}, {
105123
Name: "server-main-endpoints",
106124
Source: readTemplate("server_endpoints"),

codegen/example/templates/server_endpoints.go.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
{
1212
{{- range .Services }}
1313
{{- if .Methods }}
14-
{{ .VarName }}Endpoints = {{ .PkgName }}.NewEndpoints({{ .VarName }}Svc)
14+
{{ .VarName }}Endpoints = {{ .PkgName }}.NewEndpoints({{ .VarName }}Svc{{ if .ServerInterceptors }}, {{ .VarName }}Interceptors{{ end }})
1515
{{ .VarName }}Endpoints.Use(debug.LogPayloads())
1616
{{ .VarName }}Endpoints.Use(log.Endpoint)
1717
{{- end }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{{- if mustInitServices .Services }}
2+
{{- if .HasInterceptors }}
3+
{{ comment "Initialize the interceptors." }}
4+
var (
5+
{{- range .Services }}
6+
{{- if and .Methods .ServerInterceptors }}
7+
{{ .VarName }}Interceptors {{ .PkgName }}.ServerInterceptors
8+
{{- end }}
9+
{{- end }}
10+
)
11+
{
12+
{{- range .Services }}
13+
{{- if and .Methods .ServerInterceptors }}
14+
{{ .VarName }}Interceptors = {{ $.InterPkg }}.New{{ .StructName }}ServerInterceptors()
15+
{{- end }}
16+
{{- end }}
17+
}
18+
{{- end }}
19+
{{- end }}

codegen/generator/example.go

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
2525
files = append(files, fs...)
2626
}
2727

28+
// example interceptors implementation
29+
if fs := service.ExampleInterceptorsFiles(genpkg, r); len(fs) != 0 {
30+
files = append(files, fs...)
31+
}
32+
2833
// server main
2934
if fs := example.ServerFiles(genpkg, r); len(fs) != 0 {
3035
files = append(files, fs...)
@@ -54,6 +59,8 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
5459
files = append(files, fs...)
5560
}
5661
}
62+
63+
// Add imports defined via struct:field:type
5764
for _, f := range files {
5865
if len(f.SectionTemplates) > 0 {
5966
for _, s := range r.Services {

codegen/service/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const (
1515
// ClientFile returns the client file for the given service.
1616
func ClientFile(_ string, service *expr.ServiceExpr) *codegen.File {
1717
svc := Services.Get(service.Name)
18-
data := endpointData(service)
18+
data := endpointData(svc)
1919
path := filepath.Join(codegen.Gendir, svc.PathName, "client.go")
2020
var (
2121
sections []*codegen.SectionTemplate

codegen/service/client_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func TestClient(t *testing.T) {
3232
{"client-streaming-payload-no-result", testdata.StreamingPayloadNoResultMethodDSL, testdata.StreamingPayloadNoResultMethodClient},
3333
{"client-bidirectional-streaming", testdata.BidirectionalStreamingMethodDSL, testdata.BidirectionalStreamingMethodClient},
3434
{"client-bidirectional-streaming-no-payload", testdata.BidirectionalStreamingNoPayloadMethodDSL, testdata.BidirectionalStreamingNoPayloadMethodClient},
35+
{"client-interceptor", testdata.EndpointWithClientInterceptorDSL, testdata.InterceptorClient},
3536
}
3637
for _, c := range cases {
3738
t.Run(c.Name, func(t *testing.T) {

codegen/service/convert_test.go

-24
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"goa.design/goa/v3/codegen"
1515
"goa.design/goa/v3/codegen/service/testdata"
1616
"goa.design/goa/v3/dsl"
17-
"goa.design/goa/v3/eval"
1817
"goa.design/goa/v3/expr"
1918
)
2019

@@ -257,30 +256,7 @@ func TestConvertFile(t *testing.T) {
257256
}
258257
}
259258

260-
// runDSL returns the DSL root resulting from running the given DSL.
261-
func runDSL(t *testing.T, dsl func()) *expr.RootExpr {
262-
// reset all roots and codegen data structures
263-
Services = make(ServicesData)
264-
eval.Reset()
265-
expr.Root = new(expr.RootExpr)
266-
expr.GeneratedResultTypes = new(expr.ResultTypesRoot)
267-
require.NoError(t, eval.Register(expr.Root))
268-
require.NoError(t, eval.Register(expr.GeneratedResultTypes))
269-
expr.Root.API = expr.NewAPIExpr("test api", func() {})
270-
expr.Root.API.Servers = []*expr.ServerExpr{expr.Root.API.DefaultServer()}
271-
272-
// run DSL (first pass)
273-
require.True(t, eval.Execute(dsl, nil))
274-
275-
// run DSL (second pass)
276-
require.NoError(t, eval.RunDSL())
277-
278-
// return generated root
279-
return expr.Root
280-
}
281-
282259
// Test fixtures
283-
284260
var obj = &expr.UserTypeExpr{
285261
AttributeExpr: &expr.AttributeExpr{
286262
Type: &expr.Object{

codegen/service/endpoint.go

+19-12
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ type (
3030
// Schemes contains the security schemes types used by the
3131
// all the endpoints.
3232
Schemes SchemesData
33+
// HasServerInterceptors indicates that the service has server-side
34+
// interceptors.
35+
HasServerInterceptors bool
36+
// HasClientInterceptors indicates that the service has client-side
37+
// interceptors.
38+
HasClientInterceptors bool
3339
}
3440

3541
// EndpointMethodData describes a single endpoint method.
@@ -61,7 +67,7 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
6167
svc := Services.Get(service.Name)
6268
svcName := svc.PathName
6369
path := filepath.Join(codegen.Gendir, svcName, "endpoints.go")
64-
data := endpointData(service)
70+
data := endpointData(svc)
6571
var (
6672
sections []*codegen.SectionTemplate
6773
)
@@ -128,8 +134,7 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
128134
return &codegen.File{Path: path, SectionTemplates: sections}
129135
}
130136

131-
func endpointData(service *expr.ServiceExpr) *EndpointsData {
132-
svc := Services.Get(service.Name)
137+
func endpointData(svc *Data) *EndpointsData {
133138
methods := make([]*EndpointMethodData, len(svc.Methods))
134139
names := make([]string, len(svc.Methods))
135140
for i, m := range svc.Methods {
@@ -142,16 +147,18 @@ func endpointData(service *expr.ServiceExpr) *EndpointsData {
142147
}
143148
names[i] = codegen.Goify(m.VarName, false)
144149
}
145-
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, service.Name)
150+
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, svc.Name)
146151
return &EndpointsData{
147-
Name: service.Name,
148-
Description: desc,
149-
VarName: endpointsStructName,
150-
ClientVarName: clientStructName,
151-
ServiceVarName: serviceInterfaceName,
152-
ClientInitArgs: strings.Join(names, ", "),
153-
Methods: methods,
154-
Schemes: svc.Schemes,
152+
Name: svc.Name,
153+
Description: desc,
154+
VarName: endpointsStructName,
155+
ClientVarName: clientStructName,
156+
ServiceVarName: serviceInterfaceName,
157+
ClientInitArgs: strings.Join(names, ", "),
158+
Methods: methods,
159+
Schemes: svc.Schemes,
160+
HasServerInterceptors: len(svc.ServerInterceptors) > 0,
161+
HasClientInterceptors: len(svc.ClientInterceptors) > 0,
155162
}
156163
}
157164

codegen/service/endpoint_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func TestEndpoint(t *testing.T) {
3434
{"endpoint-streaming-payload-no-result", testdata.StreamingPayloadNoResultMethodDSL, testdata.StreamingPayloadNoResultMethodEndpoint},
3535
{"endpoint-bidirectional-streaming", testdata.BidirectionalStreamingEndpointDSL, testdata.BidirectionalStreamingMethodEndpoint},
3636
{"endpoint-bidirectional-streaming-no-payload", testdata.BidirectionalStreamingNoPayloadMethodDSL, testdata.BidirectionalStreamingNoPayloadMethodEndpoint},
37+
{"endpoint-with-server-interceptor", testdata.EndpointWithServerInterceptorDSL, testdata.EndpointWithServerInterceptor},
38+
{"endpoint-with-multiple-interceptors", testdata.EndpointWithMultipleInterceptorsDSL, testdata.EndpointWithMultipleInterceptors},
3739
}
3840
for _, c := range cases {
3941
t.Run(c.Name, func(t *testing.T) {
+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package service
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path"
7+
"path/filepath"
8+
9+
"goa.design/goa/v3/codegen"
10+
"goa.design/goa/v3/expr"
11+
)
12+
13+
// ExampleInterceptorsFiles returns the files for the example server and client interceptors.
14+
func ExampleInterceptorsFiles(genpkg string, r *expr.RootExpr) []*codegen.File {
15+
var fw []*codegen.File
16+
for _, svc := range r.Services {
17+
if f := exampleInterceptorsFile(genpkg, svc); f != nil {
18+
fw = append(fw, f...)
19+
}
20+
}
21+
return fw
22+
}
23+
24+
// exampleInterceptorsFile returns the example interceptors for the given service.
25+
func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.File {
26+
sdata := Services.Get(svc.Name)
27+
data := map[string]any{
28+
"ServiceName": sdata.Name,
29+
"StructName": sdata.StructName,
30+
"PkgName": "interceptors",
31+
"ServerInterceptors": sdata.ServerInterceptors,
32+
"ClientInterceptors": sdata.ClientInterceptors,
33+
}
34+
35+
var files []*codegen.File
36+
37+
// Generate server interceptor if needed and file doesn't exist
38+
if len(sdata.ServerInterceptors) > 0 {
39+
serverPath := filepath.Join("interceptors", sdata.PathName+"_server.go")
40+
if _, err := os.Stat(serverPath); os.IsNotExist(err) {
41+
files = append(files, &codegen.File{
42+
Path: serverPath,
43+
SectionTemplates: []*codegen.SectionTemplate{
44+
codegen.Header(fmt.Sprintf("%s example server interceptors", sdata.Name), "interceptors", []*codegen.ImportSpec{
45+
{Path: "context"},
46+
{Path: "fmt"},
47+
{Path: "goa.design/clue/log"},
48+
codegen.GoaImport(""),
49+
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
50+
}),
51+
{
52+
Name: "exmaple-server-interceptor",
53+
Source: readTemplate("example_server_interceptor"),
54+
Data: data,
55+
},
56+
},
57+
})
58+
}
59+
}
60+
61+
// Generate client interceptor if needed and file doesn't exist
62+
if len(sdata.ClientInterceptors) > 0 {
63+
clientPath := filepath.Join("interceptors", sdata.PathName+"_client.go")
64+
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
65+
files = append(files, &codegen.File{
66+
Path: clientPath,
67+
SectionTemplates: []*codegen.SectionTemplate{
68+
codegen.Header(fmt.Sprintf("%s example client interceptors", sdata.Name), "interceptors", []*codegen.ImportSpec{
69+
{Path: "context"},
70+
{Path: "fmt"},
71+
{Path: "goa.design/clue/log"},
72+
codegen.GoaImport(""),
73+
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
74+
}),
75+
{
76+
Name: "example-client-interceptor",
77+
Source: readTemplate("example_client_interceptor"),
78+
Data: data,
79+
},
80+
},
81+
})
82+
}
83+
}
84+
85+
return files
86+
}

0 commit comments

Comments
 (0)