Skip to content

Commit

Permalink
Initial interceptors implementation (#3616)
Browse files Browse the repository at this point in the history
* Merge v3

* Remove Twitter badge

* Improve generated comments

* Add support for interceptors in generated examples

* Finalize initial interceptor implementation

* Fix tests
  • Loading branch information
raphael authored Jan 19, 2025
1 parent 81a4791 commit c478bdd
Show file tree
Hide file tree
Showing 128 changed files with 4,935 additions and 220 deletions.
31 changes: 26 additions & 5 deletions codegen/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type (
// PkgName is the service HTTP client package import name,
// e.g. "storagec".
PkgName string
// Interceptors contains the data for client interceptors if any.
Interceptors *InterceptorData
}

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

// InterceptorData contains the data needed to generate interceptor code.
InterceptorData struct {
// VarName is the name of the interceptor variable.
VarName string
// PkgName is the package name containing the interceptor type.
PkgName string
}

// FlagData contains the data needed to render a command-line flag.
FlagData struct {
// Name is the name of the flag, e.g. "list-vintage"
Expand Down Expand Up @@ -151,11 +161,21 @@ func BuildCommandData(data *service.Data) *CommandData {
if description == "" {
description = fmt.Sprintf("Make requests to the %q service", data.Name)
}

var interceptors *InterceptorData
if len(data.ClientInterceptors) > 0 {
interceptors = &InterceptorData{
VarName: "inter",
PkgName: data.PkgName,
}
}

return &CommandData{
Name: codegen.KebabCase(data.Name),
VarName: codegen.Goify(data.Name, false),
Description: description,
PkgName: data.PkgName + "c",
Name: codegen.KebabCase(data.Name),
VarName: codegen.Goify(data.Name, false),
Description: description,
PkgName: data.PkgName + "c",
Interceptors: interceptors,
}
}

Expand Down Expand Up @@ -682,7 +702,8 @@ const parseFlagsT = `var (
`

// input: commandData
const commandUsageT = `{{ printf "%sUsage displays the usage of the %s command and its subcommands." .Name .Name | comment }}
const commandUsageT = `
{{ printf "%sUsage displays the usage of the %s command and its subcommands." .VarName .Name | comment }}
func {{ .VarName }}Usage() {
fmt.Fprintf(os.Stderr, ` + "`" + `{{ printDescription .Description }}
Usage:
Expand Down
20 changes: 19 additions & 1 deletion codegen/example/example_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,17 @@ func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *c
// Iterate through services listed in the server expression.
svcData := make([]*service.Data, len(svr.Services))
scope := codegen.NewNameScope()
hasInterceptors := false
for i, svc := range svr.Services {
sd := service.Services.Get(svc)
svcData[i] = sd
specs = append(specs, &codegen.ImportSpec{
Path: path.Join(genpkg, sd.PathName),
Name: scope.Unique(sd.PkgName),
Name: scope.Unique(sd.PkgName, "svc"),
})
hasInterceptors = hasInterceptors || len(sd.ServerInterceptors) > 0
}
interPkg := scope.Unique("interceptors", "ex")

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

sections := []*codegen.SectionTemplate{
codegen.Header("", "main", specs),
Expand Down Expand Up @@ -101,6 +107,18 @@ func exampleSvrMain(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *c
FuncMap: map[string]any{
"mustInitServices": mustInitServices,
},
}, {
Name: "server-main-interceptors",
Source: readTemplate("server_interceptors"),
Data: map[string]any{
"APIPkg": apiPkg,
"InterPkg": interPkg,
"Services": svcData,
"HasInterceptors": hasInterceptors,
},
FuncMap: map[string]any{
"mustInitServices": mustInitServices,
},
}, {
Name: "server-main-endpoints",
Source: readTemplate("server_endpoints"),
Expand Down
2 changes: 1 addition & 1 deletion codegen/example/templates/server_endpoints.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
{{- range .Services }}
{{- if .Methods }}
{{ .VarName }}Endpoints = {{ .PkgName }}.NewEndpoints({{ .VarName }}Svc)
{{ .VarName }}Endpoints = {{ .PkgName }}.NewEndpoints({{ .VarName }}Svc{{ if .ServerInterceptors }}, {{ .VarName }}Interceptors{{ end }})
{{ .VarName }}Endpoints.Use(debug.LogPayloads())
{{ .VarName }}Endpoints.Use(log.Endpoint)
{{- end }}
Expand Down
19 changes: 19 additions & 0 deletions codegen/example/templates/server_interceptors.go.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{- if mustInitServices .Services }}
{{- if .HasInterceptors }}
{{ comment "Initialize the interceptors." }}
var (
{{- range .Services }}
{{- if and .Methods .ServerInterceptors }}
{{ .VarName }}Interceptors {{ .PkgName }}.ServerInterceptors
{{- end }}
{{- end }}
)
{
{{- range .Services }}
{{- if and .Methods .ServerInterceptors }}
{{ .VarName }}Interceptors = {{ $.InterPkg }}.New{{ .StructName }}ServerInterceptors()
{{- end }}
{{- end }}
}
{{- end }}
{{- end }}
7 changes: 7 additions & 0 deletions codegen/generator/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
files = append(files, fs...)
}

// example interceptors implementation
if fs := service.ExampleInterceptorsFiles(genpkg, r); len(fs) != 0 {
files = append(files, fs...)
}

// server main
if fs := example.ServerFiles(genpkg, r); len(fs) != 0 {
files = append(files, fs...)
Expand Down Expand Up @@ -54,6 +59,8 @@ func Example(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
files = append(files, fs...)
}
}

// Add imports defined via struct:field:type
for _, f := range files {
if len(f.SectionTemplates) > 0 {
for _, s := range r.Services {
Expand Down
2 changes: 1 addition & 1 deletion codegen/service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const (
// ClientFile returns the client file for the given service.
func ClientFile(_ string, service *expr.ServiceExpr) *codegen.File {
svc := Services.Get(service.Name)
data := endpointData(service)
data := endpointData(svc)
path := filepath.Join(codegen.Gendir, svc.PathName, "client.go")
var (
sections []*codegen.SectionTemplate
Expand Down
1 change: 1 addition & 0 deletions codegen/service/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestClient(t *testing.T) {
{"client-streaming-payload-no-result", testdata.StreamingPayloadNoResultMethodDSL, testdata.StreamingPayloadNoResultMethodClient},
{"client-bidirectional-streaming", testdata.BidirectionalStreamingMethodDSL, testdata.BidirectionalStreamingMethodClient},
{"client-bidirectional-streaming-no-payload", testdata.BidirectionalStreamingNoPayloadMethodDSL, testdata.BidirectionalStreamingNoPayloadMethodClient},
{"client-interceptor", testdata.EndpointWithClientInterceptorDSL, testdata.InterceptorClient},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
Expand Down
24 changes: 0 additions & 24 deletions codegen/service/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"goa.design/goa/v3/codegen"
"goa.design/goa/v3/codegen/service/testdata"
"goa.design/goa/v3/dsl"
"goa.design/goa/v3/eval"
"goa.design/goa/v3/expr"
)

Expand Down Expand Up @@ -257,30 +256,7 @@ func TestConvertFile(t *testing.T) {
}
}

// runDSL returns the DSL root resulting from running the given DSL.
func runDSL(t *testing.T, dsl func()) *expr.RootExpr {
// reset all roots and codegen data structures
Services = make(ServicesData)
eval.Reset()
expr.Root = new(expr.RootExpr)
expr.GeneratedResultTypes = new(expr.ResultTypesRoot)
require.NoError(t, eval.Register(expr.Root))
require.NoError(t, eval.Register(expr.GeneratedResultTypes))
expr.Root.API = expr.NewAPIExpr("test api", func() {})
expr.Root.API.Servers = []*expr.ServerExpr{expr.Root.API.DefaultServer()}

// run DSL (first pass)
require.True(t, eval.Execute(dsl, nil))

// run DSL (second pass)
require.NoError(t, eval.RunDSL())

// return generated root
return expr.Root
}

// Test fixtures

var obj = &expr.UserTypeExpr{
AttributeExpr: &expr.AttributeExpr{
Type: &expr.Object{
Expand Down
31 changes: 19 additions & 12 deletions codegen/service/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ type (
// Schemes contains the security schemes types used by the
// all the endpoints.
Schemes SchemesData
// HasServerInterceptors indicates that the service has server-side
// interceptors.
HasServerInterceptors bool
// HasClientInterceptors indicates that the service has client-side
// interceptors.
HasClientInterceptors bool
}

// EndpointMethodData describes a single endpoint method.
Expand Down Expand Up @@ -61,7 +67,7 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
svc := Services.Get(service.Name)
svcName := svc.PathName
path := filepath.Join(codegen.Gendir, svcName, "endpoints.go")
data := endpointData(service)
data := endpointData(svc)
var (
sections []*codegen.SectionTemplate
)
Expand Down Expand Up @@ -128,8 +134,7 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
return &codegen.File{Path: path, SectionTemplates: sections}
}

func endpointData(service *expr.ServiceExpr) *EndpointsData {
svc := Services.Get(service.Name)
func endpointData(svc *Data) *EndpointsData {
methods := make([]*EndpointMethodData, len(svc.Methods))
names := make([]string, len(svc.Methods))
for i, m := range svc.Methods {
Expand All @@ -142,16 +147,18 @@ func endpointData(service *expr.ServiceExpr) *EndpointsData {
}
names[i] = codegen.Goify(m.VarName, false)
}
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, service.Name)
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, svc.Name)
return &EndpointsData{
Name: service.Name,
Description: desc,
VarName: endpointsStructName,
ClientVarName: clientStructName,
ServiceVarName: serviceInterfaceName,
ClientInitArgs: strings.Join(names, ", "),
Methods: methods,
Schemes: svc.Schemes,
Name: svc.Name,
Description: desc,
VarName: endpointsStructName,
ClientVarName: clientStructName,
ServiceVarName: serviceInterfaceName,
ClientInitArgs: strings.Join(names, ", "),
Methods: methods,
Schemes: svc.Schemes,
HasServerInterceptors: len(svc.ServerInterceptors) > 0,
HasClientInterceptors: len(svc.ClientInterceptors) > 0,
}
}

Expand Down
2 changes: 2 additions & 0 deletions codegen/service/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func TestEndpoint(t *testing.T) {
{"endpoint-streaming-payload-no-result", testdata.StreamingPayloadNoResultMethodDSL, testdata.StreamingPayloadNoResultMethodEndpoint},
{"endpoint-bidirectional-streaming", testdata.BidirectionalStreamingEndpointDSL, testdata.BidirectionalStreamingMethodEndpoint},
{"endpoint-bidirectional-streaming-no-payload", testdata.BidirectionalStreamingNoPayloadMethodDSL, testdata.BidirectionalStreamingNoPayloadMethodEndpoint},
{"endpoint-with-server-interceptor", testdata.EndpointWithServerInterceptorDSL, testdata.EndpointWithServerInterceptor},
{"endpoint-with-multiple-interceptors", testdata.EndpointWithMultipleInterceptorsDSL, testdata.EndpointWithMultipleInterceptors},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
Expand Down
86 changes: 86 additions & 0 deletions codegen/service/example_interceptors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package service

import (
"fmt"
"os"
"path"
"path/filepath"

"goa.design/goa/v3/codegen"
"goa.design/goa/v3/expr"
)

// ExampleInterceptorsFiles returns the files for the example server and client interceptors.
func ExampleInterceptorsFiles(genpkg string, r *expr.RootExpr) []*codegen.File {
var fw []*codegen.File
for _, svc := range r.Services {
if f := exampleInterceptorsFile(genpkg, svc); f != nil {
fw = append(fw, f...)
}
}
return fw
}

// exampleInterceptorsFile returns the example interceptors for the given service.
func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.File {
sdata := Services.Get(svc.Name)
data := map[string]any{
"ServiceName": sdata.Name,
"StructName": sdata.StructName,
"PkgName": "interceptors",
"ServerInterceptors": sdata.ServerInterceptors,
"ClientInterceptors": sdata.ClientInterceptors,
}

var files []*codegen.File

// Generate server interceptor if needed and file doesn't exist
if len(sdata.ServerInterceptors) > 0 {
serverPath := filepath.Join("interceptors", sdata.PathName+"_server.go")
if _, err := os.Stat(serverPath); os.IsNotExist(err) {
files = append(files, &codegen.File{
Path: serverPath,
SectionTemplates: []*codegen.SectionTemplate{
codegen.Header(fmt.Sprintf("%s example server interceptors", sdata.Name), "interceptors", []*codegen.ImportSpec{
{Path: "context"},
{Path: "fmt"},
{Path: "goa.design/clue/log"},
codegen.GoaImport(""),
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
}),
{
Name: "exmaple-server-interceptor",
Source: readTemplate("example_server_interceptor"),
Data: data,
},
},
})
}
}

// Generate client interceptor if needed and file doesn't exist
if len(sdata.ClientInterceptors) > 0 {
clientPath := filepath.Join("interceptors", sdata.PathName+"_client.go")
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
files = append(files, &codegen.File{
Path: clientPath,
SectionTemplates: []*codegen.SectionTemplate{
codegen.Header(fmt.Sprintf("%s example client interceptors", sdata.Name), "interceptors", []*codegen.ImportSpec{
{Path: "context"},
{Path: "fmt"},
{Path: "goa.design/clue/log"},
codegen.GoaImport(""),
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
}),
{
Name: "example-client-interceptor",
Source: readTemplate("example_client_interceptor"),
Data: data,
},
},
})
}
}

return files
}
Loading

0 comments on commit c478bdd

Please sign in to comment.