Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial interceptors implementation #3616

Merged
merged 7 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading