From 85b416db0c6d099aabcd2f69667989ac3e31be14 Mon Sep 17 00:00:00 2001
From: Raphael Simon
Date: Sun, 19 Jan 2025 10:53:55 -0800
Subject: [PATCH 1/6] Merge v3
---
README.md | 1 +
codegen/service/client.go | 12 +
codegen/service/client_test.go | 1 +
codegen/service/convert_test.go | 24 --
codegen/service/endpoint.go | 66 +++-
codegen/service/endpoint_test.go | 2 +
codegen/service/interceptors.go | 215 +++++++++++++
codegen/service/interceptors_test.go | 209 ++++++++++++
codegen/service/service.go | 5 +
.../service/templates/client_wrappers.go.tpl | 29 ++
.../templates/endpoint_wrappers.go.tpl | 30 ++
codegen/service/templates/interceptors.go.tpl | 153 +++++++++
.../templates/service_client_init.go.tpl | 12 +-
.../templates/service_endpoints_init.go.tpl | 15 +-
.../templates/service_interceptor.go.tpl | 0
codegen/service/testdata/client_code.go | 47 +++
codegen/service/testdata/endpoint_code.go | 181 +++++++++++
codegen/service/testdata/endpoint_dsls.go | 44 +++
.../interceptor-with-read-payload.golden | 41 +++
.../interceptor-with-read-result.golden | 41 +++
...interceptor-with-read-write-payload.golden | 45 +++
.../interceptor-with-read-write-result.golden | 45 +++
.../interceptor-with-write-payload.golden | 41 +++
.../interceptor-with-write-result.golden | 41 +++
.../interceptors/multiple-interceptors.golden | 30 ++
.../single-api-server-interceptor.golden | 16 +
.../single-client-interceptor.golden | 16 +
.../single-method-server-interceptor.golden | 16 +
.../single-service-server-interceptor.golden | 16 +
...ming-interceptors-with-read-payload.golden | 45 +++
...aming-interceptors-with-read-result.golden | 45 +++
.../streaming-interceptors.golden | 16 +
codegen/service/testdata/interceptors_dsls.go | 297 ++++++++++++++++++
codegen/service/testdata/service_dsls.go | 14 +
codegen/service/testing.go | 42 +++
dsl/description.go | 2 +
dsl/interceptor.go | 293 +++++++++++++++++
dsl/interceptor_test.go | 250 +++++++++++++++
dsl/meta.go | 39 +++
expr/api.go | 4 +
expr/interceptor.go | 84 +++++
expr/method.go | 79 ++++-
expr/root.go | 2 +
expr/service.go | 4 +
go.mod | 16 +-
go.sum | 24 +-
http/codegen/service_data.go | 48 +--
pkg/interceptor.go | 22 ++
48 files changed, 2622 insertions(+), 98 deletions(-)
create mode 100644 codegen/service/interceptors.go
create mode 100644 codegen/service/interceptors_test.go
create mode 100644 codegen/service/templates/client_wrappers.go.tpl
create mode 100644 codegen/service/templates/endpoint_wrappers.go.tpl
create mode 100644 codegen/service/templates/interceptors.go.tpl
create mode 100644 codegen/service/templates/service_interceptor.go.tpl
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-payload.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-result.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-write-payload.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-write-result.golden
create mode 100644 codegen/service/testdata/interceptors/multiple-interceptors.golden
create mode 100644 codegen/service/testdata/interceptors/single-api-server-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-client-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-method-server-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-service-server-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors.golden
create mode 100644 codegen/service/testdata/interceptors_dsls.go
create mode 100644 codegen/service/testing.go
create mode 100644 dsl/interceptor.go
create mode 100644 dsl/interceptor_test.go
create mode 100644 expr/interceptor.go
create mode 100644 pkg/interceptor.go
diff --git a/README.md b/README.md
index 7735bc20e3..cd38d1919e 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@
+
diff --git a/codegen/service/client.go b/codegen/service/client.go
index a088de0d37..f991e81aed 100644
--- a/codegen/service/client.go
+++ b/codegen/service/client.go
@@ -45,6 +45,18 @@ func ClientFile(_ string, service *expr.ServiceExpr) *codegen.File {
Source: readTemplate("service_client_method"),
Data: m,
})
+ if len(m.ClientInterceptors) > 0 {
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: "client-wrapper",
+ Source: readTemplate("client_wrappers"),
+ Data: map[string]interface{}{
+ "Method": m.Name,
+ "MethodVarName": codegen.Goify(m.Name, true),
+ "Service": svc.Name,
+ "ClientInterceptors": m.ClientInterceptors,
+ },
+ })
+ }
}
}
diff --git a/codegen/service/client_test.go b/codegen/service/client_test.go
index 2fa5152365..6cfda7d4df 100644
--- a/codegen/service/client_test.go
+++ b/codegen/service/client_test.go
@@ -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) {
diff --git a/codegen/service/convert_test.go b/codegen/service/convert_test.go
index 52a0e1d23e..479d3c1c5e 100644
--- a/codegen/service/convert_test.go
+++ b/codegen/service/convert_test.go
@@ -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"
)
@@ -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{
diff --git a/codegen/service/endpoint.go b/codegen/service/endpoint.go
index aaefc4bbd2..45d476620e 100644
--- a/codegen/service/endpoint.go
+++ b/codegen/service/endpoint.go
@@ -25,6 +25,10 @@ type (
ServiceVarName string
// Methods lists the endpoint struct methods.
Methods []*EndpointMethodData
+ // HasServerInterceptors indicates if the service has server interceptors.
+ HasServerInterceptors bool
+ // HasClientInterceptors indicates if the service has client interceptors.
+ HasClientInterceptors bool
// ClientInitArgs lists the arguments needed to instantiate the client.
ClientInitArgs string
// Schemes contains the security schemes types used by the
@@ -44,6 +48,10 @@ type (
ServiceName string
// ServiceVarName is the name of the owner service Go interface.
ServiceVarName string
+ // ServerInterceptors contains the server-side interceptors for this method
+ ServerInterceptors []*InterceptorData
+ // ClientInterceptors contains the client-side interceptors for this method
+ ClientInterceptors []*InterceptorData
}
)
@@ -122,6 +130,18 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
Data: m,
FuncMap: map[string]any{"payloadVar": payloadVar},
})
+ if len(m.ServerInterceptors) > 0 {
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: "endpoint-wrapper",
+ Source: readTemplate("endpoint_wrappers"),
+ Data: map[string]interface{}{
+ "MethodVarName": codegen.Goify(m.Name, true),
+ "Method": m.Name,
+ "Service": svc.Name,
+ "ServerInterceptors": m.ServerInterceptors,
+ },
+ })
+ }
}
}
@@ -133,25 +153,45 @@ func endpointData(service *expr.ServiceExpr) *EndpointsData {
methods := make([]*EndpointMethodData, len(svc.Methods))
names := make([]string, len(svc.Methods))
for i, m := range svc.Methods {
+ serverInts, clientInts := buildMethodInterceptors(service.Method(m.Name), svc.Scope)
methods[i] = &EndpointMethodData{
- MethodData: m,
- ArgName: codegen.Goify(m.VarName, false),
- ServiceName: svc.Name,
- ServiceVarName: serviceInterfaceName,
- ClientVarName: clientStructName,
+ MethodData: m,
+ ArgName: codegen.Goify(m.VarName, false),
+ ServiceName: svc.Name,
+ ServiceVarName: serviceInterfaceName,
+ ClientVarName: clientStructName,
+ ServerInterceptors: serverInts,
+ ClientInterceptors: clientInts,
}
names[i] = codegen.Goify(m.VarName, false)
}
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, service.Name)
+ var hasServerInterceptors, hasClientInterceptors bool
+ for _, m := range methods {
+ if len(m.ServerInterceptors) > 0 {
+ hasServerInterceptors = true
+ if hasClientInterceptors {
+ break
+ }
+ }
+ if len(m.ClientInterceptors) > 0 {
+ hasClientInterceptors = true
+ if hasServerInterceptors {
+ break
+ }
+ }
+ }
return &EndpointsData{
- Name: service.Name,
- Description: desc,
- VarName: endpointsStructName,
- ClientVarName: clientStructName,
- ServiceVarName: serviceInterfaceName,
- ClientInitArgs: strings.Join(names, ", "),
- Methods: methods,
- Schemes: svc.Schemes,
+ Name: service.Name,
+ Description: desc,
+ VarName: endpointsStructName,
+ ClientVarName: clientStructName,
+ ServiceVarName: serviceInterfaceName,
+ ClientInitArgs: strings.Join(names, ", "),
+ Methods: methods,
+ HasServerInterceptors: hasServerInterceptors,
+ HasClientInterceptors: hasClientInterceptors,
+ Schemes: svc.Schemes,
}
}
diff --git a/codegen/service/endpoint_test.go b/codegen/service/endpoint_test.go
index 0356bf958e..2132eeeeca 100644
--- a/codegen/service/endpoint_test.go
+++ b/codegen/service/endpoint_test.go
@@ -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) {
diff --git a/codegen/service/interceptors.go b/codegen/service/interceptors.go
new file mode 100644
index 0000000000..57d734e0c6
--- /dev/null
+++ b/codegen/service/interceptors.go
@@ -0,0 +1,215 @@
+package service
+
+import (
+ "path/filepath"
+
+ "goa.design/goa/v3/codegen"
+ "goa.design/goa/v3/expr"
+)
+
+type (
+ // ServiceInterceptorData contains all data needed for generating interceptor code
+ ServiceInterceptorData struct {
+ Service string
+ PkgName string
+ Methods []*MethodInterceptorData
+ ServerInterceptors []*InterceptorData
+ ClientInterceptors []*InterceptorData
+ AllInterceptors []*InterceptorData
+ HasPrivateImplementationTypes bool
+ }
+
+ // MethodInterceptorData contains interceptor data for a single method
+ MethodInterceptorData struct {
+ Service string
+ Method string
+ MethodVarName string
+ PayloadRef string
+ ResultRef string
+ ServerInterceptors []*InterceptorData
+ ClientInterceptors []*InterceptorData
+ }
+
+ // InterceptorData describes a single interceptor.
+ InterceptorData struct {
+ Name string
+ UnexportedName string
+ Description string
+ PayloadRef string
+ ResultRef string
+ ReadPayload []*AttributeData
+ WritePayload []*AttributeData
+ ReadResult []*AttributeData
+ WriteResult []*AttributeData
+ ServerStreamInputStruct string
+ ClientStreamInputStruct string
+ }
+
+ // AttributeData describes a single attribute.
+ AttributeData struct {
+ Name string
+ TypeRef string
+ FieldPointer bool
+ }
+)
+
+// InterceptorsFile returns the interceptors file for the given service.
+func InterceptorsFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
+ svc := Services.Get(service.Name)
+ data := interceptorsData(service)
+ if len(data.ServerInterceptors) == 0 && len(data.ClientInterceptors) == 0 {
+ return nil
+ }
+
+ path := filepath.Join(codegen.Gendir, svc.PathName, "interceptors.go")
+ sections := []*codegen.SectionTemplate{
+ codegen.Header(service.Name+" interceptors", svc.PkgName, []*codegen.ImportSpec{
+ {Path: "context"},
+ codegen.GoaImport(""),
+ }),
+ {
+ Name: "interceptors",
+ Source: readTemplate("interceptors"),
+ Data: data,
+ },
+ }
+
+ return &codegen.File{Path: path, SectionTemplates: sections}
+}
+
+func interceptorsData(service *expr.ServiceExpr) *ServiceInterceptorData {
+ svc := Services.Get(service.Name)
+ scope := svc.Scope
+
+ // Build method data first
+ methods := make([]*MethodInterceptorData, 0, len(service.Methods))
+ seenInts := make(map[string]*InterceptorData)
+ var serviceServerInts, serviceClientInts, allInts []*InterceptorData
+ var hasTypes bool
+
+ for _, m := range service.Methods {
+ methodServerInts, methodClientInts := buildMethodInterceptors(m, scope)
+ if len(methodServerInts) == 0 && len(methodClientInts) == 0 {
+ continue
+ }
+ hasTypes = hasTypes || hasPrivateImplementationTypes(methodServerInts) || hasPrivateImplementationTypes(methodClientInts)
+
+ // Add method data
+ methods = append(methods, &MethodInterceptorData{
+ Service: svc.Name,
+ Method: m.Name,
+ MethodVarName: codegen.Goify(m.Name, true),
+ PayloadRef: scope.GoFullTypeRef(m.Payload, ""),
+ ResultRef: scope.GoFullTypeRef(m.Result, ""),
+ ServerInterceptors: methodServerInts,
+ ClientInterceptors: methodClientInts,
+ })
+
+ // Collect unique interceptors
+ for _, i := range methodServerInts {
+ if _, ok := seenInts[i.Name]; !ok {
+ seenInts[i.Name] = i
+ serviceServerInts = append(serviceServerInts, i)
+ allInts = append(allInts, i)
+ }
+ }
+ for _, i := range methodClientInts {
+ if _, ok := seenInts[i.Name]; !ok {
+ seenInts[i.Name] = i
+ serviceClientInts = append(serviceClientInts, i)
+ allInts = append(allInts, i)
+ }
+ }
+ }
+
+ return &ServiceInterceptorData{
+ Service: service.Name,
+ PkgName: svc.PkgName,
+ Methods: methods,
+ ServerInterceptors: serviceServerInts,
+ ClientInterceptors: serviceClientInts,
+ AllInterceptors: allInts,
+ HasPrivateImplementationTypes: hasTypes,
+ }
+}
+
+func buildMethodInterceptors(m *expr.MethodExpr, scope *codegen.NameScope) ([]*InterceptorData, []*InterceptorData) {
+ svc := Services.Get(m.Service.Name)
+ methodData := svc.Method(m.Name)
+ var serverEndpointStruct, clientEndpointStruct string
+ if methodData.ServerStream != nil {
+ serverEndpointStruct = methodData.ServerStream.EndpointStruct
+ }
+ if methodData.ClientStream != nil {
+ clientEndpointStruct = methodData.ClientStream.EndpointStruct
+ }
+ var hasPrivateImplementationTypes bool
+ buildInterceptor := func(intr *expr.InterceptorExpr) *InterceptorData {
+ hasPrivateImplementationTypes = hasPrivateImplementationTypes ||
+ intr.ReadPayload != nil || intr.WritePayload != nil || intr.ReadResult != nil || intr.WriteResult != nil
+
+ return &InterceptorData{
+ Name: codegen.Goify(intr.Name, true),
+ UnexportedName: codegen.Goify(intr.Name, false),
+ Description: intr.Description,
+ PayloadRef: methodData.PayloadRef,
+ ResultRef: methodData.ResultRef,
+ ServerStreamInputStruct: serverEndpointStruct,
+ ClientStreamInputStruct: clientEndpointStruct,
+ ReadPayload: collectAttributes(intr.ReadPayload, m.Payload, scope),
+ WritePayload: collectAttributes(intr.WritePayload, m.Payload, scope),
+ ReadResult: collectAttributes(intr.ReadResult, m.Result, scope),
+ WriteResult: collectAttributes(intr.WriteResult, m.Result, scope),
+ }
+ }
+
+ serverInts := make([]*InterceptorData, len(m.ServerInterceptors))
+ for i, intr := range m.ServerInterceptors {
+ serverInts[i] = buildInterceptor(intr)
+ }
+
+ clientInts := make([]*InterceptorData, len(m.ClientInterceptors))
+ for i, intr := range m.ClientInterceptors {
+ clientInts[i] = buildInterceptor(intr)
+ }
+
+ return serverInts, clientInts
+}
+
+// hasPrivateImplementationTypes returns true if any of the interceptors have
+// private implementation types.
+func hasPrivateImplementationTypes(interceptors []*InterceptorData) bool {
+ for _, intr := range interceptors {
+ if intr.ReadPayload != nil || intr.WritePayload != nil || intr.ReadResult != nil || intr.WriteResult != nil {
+ return true
+ }
+ }
+ return false
+}
+
+// collectAttributes builds AttributeData from an AttributeExpr
+func collectAttributes(attrNames, parent *expr.AttributeExpr, scope *codegen.NameScope) []*AttributeData {
+ if attrNames == nil {
+ return nil
+ }
+
+ obj := expr.AsObject(attrNames.Type)
+ if obj == nil {
+ return nil
+ }
+
+ data := make([]*AttributeData, len(*obj))
+ for i, nat := range *obj {
+ parentAttr := parent.Find(nat.Name)
+ if parentAttr == nil {
+ continue
+ }
+
+ data[i] = &AttributeData{
+ Name: codegen.Goify(nat.Name, true),
+ TypeRef: scope.GoTypeRef(parentAttr),
+ FieldPointer: parent.IsPrimitivePointer(nat.Name, true),
+ }
+ }
+ return data
+}
diff --git a/codegen/service/interceptors_test.go b/codegen/service/interceptors_test.go
new file mode 100644
index 0000000000..0817ac2b31
--- /dev/null
+++ b/codegen/service/interceptors_test.go
@@ -0,0 +1,209 @@
+package service
+
+import (
+ "bytes"
+ "flag"
+ "go/format"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "goa.design/goa/v3/codegen"
+ "goa.design/goa/v3/codegen/service/testdata"
+ "goa.design/goa/v3/expr"
+)
+
+var updateGolden = false
+
+func init() {
+ flag.BoolVar(&updateGolden, "w", false, "update golden files")
+}
+
+func TestInterceptors(t *testing.T) {
+ cases := []struct {
+ Name string
+ DSL func()
+ }{
+ {"no-interceptors", testdata.NoInterceptorsDSL},
+ {"single-api-server-interceptor", testdata.SingleAPIServerInterceptorDSL},
+ {"single-service-server-interceptor", testdata.SingleServiceServerInterceptorDSL},
+ {"single-method-server-interceptor", testdata.SingleMethodServerInterceptorDSL},
+ {"single-client-interceptor", testdata.SingleClientInterceptorDSL},
+ {"multiple-interceptors", testdata.MultipleInterceptorsDSL},
+ {"interceptor-with-read-payload", testdata.InterceptorWithReadPayloadDSL},
+ {"interceptor-with-write-payload", testdata.InterceptorWithWritePayloadDSL},
+ {"interceptor-with-read-write-payload", testdata.InterceptorWithReadWritePayloadDSL},
+ {"interceptor-with-read-result", testdata.InterceptorWithReadResultDSL},
+ {"interceptor-with-write-result", testdata.InterceptorWithWriteResultDSL},
+ {"interceptor-with-read-write-result", testdata.InterceptorWithReadWriteResultDSL},
+ {"streaming-interceptors", testdata.StreamingInterceptorsDSL},
+ {"streaming-interceptors-with-read-payload", testdata.StreamingInterceptorsWithReadPayloadDSL},
+ {"streaming-interceptors-with-read-result", testdata.StreamingInterceptorsWithReadResultDSL},
+ }
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ root := runDSL(t, c.DSL)
+ require.Len(t, root.Services, 1)
+
+ fs := InterceptorsFile("goa.design/goa/example", root.Services[0])
+
+ if c.Name == "no-interceptors" {
+ assert.Nil(t, fs)
+ return
+ }
+
+ require.NotNil(t, fs)
+
+ buf := new(bytes.Buffer)
+ for _, s := range fs.SectionTemplates[1:] {
+ require.NoError(t, s.Write(buf))
+ }
+ bs, err := format.Source(buf.Bytes())
+ require.NoError(t, err, buf.String())
+ code := strings.ReplaceAll(string(bs), "\r\n", "\n")
+
+ golden := filepath.Join("testdata", "interceptors", c.Name+".golden")
+ compareOrUpdateGolden(t, code, golden)
+ })
+ }
+}
+
+func TestInvalidInterceptors(t *testing.T) {
+ cases := []struct {
+ Name string
+ DSL func()
+ ErrContains string
+ }{
+ {
+ Name: "streaming-result-interceptor",
+ DSL: testdata.StreamingResultInterceptorDSL,
+ ErrContains: "cannot be applied because the method result is streaming",
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ _, err := runDSLWithError(t, c.DSL)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), c.ErrContains)
+ })
+ }
+}
+
+func TestCollectAttributes(t *testing.T) {
+ cases := []struct {
+ name string
+ attrNames *expr.AttributeExpr
+ parent *expr.AttributeExpr
+ want []*AttributeData
+ }{
+ {
+ name: "nil-attributes",
+ attrNames: nil,
+ parent: &expr.AttributeExpr{Type: &expr.Object{}},
+ want: nil,
+ },
+ {
+ name: "non-object-attributes",
+ attrNames: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)},
+ parent: &expr.AttributeExpr{Type: &expr.Object{}},
+ want: nil,
+ },
+ {
+ name: "simple-string-attribute",
+ attrNames: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "name", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)}},
+ },
+ },
+ parent: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "name", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)}},
+ },
+ Validation: &expr.ValidationExpr{Required: []string{"name"}},
+ },
+ want: []*AttributeData{
+ {Name: "Name", TypeRef: "string", FieldPointer: false},
+ },
+ },
+ {
+ name: "pointer-primitive",
+ attrNames: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "age", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.IntKind)}},
+ },
+ },
+ parent: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "age", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.IntKind), Meta: map[string][]string{"struct:field:pointer": {"true"}}}},
+ },
+ },
+ want: []*AttributeData{
+ {Name: "Age", TypeRef: "int", FieldPointer: true},
+ },
+ },
+ {
+ name: "multiple-attributes",
+ attrNames: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "name", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)}},
+ {Name: "age", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.IntKind)}},
+ },
+ },
+ parent: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "name", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)}},
+ {Name: "age", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.IntKind), Meta: map[string][]string{"struct:field:pointer": {"true"}}}},
+ },
+ Validation: &expr.ValidationExpr{Required: []string{"name"}},
+ },
+ want: []*AttributeData{
+ {Name: "Name", TypeRef: "string", FieldPointer: false},
+ {Name: "Age", TypeRef: "int", FieldPointer: true},
+ },
+ },
+ {
+ name: "attribute-not-in-parent",
+ attrNames: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "missing", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)}},
+ },
+ },
+ parent: &expr.AttributeExpr{
+ Type: &expr.Object{
+ {Name: "name", Attribute: &expr.AttributeExpr{Type: expr.Primitive(expr.StringKind)}},
+ },
+ Validation: &expr.ValidationExpr{Required: []string{"name"}},
+ },
+ want: []*AttributeData{nil},
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ scope := codegen.NewNameScope()
+ got := collectAttributes(tc.attrNames, tc.parent, scope)
+ assert.Equal(t, tc.want, got)
+ })
+ }
+}
+
+func compareOrUpdateGolden(t *testing.T, code, golden string) {
+ t.Helper()
+ if updateGolden {
+ require.NoError(t, os.MkdirAll(filepath.Dir(golden), 0750))
+ require.NoError(t, os.WriteFile(golden, []byte(code), 0640))
+ return
+ }
+ data, err := os.ReadFile(golden)
+ require.NoError(t, err)
+ if runtime.GOOS == "windows" {
+ data = bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n"))
+ }
+ assert.Equal(t, string(data), code)
+}
diff --git a/codegen/service/service.go b/codegen/service/service.go
index 8d920c7854..a6c45448d1 100644
--- a/codegen/service/service.go
+++ b/codegen/service/service.go
@@ -195,6 +195,11 @@ func Files(genpkg string, service *expr.ServiceExpr, userTypePkgs map[string][]s
}
files := []*codegen.File{{Path: svcPath, SectionTemplates: sections}}
+ // interceptor.go
+ if file := InterceptorsFile(genpkg, service); file != nil {
+ files = append(files, file)
+ }
+
// user types
paths := make([]string, len(typeDefSections))
i := 0
diff --git a/codegen/service/templates/client_wrappers.go.tpl b/codegen/service/templates/client_wrappers.go.tpl
new file mode 100644
index 0000000000..94364c38cd
--- /dev/null
+++ b/codegen/service/templates/client_wrappers.go.tpl
@@ -0,0 +1,29 @@
+{{ comment (printf "Wrap%sClientEndpoint wraps the %s endpoint with the client interceptors defined in the design." .MethodVarName .Method) }}
+func Wrap{{ .MethodVarName }}ClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ {{- range .ClientInterceptors }}
+ endpoint = wrapClient{{ .Name }}(endpoint, i, "{{ $.Method }}")
+ {{- end }}
+ return endpoint
+}
+
+{{- range .ClientInterceptors }}
+{{ comment (printf "wrapClient%s applies the %s interceptor to endpoints." .Name .Name) }}
+func wrapClient{{ .Name }}(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &{{ .Name }}Info{
+ Service: "{{ $.Service }}",
+ Method: method,
+ Endpoint: endpoint,
+ {{- if .ClientStreamInputStruct }}
+ RawPayload: req.(*{{ .ClientStreamInputStruct }}).Payload,
+ {{- else }}
+ RawPayload: req,
+ {{- end }}
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.{{ .Name }}(ctx, info, next)
+ }
+}
+{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/endpoint_wrappers.go.tpl b/codegen/service/templates/endpoint_wrappers.go.tpl
new file mode 100644
index 0000000000..bae0b9fa25
--- /dev/null
+++ b/codegen/service/templates/endpoint_wrappers.go.tpl
@@ -0,0 +1,30 @@
+{{ comment (printf "Wrap%sEndpoint wraps the %s endpoint with the server-side interceptors defined in the design." .MethodVarName .Method) }}
+func Wrap{{ .MethodVarName }}Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ {{- range .ServerInterceptors }}
+ endpoint = wrap{{ .Name }}(endpoint, i, "{{ $.Method }}")
+ {{- end }}
+ return endpoint
+}
+
+{{- range .ServerInterceptors }}
+{{ comment (printf "wrap%s applies the %s interceptor to endpoints." .Name .Name) }}
+func wrap{{ .Name }}(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &{{ .Name }}Info{
+ Service: "{{ $.Service }}",
+ Method: method,
+ Endpoint: endpoint,
+ {{- if .ServerStreamInputStruct }}
+ RawPayload: req.(*{{ .ServerStreamInputStruct }}).Payload,
+ {{- else }}
+ RawPayload: req,
+ {{- end }}
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.{{ .Name }}(ctx, info, next)
+ }
+}
+
+{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/interceptors.go.tpl b/codegen/service/templates/interceptors.go.tpl
new file mode 100644
index 0000000000..46dfceedb0
--- /dev/null
+++ b/codegen/service/templates/interceptors.go.tpl
@@ -0,0 +1,153 @@
+{{- if .ServerInterceptors -}}
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+{{- range .ServerInterceptors }}
+ {{ comment .Description }}
+ {{ .Name }}(context.Context, *{{ .Name }}Info, goa.NextFunc) (any, error)
+{{- end }}
+}
+{{- end }}
+
+{{- if .ClientInterceptors -}}
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server (request interceptors) or after the response is decoded
+// and before the result is returned to the client (response interceptors).
+type ClientInterceptors interface {
+{{- range .ClientInterceptors }}
+ {{ comment .Description }}
+ {{ .Name }}(context.Context, *{{ .Name }}Info, goa.NextFunc) (any, error)
+{{- end }}
+}
+{{- end }}
+
+// Access interfaces for interceptor payloads and results
+type (
+{{- range .AllInterceptors }}
+ // {{ .Name }}Info provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ {{ .Name }}Info goa.InterceptorInfo
+ {{- if or .ReadPayload .WritePayload }}
+
+ // {{ .Name }}PayloadAccess provides type-safe access to the method payload.
+ // It allows reading and writing specific fields of the payload as defined
+ // in the design.
+ {{ .Name }}PayloadAccess interface {
+ {{- range .ReadPayload }}
+ {{ .Name }}() {{ .TypeRef }}
+ {{- end }}
+ {{- range .WritePayload }}
+ Set{{ .Name }}({{ .TypeRef }})
+ {{- end }}
+ }
+ {{- end }}
+ {{- if or .ReadResult .WriteResult }}
+
+ // {{ .Name }}ResultAccess provides type-safe access to the method result.
+ // It allows reading and writing specific fields of the result as defined
+ // in the design.
+ {{ .Name }}ResultAccess interface {
+ {{- range .ReadResult }}
+ {{ .Name }}() {{ .TypeRef }}
+ {{- end }}
+ {{- range .WriteResult }}
+ Set{{ .Name }}({{ .TypeRef }})
+ {{- end }}
+ }
+ {{- end }}
+{{- end }}
+)
+
+{{- if .HasPrivateImplementationTypes }}
+
+// Private implementation types
+type (
+ {{- range .AllInterceptors }}
+ {{- if or .ReadPayload .WritePayload }}
+ {{ .UnexportedName }}PayloadAccess struct {
+ payload {{ .PayloadRef }}
+ }
+ {{- end }}
+
+ {{- if or .ReadResult .WriteResult }}
+ {{ .UnexportedName }}ResultAccess struct {
+ result {{ .ResultRef }}
+ }
+ {{- end }}
+ {{- end }}
+)
+
+// Public accessor methods for Info types
+{{- range .AllInterceptors }}
+ {{- if or .ReadPayload .WritePayload }}
+// Payload returns a type-safe accessor for the method payload.
+func (info *{{ .Name }}Info) Payload() {{ .Name }}PayloadAccess {
+ return &{{ .UnexportedName }}PayloadAccess{payload: info.RawPayload.({{ .PayloadRef }})}
+}
+ {{- end }}
+
+ {{- if or .ReadResult .WriteResult }}
+// Result returns a type-safe accessor for the method result.
+func (info *{{ .Name }}Info) Result(res any) {{ .Name }}ResultAccess {
+ return &{{ .UnexportedName }}ResultAccess{result: res.({{ .ResultRef }})}
+}
+ {{- end }}
+{{- end }}
+
+// Private implementation methods
+{{- range .AllInterceptors }}
+ {{- $interceptor := . }}
+ {{- range .ReadPayload }}
+func (p *{{ $interceptor.UnexportedName }}PayloadAccess) {{ .Name }}() {{ .TypeRef }} {
+ {{- if .FieldPointer }}
+ if p.payload.{{ .Name }} == nil {
+ var zero {{ .TypeRef }}
+ return zero
+ }
+ return *p.payload.{{ .Name }}
+ {{- else }}
+ return p.payload.{{ .Name }}
+ {{- end }}
+}
+ {{- end }}
+
+ {{- range .WritePayload }}
+func (p *{{ $interceptor.UnexportedName }}PayloadAccess) Set{{ .Name }}(v {{ .TypeRef }}) {
+ {{- if .FieldPointer }}
+ p.payload.{{ .Name }} = &v
+ {{- else }}
+ p.payload.{{ .Name }} = v
+ {{- end }}
+}
+ {{- end }}
+
+ {{- range .ReadResult }}
+func (r *{{ $interceptor.UnexportedName }}ResultAccess) {{ .Name }}() {{ .TypeRef }} {
+ {{- if .FieldPointer }}
+ if r.result.{{ .Name }} == nil {
+ var zero {{ .TypeRef }}
+ return zero
+ }
+ return *r.result.{{ .Name }}
+ {{- else }}
+ return r.result.{{ .Name }}
+ {{- end }}
+}
+ {{- end }}
+
+ {{- range .WriteResult }}
+func (r *{{ $interceptor.UnexportedName }}ResultAccess) Set{{ .Name }}(v {{ .TypeRef }}) {
+ {{- if .FieldPointer }}
+ r.result.{{ .Name }} = &v
+ {{- else }}
+ r.result.{{ .Name }} = v
+ {{- end }}
+}
+ {{- end }}
+{{- end }}
+{{- end }}
+
+
diff --git a/codegen/service/templates/service_client_init.go.tpl b/codegen/service/templates/service_client_init.go.tpl
index 3f2302c524..20621066a3 100644
--- a/codegen/service/templates/service_client_init.go.tpl
+++ b/codegen/service/templates/service_client_init.go.tpl
@@ -1,8 +1,8 @@
{{ printf "New%s initializes a %q service client given the endpoints." .ClientVarName .Name | comment }}
-func New{{ .ClientVarName }}({{ .ClientInitArgs }} goa.Endpoint) *{{ .ClientVarName }} {
- return &{{ .ClientVarName }}{
-{{- range .Methods }}
- {{ .VarName }}Endpoint: {{ .ArgName }},
-{{- end }}
- }
+func New{{ .ClientVarName }}({{ .ClientInitArgs }} goa.Endpoint{{ if .HasClientInterceptors }}, ci ClientInterceptors{{ end }}) *{{ .ClientVarName }} {
+ return &{{ .ClientVarName }}{
+ {{- range .Methods }}
+ {{ .VarName }}Endpoint: {{ if .ClientInterceptors }}Wrap{{ .VarName }}ClientEndpoint({{ end }}{{ .ArgName }}{{ if .ClientInterceptors }}, ci){{ end }},
+ {{- end }}
+ }
}
diff --git a/codegen/service/templates/service_endpoints_init.go.tpl b/codegen/service/templates/service_endpoints_init.go.tpl
index 64a9803a6b..3f46fc4d88 100644
--- a/codegen/service/templates/service_endpoints_init.go.tpl
+++ b/codegen/service/templates/service_endpoints_init.go.tpl
@@ -1,14 +1,25 @@
-
{{ printf "New%s wraps the methods of the %q service with endpoints." .VarName .Name | comment }}
-func New{{ .VarName }}(s {{ .ServiceVarName }}) *{{ .VarName }} {
+func New{{ .VarName }}(s {{ .ServiceVarName }}{{ if .HasServerInterceptors }}, si ServerInterceptors{{ end }}) *{{ .VarName }} {
{{- if .Schemes }}
// Casting service to Auther interface
a := s.(Auther)
{{- end }}
+{{- if .HasServerInterceptors }}
+ endpoints := &{{ .VarName }}{
+{{- else }}
return &{{ .VarName }}{
+{{- end }}
{{- range .Methods }}
{{ .VarName }}: New{{ .VarName }}Endpoint(s{{ range .Schemes }}, a.{{ .Type }}Auth{{ end }}),
{{- end }}
}
+{{- if .HasServerInterceptors }}
+ {{- range .Methods }}
+ {{- if .ServerInterceptors }}
+ endpoints.{{ .VarName }} = Wrap{{ .VarName }}Endpoint(endpoints.{{ .VarName }}, si)
+ {{- end }}
+ {{- end }}
+ return endpoints
+{{- end }}
}
\ No newline at end of file
diff --git a/codegen/service/templates/service_interceptor.go.tpl b/codegen/service/templates/service_interceptor.go.tpl
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/codegen/service/testdata/client_code.go b/codegen/service/testdata/client_code.go
index 2b2356134d..96f5a7b6a0 100644
--- a/codegen/service/testdata/client_code.go
+++ b/codegen/service/testdata/client_code.go
@@ -283,3 +283,50 @@ func (c *Client) BidirectionalStreamingNoPayloadMethod(ctx context.Context) (res
return ires.(BidirectionalStreamingNoPayloadMethodClientStream), nil
}
`
+
+const InterceptorClient = `// Client is the "ServiceWithClientInterceptor" service client.
+type Client struct {
+ MethodEndpoint goa.Endpoint
+}
+
+// NewClient initializes a "ServiceWithClientInterceptor" service client given
+// the endpoints.
+func NewClient(method goa.Endpoint, ci ClientInterceptors) *Client {
+ return &Client{
+ MethodEndpoint: WrapMethodClientEndpoint(method, ci),
+ }
+}
+
+// Method calls the "Method" endpoint of the "ServiceWithClientInterceptor"
+// service.
+func (c *Client) Method(ctx context.Context, p string) (res string, err error) {
+ var ires any
+ ires, err = c.MethodEndpoint(ctx, p)
+ if err != nil {
+ return
+ }
+ return ires.(string), nil
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientTracing(endpoint, i, "Method")
+ return endpoint
+}
+
+// wrapClientTracing applies the Tracing interceptor to endpoints.
+func wrapClientTracing(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &TracingInfo{
+ Service: "ServiceWithClientInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.Tracing(ctx, info, next)
+ }
+}`
diff --git a/codegen/service/testdata/endpoint_code.go b/codegen/service/testdata/endpoint_code.go
index 978ff5e5f7..27967a6e08 100644
--- a/codegen/service/testdata/endpoint_code.go
+++ b/codegen/service/testdata/endpoint_code.go
@@ -513,3 +513,184 @@ func NewBidirectionalStreamingNoPayloadMethodEndpoint(s Service) goa.Endpoint {
}
}
`
+
+var EndpointWithServerInterceptor = `// Endpoints wraps the "ServiceWithServerInterceptor" service endpoints.
+type Endpoints struct {
+ Method goa.Endpoint
+}
+
+// NewEndpoints wraps the methods of the "ServiceWithServerInterceptor" service
+// with endpoints.
+func NewEndpoints(s Service, si ServerInterceptors) *Endpoints {
+ endpoints := &Endpoints{
+ Method: NewMethodEndpoint(s),
+ }
+ endpoints.Method = WrapMethodEndpoint(endpoints.Method, si)
+ return endpoints
+}
+
+// Use applies the given middleware to all the "ServiceWithServerInterceptor"
+// service endpoints.
+func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) {
+ e.Method = m(e.Method)
+}
+
+// NewMethodEndpoint returns an endpoint function that calls the method
+// "Method" of service "ServiceWithServerInterceptor".
+func NewMethodEndpoint(s Service) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ p := req.(string)
+ return s.Method(ctx, p)
+ }
+}
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
+
+// wrapLogging applies the Logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "ServiceWithServerInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.Logging(ctx, info, next)
+ }
+}`
+
+var EndpointWithMultipleInterceptors = `// Endpoints wraps the "ServiceWithMultipleInterceptors" service endpoints.
+type Endpoints struct {
+ Method goa.Endpoint
+}
+
+// NewEndpoints wraps the methods of the "ServiceWithMultipleInterceptors"
+// service with endpoints.
+func NewEndpoints(s Service, si ServerInterceptors) *Endpoints {
+ endpoints := &Endpoints{
+ Method: NewMethodEndpoint(s),
+ }
+ endpoints.Method = WrapMethodEndpoint(endpoints.Method, si)
+ return endpoints
+}
+
+// Use applies the given middleware to all the
+// "ServiceWithMultipleInterceptors" service endpoints.
+func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) {
+ e.Method = m(e.Method)
+}
+
+// NewMethodEndpoint returns an endpoint function that calls the method
+// "Method" of service "ServiceWithMultipleInterceptors".
+func NewMethodEndpoint(s Service) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ p := req.(string)
+ return s.Method(ctx, p)
+ }
+}
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ endpoint = wrapMetrics(endpoint, i, "Method")
+ return endpoint
+}
+
+// wrapLogging applies the Logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "ServiceWithMultipleInterceptors",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.Logging(ctx, info, next)
+ }
+}
+
+// wrapMetrics applies the Metrics interceptor to endpoints.
+func wrapMetrics(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &MetricsInfo{
+ Service: "ServiceWithMultipleInterceptors",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.Metrics(ctx, info, next)
+ }
+}`
+
+var EndpointStreamingWithInterceptor = `// Endpoints wraps the "ServiceStreamingWithInterceptor" service endpoints.
+type Endpoints struct {
+ Method goa.Endpoint
+}
+
+// MethodEndpointInput holds both the payload and the server stream of the
+// "Method" method.
+type MethodEndpointInput struct {
+ // Stream is the server stream used by the "Method" method to send data.
+ Stream MethodServerStream
+}
+
+// NewEndpoints wraps the methods of the "ServiceStreamingWithInterceptor" service
+// with endpoints.
+func NewEndpoints(s Service, i ServerInterceptors) *Endpoints {
+ return &Endpoints{
+ Method: WrapMethodEndpoint(NewMethodEndpoint(s), i),
+ }
+}
+
+// Use applies the given middleware to all the "ServiceStreamingWithInterceptor"
+// service endpoints.
+func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) {
+ e.Method = m(e.Method)
+}
+
+// NewMethodEndpoint returns an endpoint function that calls the method "Method"
+// of service "ServiceStreamingWithInterceptor".
+func NewMethodEndpoint(s Service) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ ep := req.(*MethodEndpointInput)
+ return nil, s.Method(ctx, ep.Stream)
+ }
+}
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
+
+// wrapLogging applies the Logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "ServiceStreamingWithInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req.(*MethodEndpointInput).Payload,
+ }
+ next := func(ctx context.Context) (any, error) {
+ return endpoint(ctx, req)
+ }
+ return i.Logging(ctx, info, next)
+ }
+}`
diff --git a/codegen/service/testdata/endpoint_dsls.go b/codegen/service/testdata/endpoint_dsls.go
index 4ca2c7b435..de3a220401 100644
--- a/codegen/service/testdata/endpoint_dsls.go
+++ b/codegen/service/testdata/endpoint_dsls.go
@@ -181,3 +181,47 @@ var BidirectionalStreamingEndpointDSL = func() {
})
})
}
+
+var EndpointWithServerInterceptorDSL = func() {
+ Interceptor("logging")
+ Service("ServiceWithServerInterceptor", func() {
+ Method("Method", func() {
+ ServerInterceptor("logging")
+ Payload(String)
+ Result(String)
+ HTTP(func() {
+ POST("/")
+ })
+ })
+ })
+}
+
+var EndpointWithMultipleInterceptorsDSL = func() {
+ Interceptor("logging")
+ Interceptor("metrics")
+ Service("ServiceWithMultipleInterceptors", func() {
+ Method("Method", func() {
+ ServerInterceptor("logging")
+ ServerInterceptor("metrics")
+ Payload(String)
+ Result(String)
+ HTTP(func() {
+ POST("/")
+ })
+ })
+ })
+}
+
+var EndpointStreamingWithInterceptorDSL = func() {
+ Interceptor("logging")
+ Service("ServiceStreamingWithInterceptor", func() {
+ Method("Method", func() {
+ ServerInterceptor("logging")
+ StreamingPayload(String)
+ StreamingResult(String)
+ HTTP(func() {
+ GET("/")
+ })
+ })
+ })
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload.golden
new file mode 100644
index 0000000000..eb5152b895
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload.golden
@@ -0,0 +1,41 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Validation(context.Context, *ValidationInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // ValidationInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ ValidationInfo goa.InterceptorInfo
+
+ // ValidationPayloadAccess provides type-safe access to the method payload.
+ // It allows reading and writing specific fields of the payload as defined
+ // in the design.
+ ValidationPayloadAccess interface {
+ Name() string
+ }
+)
+
+// Private implementation types
+type (
+ validationPayloadAccess struct {
+ payload *MethodPayload
+ }
+)
+
+// Public accessor methods for Info types
+// Payload returns a type-safe accessor for the method payload.
+func (info *ValidationInfo) Payload() ValidationPayloadAccess {
+ return &validationPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+}
+
+// Private implementation methods
+func (p *validationPayloadAccess) Name() string {
+ return p.payload.Name
+}
+
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result.golden
new file mode 100644
index 0000000000..254d1ddaf0
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result.golden
@@ -0,0 +1,41 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Caching(context.Context, *CachingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // CachingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ CachingInfo goa.InterceptorInfo
+
+ // CachingResultAccess provides type-safe access to the method result.
+ // It allows reading and writing specific fields of the result as defined
+ // in the design.
+ CachingResultAccess interface {
+ Data() string
+ }
+)
+
+// Private implementation types
+type (
+ cachingResultAccess struct {
+ result *MethodResult
+ }
+)
+
+// Public accessor methods for Info types
+// Result returns a type-safe accessor for the method result.
+func (info *CachingInfo) Result(res any) CachingResultAccess {
+ return &cachingResultAccess{result: res.(*MethodResult)}
+}
+
+// Private implementation methods
+func (r *cachingResultAccess) Data() string {
+ return r.result.Data
+}
+
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden
new file mode 100644
index 0000000000..07d0f938cb
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden
@@ -0,0 +1,45 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Validation(context.Context, *ValidationInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // ValidationInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ ValidationInfo goa.InterceptorInfo
+
+ // ValidationPayloadAccess provides type-safe access to the method payload.
+ // It allows reading and writing specific fields of the payload as defined
+ // in the design.
+ ValidationPayloadAccess interface {
+ Name() string
+ SetName(string)
+ }
+)
+
+// Private implementation types
+type (
+ validationPayloadAccess struct {
+ payload *MethodPayload
+ }
+)
+
+// Public accessor methods for Info types
+// Payload returns a type-safe accessor for the method payload.
+func (info *ValidationInfo) Payload() ValidationPayloadAccess {
+ return &validationPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+}
+
+// Private implementation methods
+func (p *validationPayloadAccess) Name() string {
+ return p.payload.Name
+}
+func (p *validationPayloadAccess) SetName(v string) {
+ p.payload.Name = v
+}
+
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden
new file mode 100644
index 0000000000..202260177d
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden
@@ -0,0 +1,45 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Caching(context.Context, *CachingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // CachingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ CachingInfo goa.InterceptorInfo
+
+ // CachingResultAccess provides type-safe access to the method result.
+ // It allows reading and writing specific fields of the result as defined
+ // in the design.
+ CachingResultAccess interface {
+ Data() string
+ SetData(string)
+ }
+)
+
+// Private implementation types
+type (
+ cachingResultAccess struct {
+ result *MethodResult
+ }
+)
+
+// Public accessor methods for Info types
+// Result returns a type-safe accessor for the method result.
+func (info *CachingInfo) Result(res any) CachingResultAccess {
+ return &cachingResultAccess{result: res.(*MethodResult)}
+}
+
+// Private implementation methods
+func (r *cachingResultAccess) Data() string {
+ return r.result.Data
+}
+func (r *cachingResultAccess) SetData(v string) {
+ r.result.Data = v
+}
+
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload.golden
new file mode 100644
index 0000000000..7f0eabeb04
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload.golden
@@ -0,0 +1,41 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Validation(context.Context, *ValidationInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // ValidationInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ ValidationInfo goa.InterceptorInfo
+
+ // ValidationPayloadAccess provides type-safe access to the method payload.
+ // It allows reading and writing specific fields of the payload as defined
+ // in the design.
+ ValidationPayloadAccess interface {
+ SetName(string)
+ }
+)
+
+// Private implementation types
+type (
+ validationPayloadAccess struct {
+ payload *MethodPayload
+ }
+)
+
+// Public accessor methods for Info types
+// Payload returns a type-safe accessor for the method payload.
+func (info *ValidationInfo) Payload() ValidationPayloadAccess {
+ return &validationPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+}
+
+// Private implementation methods
+func (p *validationPayloadAccess) SetName(v string) {
+ p.payload.Name = v
+}
+
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result.golden
new file mode 100644
index 0000000000..362ae77d6a
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result.golden
@@ -0,0 +1,41 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Caching(context.Context, *CachingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // CachingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ CachingInfo goa.InterceptorInfo
+
+ // CachingResultAccess provides type-safe access to the method result.
+ // It allows reading and writing specific fields of the result as defined
+ // in the design.
+ CachingResultAccess interface {
+ SetData(string)
+ }
+)
+
+// Private implementation types
+type (
+ cachingResultAccess struct {
+ result *MethodResult
+ }
+)
+
+// Public accessor methods for Info types
+// Result returns a type-safe accessor for the method result.
+func (info *CachingInfo) Result(res any) CachingResultAccess {
+ return &cachingResultAccess{result: res.(*MethodResult)}
+}
+
+// Private implementation methods
+func (r *cachingResultAccess) SetData(v string) {
+ r.result.Data = v
+}
+
+
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors.golden b/codegen/service/testdata/interceptors/multiple-interceptors.golden
new file mode 100644
index 0000000000..111d148050
--- /dev/null
+++ b/codegen/service/testdata/interceptors/multiple-interceptors.golden
@@ -0,0 +1,30 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+
+ Tracing(context.Context, *TracingInfo, goa.NextFunc) (any, error)
+} // ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server (request interceptors) or after the response is decoded
+// and before the result is returned to the client (response interceptors).
+type ClientInterceptors interface {
+ Metrics(context.Context, *MetricsInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+ // TracingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ TracingInfo goa.InterceptorInfo
+ // MetricsInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ MetricsInfo goa.InterceptorInfo
+)
+
+
diff --git a/codegen/service/testdata/interceptors/single-api-server-interceptor.golden b/codegen/service/testdata/interceptors/single-api-server-interceptor.golden
new file mode 100644
index 0000000000..ae003b0548
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-api-server-interceptor.golden
@@ -0,0 +1,16 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+
diff --git a/codegen/service/testdata/interceptors/single-client-interceptor.golden b/codegen/service/testdata/interceptors/single-client-interceptor.golden
new file mode 100644
index 0000000000..0fc4e0f1a9
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-client-interceptor.golden
@@ -0,0 +1,16 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server (request interceptors) or after the response is decoded
+// and before the result is returned to the client (response interceptors).
+type ClientInterceptors interface {
+ Tracing(context.Context, *TracingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // TracingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ TracingInfo goa.InterceptorInfo
+)
+
+
diff --git a/codegen/service/testdata/interceptors/single-method-server-interceptor.golden b/codegen/service/testdata/interceptors/single-method-server-interceptor.golden
new file mode 100644
index 0000000000..ae003b0548
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-method-server-interceptor.golden
@@ -0,0 +1,16 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+
diff --git a/codegen/service/testdata/interceptors/single-service-server-interceptor.golden b/codegen/service/testdata/interceptors/single-service-server-interceptor.golden
new file mode 100644
index 0000000000..ae003b0548
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-service-server-interceptor.golden
@@ -0,0 +1,16 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden
new file mode 100644
index 0000000000..68f1cf20cb
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden
@@ -0,0 +1,45 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+
+ // LoggingPayloadAccess provides type-safe access to the method payload.
+ // It allows reading and writing specific fields of the payload as defined
+ // in the design.
+ LoggingPayloadAccess interface {
+ Initial() string
+ }
+)
+
+// Private implementation types
+type (
+ loggingPayloadAccess struct {
+ payload *MethodPayload
+ }
+)
+
+// Public accessor methods for Info types
+// Payload returns a type-safe accessor for the method payload.
+func (info *LoggingInfo) Payload() LoggingPayloadAccess {
+ return &loggingPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+}
+
+// Private implementation methods
+func (p *loggingPayloadAccess) Initial() string {
+ if p.payload.Initial == nil {
+ var zero string
+ return zero
+ }
+ return *p.payload.Initial
+}
+
+
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden
new file mode 100644
index 0000000000..4dec14cb90
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden
@@ -0,0 +1,45 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+
+ // LoggingResultAccess provides type-safe access to the method result.
+ // It allows reading and writing specific fields of the result as defined
+ // in the design.
+ LoggingResultAccess interface {
+ Data() string
+ }
+)
+
+// Private implementation types
+type (
+ loggingResultAccess struct {
+ result *MethodResult
+ }
+)
+
+// Public accessor methods for Info types
+// Result returns a type-safe accessor for the method result.
+func (info *LoggingInfo) Result(res any) LoggingResultAccess {
+ return &loggingResultAccess{result: res.(*MethodResult)}
+}
+
+// Private implementation methods
+func (r *loggingResultAccess) Data() string {
+ if r.result.Data == nil {
+ var zero string
+ return zero
+ }
+ return *r.result.Data
+}
+
+
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors.golden b/codegen/service/testdata/interceptors/streaming-interceptors.golden
new file mode 100644
index 0000000000..ae003b0548
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors.golden
@@ -0,0 +1,16 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the payload
+// is sent to the service (request interceptors) or after the service returns and
+// before the response is encoded (response interceptors).
+type ServerInterceptors interface {
+ Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+
diff --git a/codegen/service/testdata/interceptors_dsls.go b/codegen/service/testdata/interceptors_dsls.go
new file mode 100644
index 0000000000..dead879c9f
--- /dev/null
+++ b/codegen/service/testdata/interceptors_dsls.go
@@ -0,0 +1,297 @@
+package testdata
+
+import (
+ . "goa.design/goa/v3/dsl"
+)
+
+var NoInterceptorsDSL = func() {
+ Service("NoInterceptors", func() {
+ Method("Method", func() {
+ HTTP(func() { GET("/") })
+ })
+ })
+}
+
+var SingleAPIServerInterceptorDSL = func() {
+ Interceptor("logging")
+ API("SingleAPIServerInterceptor", func() {
+ ServerInterceptor("logging")
+ })
+ Service("SingleAPIServerInterceptor", func() {
+ Method("Method", func() {
+ HTTP(func() { GET("/1") })
+ })
+ Method("Method2", func() {
+ HTTP(func() { GET("/2") })
+ })
+ })
+}
+
+var SingleServiceServerInterceptorDSL = func() {
+ Interceptor("logging")
+ Service("SingleServerInterceptor", func() {
+ ServerInterceptor("logging")
+ Method("Method", func() {
+ HTTP(func() {
+ GET("/1")
+ })
+ })
+ Method("Method2", func() {
+ HTTP(func() {
+ GET("/2")
+ })
+ })
+ })
+}
+
+var SingleMethodServerInterceptorDSL = func() {
+ Interceptor("logging")
+ Service("SingleMethodServerInterceptor", func() {
+ Method("Method", func() {
+ ServerInterceptor("logging")
+ HTTP(func() { GET("/1") })
+ })
+ Method("Method2", func() {
+ HTTP(func() { GET("/2") })
+ })
+ })
+}
+
+var SingleClientInterceptorDSL = func() {
+ Interceptor("tracing")
+ Service("SingleClientInterceptor", func() {
+ ClientInterceptor("tracing")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("id", Int)
+ })
+ Result(func() {
+ Attribute("value", String)
+ })
+ HTTP(func() { GET("/") })
+ })
+ })
+}
+
+var MultipleInterceptorsDSL = func() {
+ Interceptor("logging")
+ Interceptor("tracing")
+ Interceptor("metrics")
+ Service("MultipleInterceptors", func() {
+ ServerInterceptor("logging")
+ ServerInterceptor("tracing")
+ ClientInterceptor("metrics")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("query", String)
+ })
+ Result(func() {
+ Attribute("data", String)
+ })
+ HTTP(func() { GET("/") })
+ })
+ })
+}
+
+var InterceptorWithReadPayloadDSL = func() {
+ Interceptor("validation", func() {
+ ReadPayload(func() {
+ Attribute("name")
+ })
+ })
+ Service("InterceptorWithReadPayload", func() {
+ ServerInterceptor("validation")
+ ClientInterceptor("validation")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("name", String)
+ Required("name")
+ })
+ HTTP(func() { POST("/") })
+ })
+ })
+}
+
+var InterceptorWithWritePayloadDSL = func() {
+ Interceptor("validation", func() {
+ WritePayload(func() {
+ Attribute("name")
+ })
+ })
+ Service("InterceptorWithWritePayload", func() {
+ ServerInterceptor("validation")
+ ClientInterceptor("validation")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("name", String)
+ Required("name")
+ })
+ HTTP(func() { POST("/") })
+ })
+ })
+}
+
+var InterceptorWithReadWritePayloadDSL = func() {
+ Interceptor("validation", func() {
+ ReadPayload(func() {
+ Attribute("name")
+ })
+ WritePayload(func() {
+ Attribute("name")
+ })
+ })
+ Service("InterceptorWithReadWritePayload", func() {
+ ServerInterceptor("validation")
+ ClientInterceptor("validation")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("name", String)
+ Required("name")
+ })
+ HTTP(func() { POST("/") })
+ })
+ })
+}
+
+var InterceptorWithReadResultDSL = func() {
+ Interceptor("caching", func() {
+ ReadResult(func() {
+ Attribute("data")
+ })
+ })
+ Service("InterceptorWithReadResult", func() {
+ ServerInterceptor("caching")
+ ClientInterceptor("caching")
+ Method("Method", func() {
+ Result(func() {
+ Attribute("data", String)
+ Required("data")
+ })
+ HTTP(func() { GET("/") })
+ })
+ })
+}
+
+var InterceptorWithWriteResultDSL = func() {
+ Interceptor("caching", func() {
+ WriteResult(func() {
+ Attribute("data")
+ })
+ })
+ Service("InterceptorWithWriteResult", func() {
+ ServerInterceptor("caching")
+ ClientInterceptor("caching")
+ Method("Method", func() {
+ Result(func() {
+ Attribute("data", String)
+ Required("data")
+ })
+ HTTP(func() { GET("/") })
+ })
+ })
+}
+
+var InterceptorWithReadWriteResultDSL = func() {
+ Interceptor("caching", func() {
+ ReadResult(func() {
+ Attribute("data")
+ })
+ WriteResult(func() {
+ Attribute("data")
+ })
+ })
+ Service("InterceptorWithReadWriteResult", func() {
+ ServerInterceptor("caching")
+ ClientInterceptor("caching")
+ Method("Method", func() {
+ Result(func() {
+ Attribute("data", String)
+ Required("data")
+ })
+ HTTP(func() { GET("/") })
+ })
+ })
+}
+
+var StreamingInterceptorsDSL = func() {
+ Interceptor("logging")
+ Service("StreamingInterceptors", func() {
+ ServerInterceptor("logging")
+ Method("Method", func() {
+ StreamingPayload(func() {
+ Attribute("chunk", String)
+ })
+ StreamingResult(func() {
+ Attribute("data", String)
+ })
+ HTTP(func() { GET("/stream") })
+ })
+ })
+}
+
+var StreamingInterceptorsWithReadPayloadDSL = func() {
+ Interceptor("logging", func() {
+ ReadPayload(func() {
+ Attribute("initial")
+ })
+ })
+ Service("StreamingInterceptorsWithReadPayload", func() {
+ ServerInterceptor("logging")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("initial", String)
+ })
+ StreamingPayload(func() {
+ Attribute("chunk", String)
+ })
+ HTTP(func() {
+ Header("initial")
+ GET("/stream")
+ })
+ })
+ })
+}
+
+var StreamingInterceptorsWithReadResultDSL = func() {
+ Interceptor("logging", func() {
+ ReadResult(func() {
+ Attribute("data")
+ })
+ })
+ Service("StreamingInterceptorsWithReadResult", func() {
+ ServerInterceptor("logging")
+ Method("Method", func() {
+ Payload(func() {
+ Attribute("initial", String)
+ })
+ StreamingPayload(func() {
+ Attribute("chunk", String)
+ })
+ Result(func() {
+ Attribute("data", String)
+ })
+ HTTP(func() {
+ Header("initial")
+ GET("/stream")
+ })
+ })
+ })
+}
+
+// Invalid DSL
+var StreamingResultInterceptorDSL = func() {
+ Interceptor("logging", func() {
+ ReadResult(func() {
+ Attribute("data")
+ })
+ })
+ Service("StreamingResultInterceptor", func() {
+ ServerInterceptor("logging")
+ Method("Method", func() {
+ StreamingResult(func() {
+ Attribute("data", String)
+ })
+ HTTP(func() { GET("/stream") })
+ })
+ })
+}
diff --git a/codegen/service/testdata/service_dsls.go b/codegen/service/testdata/service_dsls.go
index 5fd9b06b2c..76f7f917b8 100644
--- a/codegen/service/testdata/service_dsls.go
+++ b/codegen/service/testdata/service_dsls.go
@@ -967,3 +967,17 @@ var PkgPathPayloadAttributeDSL = func() {
})
})
}
+
+var EndpointWithClientInterceptorDSL = func() {
+ Interceptor("tracing")
+ Service("ServiceWithClientInterceptor", func() {
+ Method("Method", func() {
+ ClientInterceptor("tracing")
+ Payload(String)
+ Result(String)
+ HTTP(func() {
+ POST("/")
+ })
+ })
+ })
+}
diff --git a/codegen/service/testing.go b/codegen/service/testing.go
new file mode 100644
index 0000000000..28243ecfac
--- /dev/null
+++ b/codegen/service/testing.go
@@ -0,0 +1,42 @@
+package service
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "goa.design/goa/v3/eval"
+ "goa.design/goa/v3/expr"
+)
+
+// initDSL initializes the DSL environment and returns the root.
+func initDSL(t *testing.T) *expr.RootExpr {
+ // reset all roots and codegen data structures
+ Services = make(ServicesData)
+ eval.Reset()
+ expr.Root = new(expr.RootExpr)
+ expr.GeneratedResultTypes = new(expr.ResultTypesRoot)
+ expr.Root.API = expr.NewAPIExpr("test api", func() {})
+ expr.Root.API.Servers = []*expr.ServerExpr{expr.Root.API.DefaultServer()}
+ root := expr.Root
+ require.NoError(t, eval.Register(root))
+ require.NoError(t, eval.Register(expr.GeneratedResultTypes))
+ return root
+}
+
+// runDSL returns the DSL root resulting from running the given DSL.
+func runDSL(t *testing.T, dsl func()) *expr.RootExpr {
+ root := initDSL(t)
+ require.True(t, eval.Execute(dsl, nil))
+ require.NoError(t, eval.RunDSL())
+ return root
+}
+
+// runDSLWithError returns the DSL root and error from running the given DSL.
+func runDSLWithError(t *testing.T, dsl func()) (*expr.RootExpr, error) {
+ root := initDSL(t)
+ require.True(t, eval.Execute(dsl, nil))
+ err := eval.RunDSL()
+ require.Error(t, err)
+ return root, err
+}
diff --git a/dsl/description.go b/dsl/description.go
index 931c4f2ab3..b8e9c78479 100644
--- a/dsl/description.go
+++ b/dsl/description.go
@@ -45,6 +45,8 @@ func Description(d string) {
e.Description = d
case *expr.GRPCResponseExpr:
e.Description = d
+ case *expr.InterceptorExpr:
+ e.Description = d
default:
eval.IncompatibleDSL()
}
diff --git a/dsl/interceptor.go b/dsl/interceptor.go
new file mode 100644
index 0000000000..28b99ce7dd
--- /dev/null
+++ b/dsl/interceptor.go
@@ -0,0 +1,293 @@
+package dsl
+
+import (
+ "goa.design/goa/v3/eval"
+ "goa.design/goa/v3/expr"
+)
+
+// Interceptor defines a request interceptor. Interceptors provide a type-safe way
+// to read and write from and to the request and response.
+//
+// Interceptor must appear in a API, Service or Method expression.
+//
+// Interceptor accepts two arguments: the name of the interceptor and the
+// defining DSL.
+//
+// Example:
+//
+// var Cache = Interceptor("Cache", func() {
+// Description("Server-side interceptor which implements a transparent cache for the loaded records")
+//
+// ReadPayload(func() {
+// Attribute("id")
+// })
+//
+// WriteResult(func() {
+// Attribute("cachedAt")
+// })
+// })
+func Interceptor(name string, fn ...func()) *expr.InterceptorExpr {
+ if len(fn) > 1 {
+ eval.ReportError("interceptor %q cannot have multiple definitions", name)
+ return nil
+ }
+ i := &expr.InterceptorExpr{Name: name}
+ if name == "" {
+ eval.ReportError("interceptor name cannot be empty")
+ return i
+ }
+ if len(fn) > 0 {
+ if !eval.Execute(fn[0], i) {
+ return i
+ }
+ }
+ for _, i := range expr.Root.Interceptors {
+ if i.Name == name {
+ eval.ReportError("interceptor %q already defined", name)
+ return i
+ }
+ }
+ expr.Root.Interceptors = append(expr.Root.Interceptors, i)
+ return i
+}
+
+// ReadPayload defines the payload attributes read by the interceptor.
+//
+// ReadPayload must appear in an interceptor DSL.
+//
+// ReadPayload takes a function as argument which can use the Attribute DSL to
+// define the attributes read by the interceptor.
+//
+// Example:
+//
+// ReadPayload(func() {
+// Attribute("id")
+// })
+//
+// ReadPayload also accepts user defined types:
+//
+// // Interceptor can read any payload field
+// ReadPayload(MethodPayload)
+func ReadPayload(arg any) {
+ setInterceptorAttribute(arg, func(i *expr.InterceptorExpr, attr *expr.AttributeExpr) {
+ i.ReadPayload = attr
+ })
+}
+
+// WritePayload defines the payload attributes written by the interceptor.
+//
+// WritePayload must appear in an interceptor DSL.
+//
+// WritePayload takes a function as argument which can use the Attribute DSL to
+// define the attributes written by the interceptor.
+//
+// Example:
+//
+// WritePayload(func() {
+// Attribute("auth")
+// })
+//
+// WritePayload also accepts user defined types:
+//
+// // Interceptor can write any payload field
+// WritePayload(MethodPayload)
+func WritePayload(arg any) {
+ setInterceptorAttribute(arg, func(i *expr.InterceptorExpr, attr *expr.AttributeExpr) {
+ i.WritePayload = attr
+ })
+}
+
+// ReadResult defines the result attributes read by the interceptor.
+//
+// ReadResult must appear in an interceptor DSL.
+//
+// ReadResult takes a function as argument which can use the Attribute DSL to
+// define the attributes read by the interceptor.
+//
+// Example:
+//
+// ReadResult(func() {
+// Attribute("cachedAt")
+// })
+//
+// ReadResult also accepts user defined types:
+//
+// // Interceptor can read any result field
+// ReadResult(MethodResult)
+func ReadResult(arg any) {
+ setInterceptorAttribute(arg, func(i *expr.InterceptorExpr, attr *expr.AttributeExpr) {
+ i.ReadResult = attr
+ })
+}
+
+// WriteResult defines the result attributes written by the interceptor.
+//
+// WriteResult must appear in an interceptor DSL.
+//
+// WriteResult takes a function as argument which can use the Attribute DSL to
+// define the attributes written by the interceptor.
+//
+// Example:
+//
+// WriteResult(func() {
+// Attribute("cachedAt")
+// })
+//
+// WriteResult also accepts user defined types:
+//
+// // Interceptor can write any result field
+// WriteResult(MethodResult)
+func WriteResult(arg any) {
+ setInterceptorAttribute(arg, func(i *expr.InterceptorExpr, attr *expr.AttributeExpr) {
+ i.WriteResult = attr
+ })
+}
+
+// ServerInterceptor lists the server-side interceptors that apply to all the
+// API endpoints, all the service endpoints or a specific endpoint.
+//
+// ServerInterceptor must appear in a API, Service or Method expression.
+//
+// ServerInterceptor accepts one or more interceptor or interceptor names as
+// arguments. ServerInterceptor can appear multiple times in the same DSL.
+//
+// Example:
+//
+// Method("get_record", func() {
+// // Interceptor defined with the Interceptor DSL
+// ServerInterceptor(SetDeadline)
+//
+// // Name of interceptor defined with the Interceptor DSL
+// ServerInterceptor("Cache")
+//
+// // Interceptor defined inline
+// ServerInterceptor(Interceptor("CheckUserID", func() {
+// ReadPayload(func() {
+// Attribute("auth")
+// })
+// }))
+//
+// // ... rest of the method DSL
+// })
+func ServerInterceptor(interceptors ...any) {
+ addInterceptors(interceptors, false)
+}
+
+// ClientInterceptor lists the client-side interceptors that apply to all the
+// API endpoints, all the service endpoints or a specific endpoint.
+//
+// ClientInterceptor must appear in a API, Service or Method expression.
+//
+// ClientInterceptor accepts one or more interceptor or interceptor names as
+// arguments. ClientInterceptor can appear multiple times in the same DSL.
+//
+// Example:
+//
+// Method("get_record", func() {
+// // Interceptor defined with the Interceptor DSL
+// ClientInterceptor(Retry)
+//
+// // Name of interceptor defined with the Interceptor DSL
+// ClientInterceptor("Cache")
+//
+// // Interceptor defined inline
+// ClientInterceptor(Interceptor("Sign", func() {
+// ReadPayload(func() {
+// Attribute("user_id")
+// })
+// WritePayload(func() {
+// Attribute("auth")
+// })
+// }))
+//
+// // ... rest of the method DSL
+// })
+func ClientInterceptor(interceptors ...any) {
+ addInterceptors(interceptors, true)
+}
+
+// setInterceptorAttribute is a helper function that handles the common logic for
+// setting interceptor attributes (ReadPayload, WritePayload, ReadResult, WriteResult).
+func setInterceptorAttribute(arg any, setter func(i *expr.InterceptorExpr, attr *expr.AttributeExpr)) {
+ i, ok := eval.Current().(*expr.InterceptorExpr)
+ if !ok {
+ eval.IncompatibleDSL()
+ return
+ }
+
+ var attr *expr.AttributeExpr
+ switch fn := arg.(type) {
+ case func():
+ attr = &expr.AttributeExpr{Type: &expr.Object{}}
+ if !eval.Execute(fn, attr) {
+ return
+ }
+ case *expr.AttributeExpr:
+ attr = fn
+ case expr.DataType:
+ attr = &expr.AttributeExpr{Type: fn}
+ default:
+ eval.InvalidArgError("type, attribute or func()", arg)
+ return
+ }
+ setter(i, attr)
+}
+
+// addInterceptors is a helper function that validates and adds interceptors to
+// the current expression.
+func addInterceptors(interceptors []any, client bool) {
+ kind := "ServerInterceptor"
+ if client {
+ kind = "ClientInterceptor"
+ }
+ if len(interceptors) == 0 {
+ eval.ReportError("%s: at least one interceptor must be specified", kind)
+ return
+ }
+
+ var ints []*expr.InterceptorExpr
+ for _, i := range interceptors {
+ switch i := i.(type) {
+ case *expr.InterceptorExpr:
+ ints = append(ints, i)
+ case string:
+ var found bool
+ for _, in := range expr.Root.Interceptors {
+ if in.Name == i {
+ ints = append(ints, in)
+ found = true
+ break
+ }
+ }
+ if !found {
+ eval.ReportError("%s: interceptor %q not found", kind, i)
+ }
+ default:
+ eval.ReportError("%s: invalid interceptor %v", kind, i)
+ }
+ }
+
+ current := eval.Current()
+ switch actual := current.(type) {
+ case *expr.APIExpr:
+ if client {
+ actual.ClientInterceptors = append(actual.ClientInterceptors, ints...)
+ } else {
+ actual.ServerInterceptors = append(actual.ServerInterceptors, ints...)
+ }
+ case *expr.ServiceExpr:
+ if client {
+ actual.ClientInterceptors = append(actual.ClientInterceptors, ints...)
+ } else {
+ actual.ServerInterceptors = append(actual.ServerInterceptors, ints...)
+ }
+ case *expr.MethodExpr:
+ if client {
+ actual.ClientInterceptors = append(actual.ClientInterceptors, ints...)
+ } else {
+ actual.ServerInterceptors = append(actual.ServerInterceptors, ints...)
+ }
+ default:
+ eval.IncompatibleDSL()
+ }
+}
diff --git a/dsl/interceptor_test.go b/dsl/interceptor_test.go
new file mode 100644
index 0000000000..7e519768d1
--- /dev/null
+++ b/dsl/interceptor_test.go
@@ -0,0 +1,250 @@
+package dsl_test
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ . "goa.design/goa/v3/dsl"
+ "goa.design/goa/v3/eval"
+ "goa.design/goa/v3/expr"
+)
+
+func TestInterceptor(t *testing.T) {
+ cases := map[string]struct {
+ DSL func()
+ Assert func(t *testing.T, intr *expr.InterceptorExpr)
+ }{
+ "valid-minimal": {
+ func() {
+ Interceptor("minimal", func() {})
+ },
+ func(t *testing.T, intr *expr.InterceptorExpr) {
+ require.NotNil(t, intr, "interceptor should not be nil")
+ assert.Equal(t, "minimal", intr.Name)
+ },
+ },
+ "valid-complete": {
+ func() {
+ Interceptor("complete", func() {
+ Description("test interceptor")
+ ReadPayload(func() {
+ Attribute("foo", String)
+ })
+ WritePayload(func() {
+ Attribute("bar", String)
+ })
+ ReadResult(func() {
+ Attribute("baz", String)
+ })
+ WriteResult(func() {
+ Attribute("qux", String)
+ })
+ })
+ },
+ func(t *testing.T, intr *expr.InterceptorExpr) {
+ require.NotNil(t, intr, "interceptor should not be nil")
+ assert.Equal(t, "test interceptor", intr.Description)
+
+ require.NotNil(t, intr.ReadPayload, "ReadPayload should not be nil")
+ rp := expr.AsObject(intr.ReadPayload.Type)
+ require.NotNil(t, rp, "ReadPayload should be an object")
+ assert.NotNil(t, rp.Attribute("foo"), "ReadPayload should have a foo attribute")
+
+ require.NotNil(t, intr.WritePayload, "WritePayload should not be nil")
+ wp := expr.AsObject(intr.WritePayload.Type)
+ require.NotNil(t, wp, "WritePayload should be an object")
+ assert.NotNil(t, wp.Attribute("bar"), "WritePayload should have a bar attribute")
+
+ require.NotNil(t, intr.ReadResult, "ReadResult should not be nil")
+ rr := expr.AsObject(intr.ReadResult.Type)
+ require.NotNil(t, rr, "ReadResult should be an object")
+ assert.NotNil(t, rr.Attribute("baz"), "ReadResult should have a baz attribute")
+
+ require.NotNil(t, intr.WriteResult, "WriteResult should not be nil")
+ wr := expr.AsObject(intr.WriteResult.Type)
+ require.NotNil(t, wr, "WriteResult should be an object")
+ assert.NotNil(t, wr.Attribute("qux"), "WriteResult should have a qux attribute")
+ },
+ },
+ "empty-name": {
+ func() {
+ Interceptor("", func() {})
+ },
+ func(t *testing.T, intr *expr.InterceptorExpr) {
+ assert.NotNil(t, eval.Context.Errors, "expected a validation error")
+ },
+ },
+ "duplicate-name": {
+ func() {
+ Interceptor("duplicate", func() {})
+ Interceptor("duplicate", func() {})
+ },
+ func(t *testing.T, intr *expr.InterceptorExpr) {
+ if eval.Context.Errors == nil {
+ t.Error("expected a validation error, got none")
+ }
+ },
+ },
+ }
+
+ for name, tc := range cases {
+ t.Run(name, func(t *testing.T) {
+ eval.Context = &eval.DSLContext{}
+ expr.Root = new(expr.RootExpr)
+ tc.DSL()
+ if len(expr.Root.Interceptors) > 0 {
+ tc.Assert(t, expr.Root.Interceptors[0])
+ }
+ })
+ }
+}
+
+func TestServerInterceptor(t *testing.T) {
+ cases := map[string]struct {
+ DSL func()
+ Assert func(t *testing.T, svc *expr.ServiceExpr, err error)
+ }{
+ "valid-reference": {
+ func() {
+ var testInterceptor = Interceptor("test", func() {})
+ Service("Service", func() {
+ ServerInterceptor(testInterceptor)
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.NoError(t, err)
+ require.NotNil(t, svc)
+ require.Len(t, svc.ServerInterceptors, 1, "should have 1 server interceptor")
+ assert.Equal(t, "test", svc.ServerInterceptors[0].Name)
+ },
+ },
+ "valid-by-name": {
+ func() {
+ Interceptor("test", func() {})
+ Service("Service", func() {
+ ServerInterceptor("test")
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.NoError(t, err)
+ require.NotNil(t, svc)
+ require.Len(t, svc.ServerInterceptors, 1, "should have 1 server interceptor")
+ assert.Equal(t, "test", svc.ServerInterceptors[0].Name)
+ },
+ },
+ "invalid-reference": {
+ func() {
+ Service("Service", func() {
+ ServerInterceptor(42) // Invalid type
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.Error(t, err)
+ },
+ },
+ "invalid-name": {
+ func() {
+ Service("Service", func() {
+ ServerInterceptor("invalid")
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.Error(t, err)
+ },
+ },
+ }
+
+ for name, tc := range cases {
+ t.Run(name, func(t *testing.T) {
+ eval.Context = &eval.DSLContext{}
+ expr.Root = new(expr.RootExpr)
+ tc.DSL()
+ root, err := runDSL(t, tc.DSL)
+ tc.Assert(t, root.Services[0], err)
+ })
+ }
+}
+
+func TestClientInterceptor(t *testing.T) {
+ cases := map[string]struct {
+ DSL func()
+ Assert func(t *testing.T, svc *expr.ServiceExpr, err error)
+ }{
+ "valid-reference": {
+ func() {
+ var testInterceptor = Interceptor("test", func() {})
+ Service("Service", func() {
+ ClientInterceptor(testInterceptor)
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.NoError(t, err)
+ require.NotNil(t, svc)
+ require.Len(t, svc.ClientInterceptors, 1, "should have 1 client interceptor")
+ },
+ },
+ "valid-by-name": {
+ func() {
+ Interceptor("test", func() {})
+ Service("Service", func() {
+ ClientInterceptor("test")
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.NoError(t, err)
+ require.NotNil(t, svc)
+ require.Len(t, svc.ClientInterceptors, 1, "should have 1 client interceptor")
+ },
+ },
+ "invalid-reference": {
+ func() {
+ Service("Service", func() {
+ ClientInterceptor(42) // Invalid type
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.Error(t, err)
+ },
+ },
+ "invalid-name": {
+ func() {
+ Service("Service", func() {
+ ClientInterceptor("invalid")
+ })
+ },
+ func(t *testing.T, svc *expr.ServiceExpr, err error) {
+ require.Error(t, err)
+ },
+ },
+ }
+
+ for name, tc := range cases {
+ t.Run(name, func(t *testing.T) {
+ eval.Context = &eval.DSLContext{}
+ expr.Root = new(expr.RootExpr)
+ tc.DSL()
+ root, err := runDSL(t, tc.DSL)
+ tc.Assert(t, root.Services[0], err)
+ })
+ }
+}
+
+// runDSL returns the DSL root resulting from running the given DSL.
+func runDSL(t *testing.T, dsl func()) (*expr.RootExpr, error) {
+ t.Helper()
+ 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()}
+ if eval.Execute(dsl, nil) {
+ return expr.Root, eval.RunDSL()
+ } else {
+ return expr.Root, errors.New(eval.Context.Error())
+ }
+}
diff --git a/dsl/meta.go b/dsl/meta.go
index 05f917f851..60ffb196f4 100644
--- a/dsl/meta.go
+++ b/dsl/meta.go
@@ -288,3 +288,42 @@ func Meta(name string, value ...string) {
eval.IncompatibleDSL()
}
}
+
+// RemoveMeta removes a meta key from an object.
+//
+// RemoveMeta may appear where Meta can appear.
+//
+// RemoveMeta takes a single argument, the name of the meta key to remove.
+func RemoveMeta(name string) {
+ switch e := eval.Current().(type) {
+ case *expr.APIExpr:
+ delete(e.Meta, name)
+ case *expr.ServerExpr:
+ delete(e.Meta, name)
+ case *expr.HostExpr:
+ delete(e.Meta, name)
+ case *expr.AttributeExpr:
+ delete(e.Meta, name)
+ case *expr.ResultTypeExpr:
+ delete(e.Meta, name)
+ case *expr.MethodExpr:
+ delete(e.Meta, name)
+ case *expr.ServiceExpr:
+ delete(e.Meta, name)
+ case *expr.HTTPServiceExpr:
+ delete(e.Meta, name)
+ case *expr.HTTPEndpointExpr:
+ delete(e.Meta, name)
+ case *expr.RouteExpr:
+ delete(e.Meta, name)
+ case *expr.HTTPFileServerExpr:
+ delete(e.Meta, name)
+ case *expr.HTTPResponseExpr:
+ delete(e.Meta, name)
+ case expr.CompositeExpr:
+ att := e.Attribute()
+ delete(att.Meta, name)
+ default:
+ eval.IncompatibleDSL()
+ }
+}
diff --git a/expr/api.go b/expr/api.go
index 01ed202620..7d77fb975e 100644
--- a/expr/api.go
+++ b/expr/api.go
@@ -36,6 +36,10 @@ type (
// potentially multiple schemes. Incoming requests must validate
// at least one requirement to be authorized.
Requirements []*SecurityExpr
+ // ClientInterceptors is the list of API client interceptors.
+ ClientInterceptors []*InterceptorExpr
+ // ServerInterceptors is the list of API server interceptors.
+ ServerInterceptors []*InterceptorExpr
// HTTP contains the HTTP specific API level expressions.
HTTP *HTTPExpr
// GRPC contains the gRPC specific API level expressions.
diff --git a/expr/interceptor.go b/expr/interceptor.go
new file mode 100644
index 0000000000..538d85463f
--- /dev/null
+++ b/expr/interceptor.go
@@ -0,0 +1,84 @@
+package expr
+
+import (
+ "goa.design/goa/v3/eval"
+)
+
+type (
+ // InterceptorExpr describes an interceptor definition in the design.
+ // Interceptors are used to inject user code into the request/response processing pipeline.
+ // There are four kinds of interceptors, in order of execution:
+ // * client-side payload: executes after the payload is encoded and before the request is sent to the server
+ // * server-side request: executes after the request is decoded and before the payload is sent to the service
+ // * server-side result: executes after the service returns a result and before the response is encoded
+ // * client-side response: executes after the response is decoded and before the result is sent to the client
+ InterceptorExpr struct {
+ // Name is the name of the interceptor
+ Name string
+ // Description is the optional description of the interceptor
+ Description string
+ // ReadPayload lists the payload attribute names read by the interceptor
+ ReadPayload *AttributeExpr
+ // WritePayload lists the payload attribute names written by the interceptor
+ WritePayload *AttributeExpr
+ // ReadResult lists the result attribute names read by the interceptor
+ ReadResult *AttributeExpr
+ // WriteResult lists the result attribute names written by the interceptor
+ WriteResult *AttributeExpr
+ }
+)
+
+// EvalName returns the generic expression name used in error messages.
+func (i *InterceptorExpr) EvalName() string {
+ return "interceptor " + i.Name
+}
+
+// validate validates the interceptor.
+func (i *InterceptorExpr) validate(m *MethodExpr) *eval.ValidationErrors {
+ verr := new(eval.ValidationErrors)
+
+ if i.ReadPayload != nil || i.WritePayload != nil {
+ payloadObj := AsObject(m.Payload.Type)
+ if payloadObj == nil {
+ verr.Add(m, "interceptor %q cannot be applied because the method payload is not an object", i.Name)
+ }
+ if i.ReadPayload != nil {
+ i.validateAttributeAccess(m, "read payload", verr, payloadObj, i.ReadPayload)
+ }
+ if i.WritePayload != nil {
+ i.validateAttributeAccess(m, "write payload", verr, payloadObj, i.WritePayload)
+ }
+ }
+
+ if i.ReadResult != nil || i.WriteResult != nil {
+ if m.IsResultStreaming() {
+ verr.Add(m, "interceptor %q cannot be applied because the method result is streaming", i.Name)
+ }
+ resultObj := AsObject(m.Result.Type)
+ if resultObj == nil {
+ verr.Add(m, "interceptor %q cannot be applied because the method result is not an object", i.Name)
+ }
+ if i.ReadResult != nil {
+ i.validateAttributeAccess(m, "read result", verr, resultObj, i.ReadResult)
+ }
+ if i.WriteResult != nil {
+ i.validateAttributeAccess(m, "write result", verr, resultObj, i.WriteResult)
+ }
+ }
+
+ return verr
+}
+
+// validateAttributeAccess validates that all attributes in attr exist in obj
+func (i *InterceptorExpr) validateAttributeAccess(m *MethodExpr, source string, verr *eval.ValidationErrors, obj *Object, attr *AttributeExpr) {
+ attrObj := AsObject(attr.Type)
+ if attrObj == nil {
+ verr.Add(m, "interceptor %q %s attribute is not an object", i.Name, source)
+ return
+ }
+ for _, att := range *attrObj {
+ if obj.Attribute(att.Name) == nil {
+ verr.Add(m, "interceptor %q cannot %s attribute %q: attribute does not exist", i.Name, source, att.Name)
+ }
+ }
+}
diff --git a/expr/method.go b/expr/method.go
index 3ba80728bd..94e3e59356 100644
--- a/expr/method.go
+++ b/expr/method.go
@@ -32,6 +32,10 @@ type (
// schemes. Incoming requests must validate at least one
// requirement to be authorized.
Requirements []*SecurityExpr
+ // ClientInterceptors is the list of client interceptors.
+ ClientInterceptors []*InterceptorExpr
+ // ServerInterceptors is the list of server interceptors.
+ ServerInterceptors []*InterceptorExpr
// Service that owns method.
Service *ServiceExpr
// Meta is an arbitrary set of key/value pairs, see dsl.Meta
@@ -84,7 +88,8 @@ func (m *MethodExpr) EvalName() string {
}
// Prepare makes sure the payload and result types are initialized (to the Empty
-// type if nil).
+// type if nil) and merges the method interceptors with the API and service level
+// interceptors.
func (m *MethodExpr) Prepare() {
if m.Payload == nil {
m.Payload = &AttributeExpr{Type: Empty}
@@ -95,13 +100,57 @@ func (m *MethodExpr) Prepare() {
if m.Result == nil {
m.Result = &AttributeExpr{Type: Empty}
}
+
+ m.ClientInterceptors = mergeInterceptors(m.ClientInterceptors, m.Service.ClientInterceptors, Root.API.ClientInterceptors)
+ m.ServerInterceptors = mergeInterceptors(m.ServerInterceptors, m.Service.ServerInterceptors, Root.API.ServerInterceptors)
+}
+
+// mergeInterceptors merges interceptors from different levels (method, service, API)
+// while avoiding duplicates. The order of precedence is: method > service > API.
+func mergeInterceptors(methodLevel, serviceLevel, apiLevel []*InterceptorExpr) []*InterceptorExpr {
+ existing := make(map[string]struct{})
+ result := make([]*InterceptorExpr, 0, len(methodLevel)+len(serviceLevel)+len(apiLevel))
+
+ // Add method-level interceptors
+ for _, i := range methodLevel {
+ existing[i.Name] = struct{}{}
+ result = append(result, i)
+ }
+
+ // Add service-level interceptors
+ for _, i := range serviceLevel {
+ if _, ok := existing[i.Name]; !ok {
+ result = append(result, i)
+ existing[i.Name] = struct{}{}
+ }
+ }
+
+ // Add API-level interceptors
+ for _, i := range apiLevel {
+ if _, ok := existing[i.Name]; !ok {
+ result = append(result, i)
+ }
+ }
+
+ return result
}
-// Validate validates the method payloads, results, and errors (if any).
+// Validate validates the method payloads, results, errors, security
+// requirements, and interceptors.
func (m *MethodExpr) Validate() error {
verr := new(eval.ValidationErrors)
verr.Merge(m.Payload.Validate("payload", m))
- // validate security scheme requirements
+ verr.Merge(m.StreamingPayload.Validate("streaming_payload", m))
+ verr.Merge(m.Result.Validate("result", m))
+ verr.Merge(m.validateRequirements())
+ verr.Merge(m.validateErrors())
+ verr.Merge(m.validateInterceptors())
+ return verr
+}
+
+// validateRequirements validates the security requirements.
+func (m *MethodExpr) validateRequirements() *eval.ValidationErrors {
+ verr := new(eval.ValidationErrors)
var requirements []*SecurityExpr
if len(m.Requirements) > 0 {
requirements = m.Requirements
@@ -185,12 +234,12 @@ func (m *MethodExpr) Validate() error {
verr.Add(m, "payload of method %q of service %q defines a OAuth2 access token attribute, but no OAuth2 security scheme exist", m.Name, m.Service.Name)
}
}
- if m.StreamingPayload.Type != Empty {
- verr.Merge(m.StreamingPayload.Validate("streaming_payload", m))
- }
- if m.Result.Type != Empty {
- verr.Merge(m.Result.Validate("result", m))
- }
+ return verr
+}
+
+// validateErrors validates the method errors.
+func (m *MethodExpr) validateErrors() *eval.ValidationErrors {
+ verr := new(eval.ValidationErrors)
for i, e := range m.Errors {
if err := e.Validate(); err != nil {
var verrs *eval.ValidationErrors
@@ -220,6 +269,18 @@ func (m *MethodExpr) Validate() error {
return verr
}
+// validateInterceptors validates the method interceptors.
+func (m *MethodExpr) validateInterceptors() *eval.ValidationErrors {
+ verr := new(eval.ValidationErrors)
+ for _, i := range m.ClientInterceptors {
+ verr.Merge(i.validate(m))
+ }
+ for _, i := range m.ServerInterceptors {
+ verr.Merge(i.validate(m))
+ }
+ return verr
+}
+
// hasTag is a helper function that traverses the given attribute and all its
// bases recursively looking for an attribute with the given tag meta. This
// recursion is only needed for attributes that have not been finalized yet.
diff --git a/expr/root.go b/expr/root.go
index 0240e54e1f..fb215c40f9 100644
--- a/expr/root.go
+++ b/expr/root.go
@@ -18,6 +18,8 @@ type (
API *APIExpr
// Services contains the list of services exposed by the API.
Services []*ServiceExpr
+ // Interceptors contains the list of interceptors.
+ Interceptors []*InterceptorExpr
// Errors contains the list of errors returned by all the API
// methods.
Errors []*ErrorExpr
diff --git a/expr/service.go b/expr/service.go
index 1a7d88e9cf..ab906c4688 100644
--- a/expr/service.go
+++ b/expr/service.go
@@ -27,6 +27,10 @@ type (
// potentially multiple schemes. Incoming requests must validate
// at least one requirement to be authorized.
Requirements []*SecurityExpr
+ // ClientInterceptors is the list of client interceptors.
+ ClientInterceptors []*InterceptorExpr
+ // ServerInterceptors is the list of server interceptors.
+ ServerInterceptors []*InterceptorExpr
// Meta is a set of key/value pairs with semantic that is
// specific to each generator.
Meta MetaExpr
diff --git a/go.mod b/go.mod
index 17d9e52fcc..f2a5d754de 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,8 @@
module goa.design/goa/v3
-go 1.22.0
+go 1.22.7
-toolchain go1.23.1
+toolchain go1.23.3
require (
github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598
@@ -14,9 +14,9 @@ require (
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
golang.org/x/text v0.21.0
- golang.org/x/tools v0.28.0
- google.golang.org/grpc v1.69.2
- google.golang.org/protobuf v1.36.1
+ golang.org/x/tools v0.29.0
+ google.golang.org/grpc v1.69.4
+ google.golang.org/protobuf v1.36.3
gopkg.in/yaml.v3 v3.0.1
)
@@ -32,8 +32,8 @@ require (
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.22.0 // indirect
- golang.org/x/net v0.32.0 // indirect
+ golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
+ golang.org/x/sys v0.29.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
)
diff --git a/go.sum b/go.sum
index 7af0c27c31..2c8593e441 100644
--- a/go.sum
+++ b/go.sum
@@ -64,22 +64,22 @@ go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HY
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
-golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
-golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
-golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
-google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
-google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
-google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
-google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
+golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
+google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
+google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
+google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
+google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go
index c1818960c9..b570c4828b 100644
--- a/http/codegen/service_data.go
+++ b/http/codegen/service_data.go
@@ -595,7 +595,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
scope := codegen.NewNameScope()
scope.Unique("c") // 'c' is reserved as the client's receiver name.
scope.Unique("v") // 'v' is reserved as the request builder payload argument name.
- rd := &ServiceData{
+ sd := &ServiceData{
Service: svc,
ServerStruct: "Server",
MountPointStruct: "MountPoint",
@@ -641,7 +641,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
VarName: scope.Unique(codegen.Goify(s.FilePath, true)),
ArgName: scope.Unique(fmt.Sprintf("fileSystem%s", codegen.Goify(s.FilePath, true))),
}
- rd.FileServers = append(rd.FileServers, data)
+ sd.FileServers = append(sd.FileServers, data)
}
for _, httpEndpoint := range httpSvc.HTTPEndpoints {
@@ -672,10 +672,10 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
// Path params may override requiredness, need to check payload.
pointer = httpEndpoint.MethodExpr.Payload.IsPrimitivePointer(arg, true)
}
- name := rd.Scope.Name(codegen.Goify(arg, false))
+ name := sd.Scope.Name(codegen.Goify(arg, false))
var vcode string
if att.Validation != nil {
- ctx := httpContext("", rd.Scope, true, false)
+ ctx := httpContext("", sd.Scope, true, false)
vcode = codegen.AttributeValidationCode(att, nil, ctx, true, expr.IsAlias(att.Type), name, arg)
}
initArgs[j] = &InitArgData{
@@ -686,8 +686,8 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
Description: att.Description,
FieldName: codegen.Goify(arg, true),
FieldType: patt.Type,
- TypeName: rd.Scope.GoTypeName(att),
- TypeRef: rd.Scope.GoTypeRef(att),
+ TypeName: sd.Scope.GoTypeName(att),
+ TypeRef: sd.Scope.GoTypeRef(att),
Type: att.Type,
Pointer: pointer,
Required: true,
@@ -727,7 +727,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
}
}
- payload := buildPayloadData(httpEndpoint, rd)
+ payload := buildPayloadData(httpEndpoint, sd)
var (
reqs service.RequirementsData
@@ -817,8 +817,8 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
ServiceVarName: svc.VarName,
ServicePkgName: svc.PkgName,
Payload: payload,
- Result: buildResultData(httpEndpoint, rd),
- Errors: buildErrorsData(httpEndpoint, rd),
+ Result: buildResultData(httpEndpoint, sd),
+ Errors: buildErrorsData(httpEndpoint, sd),
HeaderSchemes: hsch,
BodySchemes: bosch,
QuerySchemes: qsch,
@@ -837,7 +837,7 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
Requirements: reqs,
}
if httpEndpoint.MethodExpr.IsStreaming() {
- initWebSocketData(ed, httpEndpoint, rd)
+ initWebSocketData(ed, httpEndpoint, sd)
}
if httpEndpoint.MultipartRequest {
@@ -870,26 +870,26 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
}
}
- rd.Endpoints = append(rd.Endpoints, ed)
+ sd.Endpoints = append(sd.Endpoints, ed)
}
for _, a := range httpSvc.HTTPEndpoints {
collectUserTypes(a.Body.Type, func(ut expr.UserType) {
- if d := attributeTypeData(ut, true, true, true, rd); d != nil {
- rd.ServerBodyAttributeTypes = append(rd.ServerBodyAttributeTypes, d)
+ if d := attributeTypeData(ut, true, true, true, sd); d != nil {
+ sd.ServerBodyAttributeTypes = append(sd.ServerBodyAttributeTypes, d)
}
- if d := attributeTypeData(ut, true, false, false, rd); d != nil {
- rd.ClientBodyAttributeTypes = append(rd.ClientBodyAttributeTypes, d)
+ if d := attributeTypeData(ut, true, false, false, sd); d != nil {
+ sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
}
})
if a.MethodExpr.StreamingPayload.Type != expr.Empty {
collectUserTypes(a.StreamingBody.Type, func(ut expr.UserType) {
- if d := attributeTypeData(ut, true, true, true, rd); d != nil {
- rd.ServerBodyAttributeTypes = append(rd.ServerBodyAttributeTypes, d)
+ if d := attributeTypeData(ut, true, true, true, sd); d != nil {
+ sd.ServerBodyAttributeTypes = append(sd.ServerBodyAttributeTypes, d)
}
- if d := attributeTypeData(ut, true, false, false, rd); d != nil {
- rd.ClientBodyAttributeTypes = append(rd.ClientBodyAttributeTypes, d)
+ if d := attributeTypeData(ut, true, false, false, sd); d != nil {
+ sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
}
})
}
@@ -900,8 +900,8 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
// NOTE: ServerBodyAttributeTypes for response body types are
// collected in buildResponseBodyType because we have to generate
// body types for each view in a result type.
- if d := attributeTypeData(ut, false, true, false, rd); d != nil {
- rd.ClientBodyAttributeTypes = append(rd.ClientBodyAttributeTypes, d)
+ if d := attributeTypeData(ut, false, true, false, sd); d != nil {
+ sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
}
})
}
@@ -912,14 +912,14 @@ func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData {
// NOTE: ServerBodyAttributeTypes for error response body types are
// collected in buildResponseBodyType because we have to generate
// body types for each view in a result type.
- if d := attributeTypeData(ut, false, true, false, rd); d != nil {
- rd.ClientBodyAttributeTypes = append(rd.ClientBodyAttributeTypes, d)
+ if d := attributeTypeData(ut, false, true, false, sd); d != nil {
+ sd.ClientBodyAttributeTypes = append(sd.ClientBodyAttributeTypes, d)
}
})
}
}
- return rd
+ return sd
}
// makeHTTPType traverses the attribute recursively and performs these actions:
diff --git a/pkg/interceptor.go b/pkg/interceptor.go
new file mode 100644
index 0000000000..022d7a4de6
--- /dev/null
+++ b/pkg/interceptor.go
@@ -0,0 +1,22 @@
+package goa
+
+import "context"
+
+type (
+ // InterceptorInfo contains information about the request shared between
+ // all interceptors in the service chain. It provides access to the service name,
+ // method name, endpoint function, and request payload.
+ InterceptorInfo struct {
+ // Name of service handling request
+ Service string
+ // Name of method handling request
+ Method string
+ // Endpoint of request, can be used for retrying
+ Endpoint Endpoint
+ // Payload of request
+ RawPayload any
+ }
+
+ // NextFunc is a function that will continue the request processing chain.
+ NextFunc func(ctx context.Context) (any, error)
+)
From fde6f80f7ce1131e643824ced145771d839dba0d Mon Sep 17 00:00:00 2001
From: Raphael Simon
Date: Tue, 26 Nov 2024 13:14:30 -0800
Subject: [PATCH 2/6] Remove Twitter badge
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index cd38d1919e..7735bc20e3 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,6 @@
-
From c2caae636ae90d3a58f29370fdbc80977e716ad7 Mon Sep 17 00:00:00 2001
From: Raphael Simon
Date: Tue, 26 Nov 2024 13:30:40 -0800
Subject: [PATCH 3/6] Improve generated comments
---
codegen/service/interceptors.go | 2 ++
codegen/service/templates/client_wrappers.go.tpl | 2 +-
codegen/service/templates/endpoint_wrappers.go.tpl | 2 +-
codegen/service/testdata/client_code.go | 2 +-
codegen/service/testdata/endpoint_code.go | 8 ++++----
5 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/codegen/service/interceptors.go b/codegen/service/interceptors.go
index 57d734e0c6..a383ddbb24 100644
--- a/codegen/service/interceptors.go
+++ b/codegen/service/interceptors.go
@@ -33,6 +33,7 @@ type (
// InterceptorData describes a single interceptor.
InterceptorData struct {
Name string
+ DesignName string
UnexportedName string
Description string
PayloadRef string
@@ -150,6 +151,7 @@ func buildMethodInterceptors(m *expr.MethodExpr, scope *codegen.NameScope) ([]*I
return &InterceptorData{
Name: codegen.Goify(intr.Name, true),
+ DesignName: intr.Name,
UnexportedName: codegen.Goify(intr.Name, false),
Description: intr.Description,
PayloadRef: methodData.PayloadRef,
diff --git a/codegen/service/templates/client_wrappers.go.tpl b/codegen/service/templates/client_wrappers.go.tpl
index 94364c38cd..dd52071dd9 100644
--- a/codegen/service/templates/client_wrappers.go.tpl
+++ b/codegen/service/templates/client_wrappers.go.tpl
@@ -7,7 +7,7 @@ func Wrap{{ .MethodVarName }}ClientEndpoint(endpoint goa.Endpoint, i ClientInter
}
{{- range .ClientInterceptors }}
-{{ comment (printf "wrapClient%s applies the %s interceptor to endpoints." .Name .Name) }}
+{{ comment (printf "wrapClient%s applies the %s interceptor to endpoints." .Name .DesignName) }}
func wrapClient{{ .Name }}(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &{{ .Name }}Info{
diff --git a/codegen/service/templates/endpoint_wrappers.go.tpl b/codegen/service/templates/endpoint_wrappers.go.tpl
index bae0b9fa25..63dcf39e1e 100644
--- a/codegen/service/templates/endpoint_wrappers.go.tpl
+++ b/codegen/service/templates/endpoint_wrappers.go.tpl
@@ -7,7 +7,7 @@ func Wrap{{ .MethodVarName }}Endpoint(endpoint goa.Endpoint, i ServerInterceptor
}
{{- range .ServerInterceptors }}
-{{ comment (printf "wrap%s applies the %s interceptor to endpoints." .Name .Name) }}
+{{ comment (printf "wrap%s applies the %s interceptor to endpoints." .Name .DesignName) }}
func wrap{{ .Name }}(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &{{ .Name }}Info{
diff --git a/codegen/service/testdata/client_code.go b/codegen/service/testdata/client_code.go
index 96f5a7b6a0..8153742480 100644
--- a/codegen/service/testdata/client_code.go
+++ b/codegen/service/testdata/client_code.go
@@ -315,7 +315,7 @@ func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.E
return endpoint
}
-// wrapClientTracing applies the Tracing interceptor to endpoints.
+// wrapClientTracing applies the tracing interceptor to endpoints.
func wrapClientTracing(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &TracingInfo{
diff --git a/codegen/service/testdata/endpoint_code.go b/codegen/service/testdata/endpoint_code.go
index 27967a6e08..98db14f0d1 100644
--- a/codegen/service/testdata/endpoint_code.go
+++ b/codegen/service/testdata/endpoint_code.go
@@ -551,7 +551,7 @@ func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoin
return endpoint
}
-// wrapLogging applies the Logging interceptor to endpoints.
+// wrapLogging applies the logging interceptor to endpoints.
func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
@@ -605,7 +605,7 @@ func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoin
return endpoint
}
-// wrapLogging applies the Logging interceptor to endpoints.
+// wrapLogging applies the logging interceptor to endpoints.
func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
@@ -621,7 +621,7 @@ func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa
}
}
-// wrapMetrics applies the Metrics interceptor to endpoints.
+// wrapMetrics applies the metrics interceptor to endpoints.
func wrapMetrics(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &MetricsInfo{
@@ -679,7 +679,7 @@ func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoin
return endpoint
}
-// wrapLogging applies the Logging interceptor to endpoints.
+// wrapLogging applies the logging interceptor to endpoints.
func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
From 6d9bc66cf3112c928bc4cff625ec26b8e7c6b8ed Mon Sep 17 00:00:00 2001
From: Raphael Simon
Date: Thu, 19 Dec 2024 10:50:11 -0400
Subject: [PATCH 4/6] Add support for interceptors in generated examples
---
codegen/cli/cli.go | 28 +-
codegen/example/example_server.go | 20 +-
.../example/templates/server_endpoints.go.tpl | 2 +-
.../templates/server_interceptors.go.tpl | 23 ++
codegen/generator/example.go | 7 +
codegen/service/client.go | 12 -
codegen/service/endpoint.go | 46 +--
codegen/service/example_interceptors.go | 86 ++++++
codegen/service/example_interceptors_test.go | 119 ++++++++
codegen/service/example_svc.go | 6 +-
codegen/service/interceptors.go | 280 +++++++-----------
codegen/service/interceptors_test.go | 63 ++--
codegen/service/service.go | 8 +-
codegen/service/service_data.go | 228 +++++++++++---
.../client_interceptor_wrappers.go.tpl | 18 ++
.../templates/client_interceptors.go.tpl | 12 +
.../service/templates/client_wrappers.go.tpl | 22 --
.../templates/endpoint_wrappers.go.tpl | 23 --
.../example_client_interceptor.go.tpl | 24 ++
....tpl => example_security_authfuncs.go.tpl} | 0
.../example_server_interceptor.go.tpl | 24 ++
...nit.go.tpl => example_service_init.go.tpl} | 0
...t.go.tpl => example_service_struct.go.tpl} | 0
codegen/service/templates/interceptors.go.tpl | 35 +--
.../server_interceptor_wrappers.go.tpl | 18 ++
.../templates/server_interceptors.go.tpl | 12 +
codegen/service/testdata/client_code.go | 24 +-
codegen/service/testdata/endpoint_code.go | 89 +-----
.../api_interceptor_service_client.golden | 35 +++
.../api_interceptor_service_server.golden | 35 +++
.../chained_interceptor_service_client.golden | 55 ++++
.../chained_interceptor_service_server.golden | 55 ++++
.../client_interceptor_service_client.golden | 35 +++
...ultiple_interceptors_service_client.golden | 45 +++
...ultiple_interceptors_service_server.golden | 45 +++
...rvices_interceptors_service2_client.golden | 45 +++
...rvices_interceptors_service2_server.golden | 45 +++
...ervices_interceptors_service_client.golden | 45 +++
...ervices_interceptors_service_server.golden | 45 +++
..._interceptor_by_name_service_server.golden | 35 +++
.../server_interceptor_service_server.golden | 35 +++
.../testdata/example_interceptors_dsls.go | 96 ++++++
...read-payload_client_interceptors.go.golden | 14 +
...ead-payload_interceptor_wrappers.go.golden | 26 ++
...ad-payload_service_interceptors.go.golden} | 15 +-
...-read-result_client_interceptors.go.golden | 14 +
...read-result_interceptor_wrappers.go.golden | 26 ++
...ead-result_service_interceptors.go.golden} | 15 +-
...rite-payload_client_interceptors.go.golden | 14 +
...ite-payload_interceptor_wrappers.go.golden | 26 ++
...te-payload_service_interceptors.go.golden} | 15 +-
...write-result_client_interceptors.go.golden | 14 +
...rite-result_interceptor_wrappers.go.golden | 26 ++
...ite-result_service_interceptors.go.golden} | 15 +-
...rite-payload_client_interceptors.go.golden | 14 +
...ite-payload_interceptor_wrappers.go.golden | 26 ++
...te-payload_service_interceptors.go.golden} | 15 +-
...write-result_client_interceptors.go.golden | 14 +
...rite-result_interceptor_wrappers.go.golden | 26 ++
...ite-result_service_interceptors.go.golden} | 15 +-
.../interceptors/multiple-interceptors.golden | 30 --
...interceptors_client_interceptors.go.golden | 26 ++
...nterceptors_interceptor_wrappers.go.golden | 52 ++++
...nterceptors_service_interceptors.go.golden | 26 ++
.../single-api-server-interceptor.golden | 16 -
...interceptor_interceptor_wrappers.go.golden | 13 +
...interceptor_service_interceptors.go.golden | 28 ++
.../single-client-interceptor.golden | 16 -
...-interceptor_client_interceptors.go.golden | 21 ++
...interceptor_interceptor_wrappers.go.golden | 13 +
.../single-method-server-interceptor.golden | 16 -
...interceptor_interceptor_wrappers.go.golden | 13 +
...interceptor_service_interceptors.go.golden | 21 ++
.../single-service-server-interceptor.golden | 16 -
...interceptor_interceptor_wrappers.go.golden | 13 +
...interceptor_service_interceptors.go.golden | 28 ++
...ead-payload_interceptor_wrappers.go.golden | 13 +
...ad-payload_service_interceptors.go.golden} | 15 +-
...read-result_interceptor_wrappers.go.golden | 13 +
...ead-result_service_interceptors.go.golden} | 15 +-
.../streaming-interceptors.golden | 16 -
...nterceptors_interceptor_wrappers.go.golden | 13 +
...nterceptors_service_interceptors.go.golden | 21 ++
dsl/method_test.go | 24 +-
expr/http_endpoint_test.go | 4 +-
expr/method_test.go | 100 +++++++
grpc/codegen/example_cli.go | 29 +-
grpc/codegen/example_cli_test.go | 6 +-
grpc/codegen/templates/do_grpc_cli.go.tpl | 14 +-
grpc/codegen/templates/parse_endpoint.go.tpl | 15 +-
.../testdata/client-interceptors.golden | 30 ++
.../client-no-server-pkgpath.golden | 4 +-
.../{ => testdata}/client-no-server.golden | 4 +-
...r-hosting-multiple-services-pkgpath.golden | 4 +-
...nt-server-hosting-multiple-services.golden | 4 +-
...rver-hosting-service-subset-pkgpath.golden | 4 +-
...lient-server-hosting-service-subset.golden | 4 +-
grpc/codegen/testdata/dsls.go | 24 ++
http/codegen/client_cli.go | 7 +
http/codegen/example_cli.go | 34 ++-
http/codegen/templates/cli_end.go.tpl | 29 +-
http/codegen/templates/cli_start.go.tpl | 10 +
http/codegen/templates/parse_endpoint.go.tpl | 14 +-
pkg/interceptor.go | 5 -
104 files changed, 2321 insertions(+), 712 deletions(-)
create mode 100644 codegen/example/templates/server_interceptors.go.tpl
create mode 100644 codegen/service/example_interceptors.go
create mode 100644 codegen/service/example_interceptors_test.go
create mode 100644 codegen/service/templates/client_interceptor_wrappers.go.tpl
create mode 100644 codegen/service/templates/client_interceptors.go.tpl
create mode 100644 codegen/service/templates/example_client_interceptor.go.tpl
rename codegen/service/templates/{security_authfuncs.go.tpl => example_security_authfuncs.go.tpl} (100%)
create mode 100644 codegen/service/templates/example_server_interceptor.go.tpl
rename codegen/service/templates/{service_init.go.tpl => example_service_init.go.tpl} (100%)
rename codegen/service/templates/{service_struct.go.tpl => example_service_struct.go.tpl} (100%)
create mode 100644 codegen/service/templates/server_interceptor_wrappers.go.tpl
create mode 100644 codegen/service/templates/server_interceptors.go.tpl
create mode 100644 codegen/service/testdata/example_interceptors/api_interceptor_service_client.golden
create mode 100644 codegen/service/testdata/example_interceptors/api_interceptor_service_server.golden
create mode 100644 codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden
create mode 100644 codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden
create mode 100644 codegen/service/testdata/example_interceptors/client_interceptor_service_client.golden
create mode 100644 codegen/service/testdata/example_interceptors/multiple_interceptors_service_client.golden
create mode 100644 codegen/service/testdata/example_interceptors/multiple_interceptors_service_server.golden
create mode 100644 codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_client.golden
create mode 100644 codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_server.golden
create mode 100644 codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_client.golden
create mode 100644 codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_server.golden
create mode 100644 codegen/service/testdata/example_interceptors/server_interceptor_by_name_service_server.golden
create mode 100644 codegen/service/testdata/example_interceptors/server_interceptor_service_server.golden
create mode 100644 codegen/service/testdata/example_interceptors_dsls.go
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{interceptor-with-read-payload.golden => interceptor-with-read-payload_service_interceptors.go.golden} (70%)
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{interceptor-with-read-result.golden => interceptor-with-read-result_service_interceptors.go.golden} (69%)
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{interceptor-with-read-write-payload.golden => interceptor-with-read-write-payload_service_interceptors.go.golden} (71%)
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{interceptor-with-read-write-result.golden => interceptor-with-read-write-result_service_interceptors.go.golden} (71%)
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{interceptor-with-write-payload.golden => interceptor-with-write-payload_service_interceptors.go.golden} (70%)
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{interceptor-with-write-result.golden => interceptor-with-write-result_service_interceptors.go.golden} (69%)
delete mode 100644 codegen/service/testdata/interceptors/multiple-interceptors.golden
create mode 100644 codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden
create mode 100644 codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden
delete mode 100644 codegen/service/testdata/interceptors/single-api-server-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden
create mode 100644 codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden
delete mode 100644 codegen/service/testdata/interceptors/single-client-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden
delete mode 100644 codegen/service/testdata/interceptors/single-method-server-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden
create mode 100644 codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden
delete mode 100644 codegen/service/testdata/interceptors/single-service-server-interceptor.golden
create mode 100644 codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden
create mode 100644 codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{streaming-interceptors-with-read-payload.golden => streaming-interceptors-with-read-payload_service_interceptors.go.golden} (71%)
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden
rename codegen/service/testdata/interceptors/{streaming-interceptors-with-read-result.golden => streaming-interceptors-with-read-result_service_interceptors.go.golden} (70%)
delete mode 100644 codegen/service/testdata/interceptors/streaming-interceptors.golden
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
create mode 100644 codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden
create mode 100644 grpc/codegen/testdata/client-interceptors.golden
rename grpc/codegen/{ => testdata}/client-no-server-pkgpath.golden (91%)
rename grpc/codegen/{ => testdata}/client-no-server.golden (91%)
rename grpc/codegen/{ => testdata}/client-server-hosting-multiple-services-pkgpath.golden (92%)
rename grpc/codegen/{ => testdata}/client-server-hosting-multiple-services.golden (91%)
rename grpc/codegen/{ => testdata}/client-server-hosting-service-subset-pkgpath.golden (92%)
rename grpc/codegen/{ => testdata}/client-server-hosting-service-subset.golden (91%)
diff --git a/codegen/cli/cli.go b/codegen/cli/cli.go
index a69f747036..1135424ae3 100644
--- a/codegen/cli/cli.go
+++ b/codegen/cli/cli.go
@@ -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.
@@ -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"
@@ -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,
}
}
diff --git a/codegen/example/example_server.go b/codegen/example/example_server.go
index f896106eb4..35576abf21 100644
--- a/codegen/example/example_server.go
+++ b/codegen/example/example_server.go
@@ -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
@@ -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),
@@ -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"),
diff --git a/codegen/example/templates/server_endpoints.go.tpl b/codegen/example/templates/server_endpoints.go.tpl
index 3916ab3bd4..97be6436ee 100644
--- a/codegen/example/templates/server_endpoints.go.tpl
+++ b/codegen/example/templates/server_endpoints.go.tpl
@@ -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 }}
diff --git a/codegen/example/templates/server_interceptors.go.tpl b/codegen/example/templates/server_interceptors.go.tpl
new file mode 100644
index 0000000000..c8bf2215b7
--- /dev/null
+++ b/codegen/example/templates/server_interceptors.go.tpl
@@ -0,0 +1,23 @@
+{{- if mustInitServices .Services }}
+ {{- if .HasInterceptors }}
+ {{ comment "Initialize the interceptors." }}
+ var (
+ {{- range .Services }}
+ {{- if and .Methods .ServerInterceptors }}
+ {{ .VarName }}Interceptors {{ .PkgName }}.ServerInterceptors
+ {{- end }}
+ {{- end }}
+ )
+ {{- end }}
+ {{- if .HasInterceptors }}
+ {
+ {{- end }}
+ {{- range .Services }}
+ {{- if and .Methods .ServerInterceptors }}
+ {{ .VarName }}Interceptors = {{ $.InterPkg }}.New{{ .StructName }}ServerInterceptors()
+ {{- end }}
+ {{- end }}
+ {{- if .HasInterceptors }}
+ }
+ {{- end }}
+{{- end }}
\ No newline at end of file
diff --git a/codegen/generator/example.go b/codegen/generator/example.go
index c78545a3f7..827880149f 100644
--- a/codegen/generator/example.go
+++ b/codegen/generator/example.go
@@ -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...)
@@ -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 {
diff --git a/codegen/service/client.go b/codegen/service/client.go
index f991e81aed..a088de0d37 100644
--- a/codegen/service/client.go
+++ b/codegen/service/client.go
@@ -45,18 +45,6 @@ func ClientFile(_ string, service *expr.ServiceExpr) *codegen.File {
Source: readTemplate("service_client_method"),
Data: m,
})
- if len(m.ClientInterceptors) > 0 {
- sections = append(sections, &codegen.SectionTemplate{
- Name: "client-wrapper",
- Source: readTemplate("client_wrappers"),
- Data: map[string]interface{}{
- "Method": m.Name,
- "MethodVarName": codegen.Goify(m.Name, true),
- "Service": svc.Name,
- "ClientInterceptors": m.ClientInterceptors,
- },
- })
- }
}
}
diff --git a/codegen/service/endpoint.go b/codegen/service/endpoint.go
index 45d476620e..b499a9e6f7 100644
--- a/codegen/service/endpoint.go
+++ b/codegen/service/endpoint.go
@@ -25,15 +25,17 @@ type (
ServiceVarName string
// Methods lists the endpoint struct methods.
Methods []*EndpointMethodData
- // HasServerInterceptors indicates if the service has server interceptors.
- HasServerInterceptors bool
- // HasClientInterceptors indicates if the service has client interceptors.
- HasClientInterceptors bool
// ClientInitArgs lists the arguments needed to instantiate the client.
ClientInitArgs string
// 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.
@@ -130,18 +132,6 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
Data: m,
FuncMap: map[string]any{"payloadVar": payloadVar},
})
- if len(m.ServerInterceptors) > 0 {
- sections = append(sections, &codegen.SectionTemplate{
- Name: "endpoint-wrapper",
- Source: readTemplate("endpoint_wrappers"),
- Data: map[string]interface{}{
- "MethodVarName": codegen.Goify(m.Name, true),
- "Method": m.Name,
- "Service": svc.Name,
- "ServerInterceptors": m.ServerInterceptors,
- },
- })
- }
}
}
@@ -153,34 +143,18 @@ func endpointData(service *expr.ServiceExpr) *EndpointsData {
methods := make([]*EndpointMethodData, len(svc.Methods))
names := make([]string, len(svc.Methods))
for i, m := range svc.Methods {
- serverInts, clientInts := buildMethodInterceptors(service.Method(m.Name), svc.Scope)
methods[i] = &EndpointMethodData{
MethodData: m,
ArgName: codegen.Goify(m.VarName, false),
ServiceName: svc.Name,
ServiceVarName: serviceInterfaceName,
ClientVarName: clientStructName,
- ServerInterceptors: serverInts,
- ClientInterceptors: clientInts,
+ ServerInterceptors: m.ServerInterceptors,
+ ClientInterceptors: m.ClientInterceptors,
}
names[i] = codegen.Goify(m.VarName, false)
}
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, service.Name)
- var hasServerInterceptors, hasClientInterceptors bool
- for _, m := range methods {
- if len(m.ServerInterceptors) > 0 {
- hasServerInterceptors = true
- if hasClientInterceptors {
- break
- }
- }
- if len(m.ClientInterceptors) > 0 {
- hasClientInterceptors = true
- if hasServerInterceptors {
- break
- }
- }
- }
return &EndpointsData{
Name: service.Name,
Description: desc,
@@ -189,9 +163,9 @@ func endpointData(service *expr.ServiceExpr) *EndpointsData {
ServiceVarName: serviceInterfaceName,
ClientInitArgs: strings.Join(names, ", "),
Methods: methods,
- HasServerInterceptors: hasServerInterceptors,
- HasClientInterceptors: hasClientInterceptors,
Schemes: svc.Schemes,
+ HasServerInterceptors: len(svc.ServerInterceptors) > 0,
+ HasClientInterceptors: len(svc.ClientInterceptors) > 0,
}
}
diff --git a/codegen/service/example_interceptors.go b/codegen/service/example_interceptors.go
new file mode 100644
index 0000000000..c348beae57
--- /dev/null
+++ b/codegen/service/example_interceptors.go
@@ -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: "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: "client-interceptor",
+ Source: readTemplate("example_client_interceptor"),
+ Data: data,
+ },
+ },
+ })
+ }
+ }
+
+ return files
+}
diff --git a/codegen/service/example_interceptors_test.go b/codegen/service/example_interceptors_test.go
new file mode 100644
index 0000000000..37b8aa4207
--- /dev/null
+++ b/codegen/service/example_interceptors_test.go
@@ -0,0 +1,119 @@
+package service
+
+import (
+ "bytes"
+ "go/format"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "goa.design/goa/v3/codegen/service/testdata"
+ "goa.design/goa/v3/expr"
+)
+
+func TestExampleInterceptorsFiles(t *testing.T) {
+ cases := []struct {
+ Name string
+ DSL func()
+ ExpectedFiles []string
+ }{
+ {
+ Name: "no-interceptors",
+ DSL: testdata.NoInterceptorExampleDSL,
+ },
+ {
+ Name: "server-interceptor",
+ DSL: testdata.ServerInterceptorExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "server_interceptor_service_server.go"),
+ },
+ },
+ {
+ Name: "client-interceptor",
+ DSL: testdata.ClientInterceptorExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "client_interceptor_service_client.go"),
+ },
+ },
+ {
+ Name: "server-interceptor-by-name",
+ DSL: testdata.ServerInterceptorByNameExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "server_interceptor_by_name_service_server.go"),
+ },
+ },
+ {
+ Name: "multiple-interceptors",
+ DSL: testdata.MultipleInterceptorsExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "multiple_interceptors_service_server.go"),
+ filepath.Join("interceptors", "multiple_interceptors_service_client.go"),
+ },
+ },
+ {
+ Name: "multiple-services",
+ DSL: testdata.MultipleServicesInterceptorsExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "multiple_services_interceptors_service_server.go"),
+ filepath.Join("interceptors", "multiple_services_interceptors_service_client.go"),
+ filepath.Join("interceptors", "multiple_services_interceptors_service2_server.go"),
+ filepath.Join("interceptors", "multiple_services_interceptors_service2_client.go"),
+ },
+ },
+ {
+ Name: "api-interceptors",
+ DSL: testdata.APIInterceptorExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "api_interceptor_service_server.go"),
+ filepath.Join("interceptors", "api_interceptor_service_client.go"),
+ },
+ },
+ {
+ Name: "chained-interceptors",
+ DSL: testdata.ChainedInterceptorExampleDSL,
+ ExpectedFiles: []string{
+ filepath.Join("interceptors", "chained_interceptor_service_server.go"),
+ filepath.Join("interceptors", "chained_interceptor_service_client.go"),
+ },
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ // Run DSL
+ root := expr.RunDSL(t, c.DSL)
+ require.NotNil(t, root)
+
+ // Generate files
+ fs := ExampleInterceptorsFiles("", root)
+ require.Len(t, fs, len(c.ExpectedFiles))
+
+ // Verify file paths
+ paths := make([]string, len(fs))
+ for i, f := range fs {
+ paths[i] = f.Path
+ }
+ assert.ElementsMatch(t, c.ExpectedFiles, paths)
+
+ // Verify file content
+ for _, f := range fs {
+ buf := new(bytes.Buffer)
+ for _, s := range f.SectionTemplates {
+ require.NoError(t, s.Write(buf))
+ }
+ bs, err := format.Source(buf.Bytes())
+ require.NoError(t, err, buf.String())
+ code := string(bs)
+
+ // Use the base name of the generated file without extension for the golden file
+ baseName := filepath.Base(f.Path)
+ ext := filepath.Ext(baseName)
+ goldenName := baseName[:len(baseName)-len(ext)] + ".golden"
+ golden := filepath.Join("testdata", "example_interceptors", goldenName)
+ compareOrUpdateGolden(t, code, golden)
+ }
+ })
+ }
+}
diff --git a/codegen/service/example_svc.go b/codegen/service/example_svc.go
index 7f4b156054..46b977eff1 100644
--- a/codegen/service/example_svc.go
+++ b/codegen/service/example_svc.go
@@ -78,18 +78,18 @@ func exampleServiceFile(genpkg string, _ *expr.RootExpr, svc *expr.ServiceExpr,
codegen.Header("", apipkg, specs),
{
Name: "basic-service-struct",
- Source: readTemplate("service_struct"),
+ Source: readTemplate("example_service_struct"),
Data: data,
}, {
Name: "basic-service-init",
- Source: readTemplate("service_init"),
+ Source: readTemplate("example_service_init"),
Data: data,
},
}
if len(data.Schemes) > 0 {
sections = append(sections, &codegen.SectionTemplate{
Name: "security-authfuncs",
- Source: readTemplate("security_authfuncs"),
+ Source: readTemplate("example_security_authfuncs"),
Data: data,
})
}
diff --git a/codegen/service/interceptors.go b/codegen/service/interceptors.go
index a383ddbb24..78ddb543ca 100644
--- a/codegen/service/interceptors.go
+++ b/codegen/service/interceptors.go
@@ -7,175 +7,144 @@ import (
"goa.design/goa/v3/expr"
)
-type (
- // ServiceInterceptorData contains all data needed for generating interceptor code
- ServiceInterceptorData struct {
- Service string
- PkgName string
- Methods []*MethodInterceptorData
- ServerInterceptors []*InterceptorData
- ClientInterceptors []*InterceptorData
- AllInterceptors []*InterceptorData
- HasPrivateImplementationTypes bool
- }
+// InterceptorsFiles returns the interceptors files for the given service.
+func InterceptorsFiles(genpkg string, service *expr.ServiceExpr) []*codegen.File {
+ var files []*codegen.File
+ svc := Services.Get(service.Name)
- // MethodInterceptorData contains interceptor data for a single method
- MethodInterceptorData struct {
- Service string
- Method string
- MethodVarName string
- PayloadRef string
- ResultRef string
- ServerInterceptors []*InterceptorData
- ClientInterceptors []*InterceptorData
+ // Generate service-specific interceptor files
+ if len(svc.ServerInterceptors) > 0 {
+ files = append(files, interceptorFile(svc, true))
}
-
- // InterceptorData describes a single interceptor.
- InterceptorData struct {
- Name string
- DesignName string
- UnexportedName string
- Description string
- PayloadRef string
- ResultRef string
- ReadPayload []*AttributeData
- WritePayload []*AttributeData
- ReadResult []*AttributeData
- WriteResult []*AttributeData
- ServerStreamInputStruct string
- ClientStreamInputStruct string
+ if len(svc.ClientInterceptors) > 0 {
+ files = append(files, interceptorFile(svc, false))
}
- // AttributeData describes a single attribute.
- AttributeData struct {
- Name string
- TypeRef string
- FieldPointer bool
+ // Generate wrapper file if this service has any interceptors
+ if len(svc.ServerInterceptors) > 0 || len(svc.ClientInterceptors) > 0 {
+ files = append(files, wrapperFile(svc))
}
-)
-// InterceptorsFile returns the interceptors file for the given service.
-func InterceptorsFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
- svc := Services.Get(service.Name)
- data := interceptorsData(service)
- if len(data.ServerInterceptors) == 0 && len(data.ClientInterceptors) == 0 {
- return nil
+ return files
+}
+
+// interceptorFile returns the file defining the interceptors.
+func interceptorFile(svc *Data, server bool) *codegen.File {
+ filename := "client_interceptors.go"
+ template := "client_interceptors"
+ section := "client-interceptors"
+ desc := "Client Interceptors"
+ var data []*InterceptorData
+ if server {
+ filename = "service_interceptors.go"
+ template = "server_interceptors"
+ section = "server-interceptors"
+ desc = "Server Interceptors"
+ data = svc.ServerInterceptors
+ }
+ desc = svc.Name + desc
+ path := filepath.Join(codegen.Gendir, svc.PathName, filename)
+
+ if !server {
+ // We don't want to generate duplicate interceptor info data structures for
+ // interceptors that are both server and client side.
+ serverInterceptors := make(map[string]struct{}, len(svc.ServerInterceptors))
+ for _, intr := range svc.ServerInterceptors {
+ serverInterceptors[intr.Name] = struct{}{}
+ }
+ for _, intr := range svc.ClientInterceptors {
+ if _, ok := serverInterceptors[intr.Name]; ok {
+ continue
+ }
+ data = append(data, intr)
+ }
}
- path := filepath.Join(codegen.Gendir, svc.PathName, "interceptors.go")
sections := []*codegen.SectionTemplate{
- codegen.Header(service.Name+" interceptors", svc.PkgName, []*codegen.ImportSpec{
+ codegen.Header(desc, svc.PkgName, []*codegen.ImportSpec{
{Path: "context"},
codegen.GoaImport(""),
}),
{
- Name: "interceptors",
+ Name: "section" + "-struct",
+ Source: readTemplate(template),
+ Data: svc,
+ },
+ }
+ if len(data) > 0 {
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: section,
Source: readTemplate("interceptors"),
Data: data,
- },
+ FuncMap: map[string]any{
+ "hasPrivateImplementationTypes": hasPrivateImplementationTypes,
+ },
+ })
}
- return &codegen.File{Path: path, SectionTemplates: sections}
-}
-
-func interceptorsData(service *expr.ServiceExpr) *ServiceInterceptorData {
- svc := Services.Get(service.Name)
- scope := svc.Scope
-
- // Build method data first
- methods := make([]*MethodInterceptorData, 0, len(service.Methods))
- seenInts := make(map[string]*InterceptorData)
- var serviceServerInts, serviceClientInts, allInts []*InterceptorData
- var hasTypes bool
-
- for _, m := range service.Methods {
- methodServerInts, methodClientInts := buildMethodInterceptors(m, scope)
- if len(methodServerInts) == 0 && len(methodClientInts) == 0 {
+ // Add wrapper sections for each method that has interceptors
+ for _, m := range svc.Methods {
+ if server && len(m.ServerInterceptors) == 0 || !server && len(m.ClientInterceptors) == 0 {
continue
}
- hasTypes = hasTypes || hasPrivateImplementationTypes(methodServerInts) || hasPrivateImplementationTypes(methodClientInts)
-
- // Add method data
- methods = append(methods, &MethodInterceptorData{
- Service: svc.Name,
- Method: m.Name,
- MethodVarName: codegen.Goify(m.Name, true),
- PayloadRef: scope.GoFullTypeRef(m.Payload, ""),
- ResultRef: scope.GoFullTypeRef(m.Result, ""),
- ServerInterceptors: methodServerInts,
- ClientInterceptors: methodClientInts,
- })
-
- // Collect unique interceptors
- for _, i := range methodServerInts {
- if _, ok := seenInts[i.Name]; !ok {
- seenInts[i.Name] = i
- serviceServerInts = append(serviceServerInts, i)
- allInts = append(allInts, i)
- }
+ template := "endpoint_wrappers"
+ templateName := "endpoint-wrapper"
+ if !server {
+ template = "client_wrappers"
+ templateName = "client-wrapper"
}
- for _, i := range methodClientInts {
- if _, ok := seenInts[i.Name]; !ok {
- seenInts[i.Name] = i
- serviceClientInts = append(serviceClientInts, i)
- allInts = append(allInts, i)
- }
- }
- }
-
- return &ServiceInterceptorData{
- Service: service.Name,
- PkgName: svc.PkgName,
- Methods: methods,
- ServerInterceptors: serviceServerInts,
- ClientInterceptors: serviceClientInts,
- AllInterceptors: allInts,
- HasPrivateImplementationTypes: hasTypes,
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: templateName,
+ Source: readTemplate(template),
+ Data: map[string]interface{}{
+ "MethodVarName": codegen.Goify(m.Name, true),
+ "Method": m.Name,
+ "Service": svc.Name,
+ "ServerInterceptors": m.ServerInterceptors,
+ "ClientInterceptors": m.ClientInterceptors,
+ },
+ })
}
+ return &codegen.File{Path: path, SectionTemplates: sections}
}
-func buildMethodInterceptors(m *expr.MethodExpr, scope *codegen.NameScope) ([]*InterceptorData, []*InterceptorData) {
- svc := Services.Get(m.Service.Name)
- methodData := svc.Method(m.Name)
- var serverEndpointStruct, clientEndpointStruct string
- if methodData.ServerStream != nil {
- serverEndpointStruct = methodData.ServerStream.EndpointStruct
- }
- if methodData.ClientStream != nil {
- clientEndpointStruct = methodData.ClientStream.EndpointStruct
- }
- var hasPrivateImplementationTypes bool
- buildInterceptor := func(intr *expr.InterceptorExpr) *InterceptorData {
- hasPrivateImplementationTypes = hasPrivateImplementationTypes ||
- intr.ReadPayload != nil || intr.WritePayload != nil || intr.ReadResult != nil || intr.WriteResult != nil
-
- return &InterceptorData{
- Name: codegen.Goify(intr.Name, true),
- DesignName: intr.Name,
- UnexportedName: codegen.Goify(intr.Name, false),
- Description: intr.Description,
- PayloadRef: methodData.PayloadRef,
- ResultRef: methodData.ResultRef,
- ServerStreamInputStruct: serverEndpointStruct,
- ClientStreamInputStruct: clientEndpointStruct,
- ReadPayload: collectAttributes(intr.ReadPayload, m.Payload, scope),
- WritePayload: collectAttributes(intr.WritePayload, m.Payload, scope),
- ReadResult: collectAttributes(intr.ReadResult, m.Result, scope),
- WriteResult: collectAttributes(intr.WriteResult, m.Result, scope),
- }
+// wrapperFile returns the file containing the interceptor wrappers.
+func wrapperFile(svc *Data) *codegen.File {
+ path := filepath.Join(codegen.Gendir, svc.PathName, "interceptor_wrappers.go")
+
+ var sections []*codegen.SectionTemplate
+ sections = append(sections, codegen.Header("Interceptor wrappers", svc.PkgName, []*codegen.ImportSpec{
+ {Path: "context"},
+ {Path: "fmt"},
+ codegen.GoaImport(""),
+ }))
+
+ // Generate the interceptor wrapper functions first (only once)
+ if len(svc.ServerInterceptors) > 0 {
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: "server-interceptor-wrappers",
+ Source: readTemplate("server_interceptor_wrappers"),
+ Data: map[string]interface{}{
+ "Service": svc.Name,
+ "ServerInterceptors": svc.ServerInterceptors,
+ },
+ })
}
-
- serverInts := make([]*InterceptorData, len(m.ServerInterceptors))
- for i, intr := range m.ServerInterceptors {
- serverInts[i] = buildInterceptor(intr)
+ if len(svc.ClientInterceptors) > 0 {
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: "client-interceptor-wrappers",
+ Source: readTemplate("client_interceptor_wrappers"),
+ Data: map[string]interface{}{
+ "Service": svc.Name,
+ "ClientInterceptors": svc.ClientInterceptors,
+ },
+ })
}
- clientInts := make([]*InterceptorData, len(m.ClientInterceptors))
- for i, intr := range m.ClientInterceptors {
- clientInts[i] = buildInterceptor(intr)
+ return &codegen.File{
+ Path: path,
+ SectionTemplates: sections,
}
-
- return serverInts, clientInts
}
// hasPrivateImplementationTypes returns true if any of the interceptors have
@@ -188,30 +157,3 @@ func hasPrivateImplementationTypes(interceptors []*InterceptorData) bool {
}
return false
}
-
-// collectAttributes builds AttributeData from an AttributeExpr
-func collectAttributes(attrNames, parent *expr.AttributeExpr, scope *codegen.NameScope) []*AttributeData {
- if attrNames == nil {
- return nil
- }
-
- obj := expr.AsObject(attrNames.Type)
- if obj == nil {
- return nil
- }
-
- data := make([]*AttributeData, len(*obj))
- for i, nat := range *obj {
- parentAttr := parent.Find(nat.Name)
- if parentAttr == nil {
- continue
- }
-
- data[i] = &AttributeData{
- Name: codegen.Goify(nat.Name, true),
- TypeRef: scope.GoTypeRef(parentAttr),
- FieldPointer: parent.IsPrimitivePointer(nat.Name, true),
- }
- }
- return data
-}
diff --git a/codegen/service/interceptors_test.go b/codegen/service/interceptors_test.go
index 0817ac2b31..e484535c9a 100644
--- a/codegen/service/interceptors_test.go
+++ b/codegen/service/interceptors_test.go
@@ -26,49 +26,46 @@ func init() {
func TestInterceptors(t *testing.T) {
cases := []struct {
- Name string
- DSL func()
+ Name string
+ DSL func()
+ expectedFileCount int
}{
- {"no-interceptors", testdata.NoInterceptorsDSL},
- {"single-api-server-interceptor", testdata.SingleAPIServerInterceptorDSL},
- {"single-service-server-interceptor", testdata.SingleServiceServerInterceptorDSL},
- {"single-method-server-interceptor", testdata.SingleMethodServerInterceptorDSL},
- {"single-client-interceptor", testdata.SingleClientInterceptorDSL},
- {"multiple-interceptors", testdata.MultipleInterceptorsDSL},
- {"interceptor-with-read-payload", testdata.InterceptorWithReadPayloadDSL},
- {"interceptor-with-write-payload", testdata.InterceptorWithWritePayloadDSL},
- {"interceptor-with-read-write-payload", testdata.InterceptorWithReadWritePayloadDSL},
- {"interceptor-with-read-result", testdata.InterceptorWithReadResultDSL},
- {"interceptor-with-write-result", testdata.InterceptorWithWriteResultDSL},
- {"interceptor-with-read-write-result", testdata.InterceptorWithReadWriteResultDSL},
- {"streaming-interceptors", testdata.StreamingInterceptorsDSL},
- {"streaming-interceptors-with-read-payload", testdata.StreamingInterceptorsWithReadPayloadDSL},
- {"streaming-interceptors-with-read-result", testdata.StreamingInterceptorsWithReadResultDSL},
+ {"no-interceptors", testdata.NoInterceptorsDSL, 0},
+ {"single-api-server-interceptor", testdata.SingleAPIServerInterceptorDSL, 2},
+ {"single-service-server-interceptor", testdata.SingleServiceServerInterceptorDSL, 2},
+ {"single-method-server-interceptor", testdata.SingleMethodServerInterceptorDSL, 2},
+ {"single-client-interceptor", testdata.SingleClientInterceptorDSL, 2},
+ {"multiple-interceptors", testdata.MultipleInterceptorsExampleDSL, 3},
+ {"interceptor-with-read-payload", testdata.InterceptorWithReadPayloadDSL, 3},
+ {"interceptor-with-write-payload", testdata.InterceptorWithWritePayloadDSL, 3},
+ {"interceptor-with-read-write-payload", testdata.InterceptorWithReadWritePayloadDSL, 3},
+ {"interceptor-with-read-result", testdata.InterceptorWithReadResultDSL, 3},
+ {"interceptor-with-write-result", testdata.InterceptorWithWriteResultDSL, 3},
+ {"interceptor-with-read-write-result", testdata.InterceptorWithReadWriteResultDSL, 3},
+ {"streaming-interceptors", testdata.StreamingInterceptorsDSL, 2},
+ {"streaming-interceptors-with-read-payload", testdata.StreamingInterceptorsWithReadPayloadDSL, 2},
+ {"streaming-interceptors-with-read-result", testdata.StreamingInterceptorsWithReadResultDSL, 2},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
root := runDSL(t, c.DSL)
require.Len(t, root.Services, 1)
- fs := InterceptorsFile("goa.design/goa/example", root.Services[0])
+ fs := InterceptorsFiles("goa.design/goa/example", root.Services[0])
- if c.Name == "no-interceptors" {
- assert.Nil(t, fs)
- return
- }
-
- require.NotNil(t, fs)
+ require.Len(t, fs, c.expectedFileCount)
+ for _, f := range fs {
+ buf := new(bytes.Buffer)
+ for _, s := range f.SectionTemplates[1:] {
+ require.NoError(t, s.Write(buf))
+ }
+ bs, err := format.Source(buf.Bytes())
+ require.NoError(t, err, buf.String())
+ code := strings.ReplaceAll(string(bs), "\r\n", "\n")
- buf := new(bytes.Buffer)
- for _, s := range fs.SectionTemplates[1:] {
- require.NoError(t, s.Write(buf))
+ golden := filepath.Join("testdata", "interceptors", c.Name+"_"+filepath.Base(f.Path)+".golden")
+ compareOrUpdateGolden(t, code, golden)
}
- bs, err := format.Source(buf.Bytes())
- require.NoError(t, err, buf.String())
- code := strings.ReplaceAll(string(bs), "\r\n", "\n")
-
- golden := filepath.Join("testdata", "interceptors", c.Name+".golden")
- compareOrUpdateGolden(t, code, golden)
})
}
}
diff --git a/codegen/service/service.go b/codegen/service/service.go
index a6c45448d1..5fc68eb860 100644
--- a/codegen/service/service.go
+++ b/codegen/service/service.go
@@ -195,11 +195,9 @@ func Files(genpkg string, service *expr.ServiceExpr, userTypePkgs map[string][]s
}
files := []*codegen.File{{Path: svcPath, SectionTemplates: sections}}
- // interceptor.go
- if file := InterceptorsFile(genpkg, service); file != nil {
- files = append(files, file)
- }
-
+ // service and client interceptors
+ files = append(files, InterceptorsFiles(genpkg, service)...)
+
// user types
paths := make([]string, len(typeDefSections))
i := 0
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index f0a62f81cb..9451d05df7 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -37,12 +37,6 @@ type (
// ServicesData encapsulates the data computed from the service designs.
ServicesData map[string]*Data
- // RequirementsData is the list of security requirements.
- RequirementsData []*RequirementData
-
- // SchemesData is the list of security schemes.
- SchemesData []*SchemeData
-
// Data contains the data used to render the code related to a single
// service.
Data struct {
@@ -70,6 +64,12 @@ type (
Methods []*MethodData
// Schemes is the list of security schemes required by the service methods.
Schemes SchemesData
+ // ServerInterceptors is the union of the server interceptors defined
+ // at the methods, service and the API level.
+ ServerInterceptors []*InterceptorData
+ // ClientInterceptors is the union of the client interceptors defined
+ // at the methods, service and the API level.
+ ClientInterceptors []*InterceptorData
// Scope initialized with all the service types.
Scope *codegen.NameScope
// ViewScope initialized with all the viewed types.
@@ -99,38 +99,6 @@ type (
unionValueMethods []*UnionValueMethodData
}
- // UnionValueMethodData describes a method used on a union value type.
- UnionValueMethodData struct {
- // Name is the name of the function.
- Name string
- // TypeRef is a reference on the target union value type.
- TypeRef string
- // Loc defines the file and Go package of the method if
- // overridden in corresponding union type via Meta.
- Loc *codegen.Location
- }
-
- // ErrorInitData describes an error returned by a service method of type
- // ErrorResult.
- ErrorInitData struct {
- // Name is the name of the init function.
- Name string
- // Description is the error description.
- Description string
- // ErrName is the name of the error.
- ErrName string
- // TypeName is the error struct type name.
- TypeName string
- // TypeRef is the reference to the error type.
- TypeRef string
- // Temporary indicates whether the error is temporary.
- Temporary bool
- // Timeout indicates whether the error is due to timeouts.
- Timeout bool
- // Fault indicates whether the error is server-side fault.
- Fault bool
- }
-
// MethodData describes a single service method.
MethodData struct {
// Name is the method name.
@@ -188,6 +156,12 @@ type (
// Schemes contains the security schemes types used by the
// method.
Schemes SchemesData
+ // ServerInterceptors list the server interceptors that apply to this
+ // method.
+ ServerInterceptors []*InterceptorData
+ // ClientInterceptors list the client interceptors that apply to this
+ // method.
+ ClientInterceptors []*InterceptorData
// ViewedResult contains the data required to generate the code handling
// views if any.
ViewedResult *ViewedResultTypeData
@@ -252,6 +226,81 @@ type (
Kind expr.StreamKind
}
+ // ErrorInitData describes an error returned by a service method of type
+ // ErrorResult.
+ ErrorInitData struct {
+ // Name is the name of the init function.
+ Name string
+ // Description is the error description.
+ Description string
+ // ErrName is the name of the error.
+ ErrName string
+ // TypeName is the error struct type name.
+ TypeName string
+ // TypeRef is the reference to the error type.
+ TypeRef string
+ // Temporary indicates whether the error is temporary.
+ Temporary bool
+ // Timeout indicates whether the error is due to timeouts.
+ Timeout bool
+ // Fault indicates whether the error is server-side fault.
+ Fault bool
+ }
+
+ // InterceptorData describes a single interceptor that can read and write
+ // payload and result attributes.
+ InterceptorData struct {
+ // Name is the name of the interceptor used in the generated code.
+ Name string
+ // DesignName is the name of the interceptor as defined in the design.
+ DesignName string
+ // UnexportedName is the private version of the interceptor name used in
+ // generated code.
+ UnexportedName string
+ // Description is the description of the interceptor from the design.
+ Description string
+ // PayloadRef is the reference to the payload type that the interceptor
+ // can access.
+ PayloadRef string
+ // ResultRef is the reference to the result type that the interceptor
+ // can access.
+ ResultRef string
+ // ReadPayload lists the payload attributes that the interceptor can
+ // read. These are defined using ReadPayload() in the design DSL.
+ ReadPayload []*AttributeData
+ // WritePayload lists the payload attributes that the interceptor can
+ // write. These are defined using WritePayload() in the design DSL.
+ WritePayload []*AttributeData
+ // ReadResult lists the result attributes that the interceptor can read.
+ // These are defined using ReadResult() in the design DSL.
+ ReadResult []*AttributeData
+ // WriteResult lists the result attributes that the interceptor can
+ // write. These are defined using WriteResult() in the design DSL.
+ WriteResult []*AttributeData
+ // ServerStreamInputStruct is the name of the server stream input struct
+ // type used when the interceptor is applied to a streaming endpoint.
+ ServerStreamInputStruct string
+ // ClientStreamInputStruct is the name of the client stream input struct
+ // type used when the interceptor is applied to a streaming endpoint.
+ ClientStreamInputStruct string
+ }
+
+ // AttributeData describes a single attribute.
+ AttributeData struct {
+ // Name is the name of the attribute.
+ Name string
+ // TypeRef is the reference to the attribute type.
+ TypeRef string
+ // FieldPointer is true if the attribute is a pointer.
+ FieldPointer bool
+ }
+
+ // RequirementsData is the list of security requirements.
+ RequirementsData []*RequirementData
+
+ // SchemesData is the list of security schemes.
+ SchemesData []*SchemeData
+
// RequirementData lists the schemes and scopes defined by a single
// security requirement.
RequirementData struct {
@@ -280,6 +329,17 @@ type (
Type expr.UserType
}
+ // UnionValueMethodData describes a method used on a union value type.
+ UnionValueMethodData struct {
+ // Name is the name of the function.
+ Name string
+ // TypeRef is a reference on the target union value type.
+ TypeRef string
+ // Loc defines the file and Go package of the method if
+ // overridden in corresponding union type via Meta.
+ Loc *codegen.Location
+ }
+
// SchemeData describes a single security scheme.
SchemeData struct {
// Kind is the type of scheme, one of "Basic", "APIKey", "JWT"
@@ -746,6 +806,17 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
}
}
+ var (
+ serverInterceptors []*InterceptorData
+ clientInterceptors []*InterceptorData
+ seenServer = make(map[string]struct{})
+ seenClient = make(map[string]struct{})
+ )
+ for _, m := range methods {
+ serverInterceptors = append(serverInterceptors, collectInterceptors(m.ServerInterceptors, seenServer)...)
+ clientInterceptors = append(clientInterceptors, collectInterceptors(m.ClientInterceptors, seenClient)...)
+ }
+
var (
desc string
)
@@ -769,6 +840,8 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
ViewsPkg: viewspkg,
Methods: methods,
Schemes: schemes,
+ ServerInterceptors: serverInterceptors,
+ ClientInterceptors: clientInterceptors,
Scope: scope,
ViewScope: viewScope,
errorTypes: errTypes,
@@ -876,6 +949,18 @@ func collectUnionMethods(att *expr.AttributeExpr, scope *codegen.NameScope, loc
return
}
+// collectInterceptors traverses the interceptors to build a unique set.
+func collectInterceptors(interceptors []*InterceptorData, seen map[string]struct{}) (data []*InterceptorData) {
+ for _, i := range interceptors {
+ if _, ok := seen[i.Name]; ok {
+ continue
+ }
+ seen[i.Name] = struct{}{}
+ data = append(data, i)
+ }
+ return
+}
+
// buildErrorInitData creates the data needed to generate code around endpoint error return values.
func buildErrorInitData(er *expr.ErrorExpr, scope *codegen.NameScope) *ErrorInitData {
_, temporary := er.AttributeExpr.Meta["goa:error:temporary"]
@@ -1001,14 +1086,16 @@ func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
RequestStruct: vname + "RequestData",
ResponseStruct: vname + "ResponseData",
}
- if m.IsStreaming() {
- initStreamData(data, m, vname, rname, resultRef, scope)
- }
+ initStreamData(data, m, vname, rname, resultRef, scope)
+ initInterceptorsData(data, m, payloadRef, resultRef, scope)
return data
}
// initStreamData initializes the streaming payload data structures and methods.
func initStreamData(data *MethodData, m *expr.MethodExpr, vname, rname, resultRef string, scope *codegen.NameScope) {
+ if !m.IsStreaming() {
+ return
+ }
var (
spayloadName string
spayloadRef string
@@ -1082,6 +1169,41 @@ func initStreamData(data *MethodData, m *expr.MethodExpr, vname, rname, resultRe
data.StreamingPayloadEx = spayloadEx
}
+func initInterceptorsData(data *MethodData, m *expr.MethodExpr, payloadRef, resultRef string, scope *codegen.NameScope) {
+ if len(m.ServerInterceptors) == 0 && len(m.ClientInterceptors) == 0 {
+ return
+ }
+ buildInterceptorData := func(i *expr.InterceptorExpr, serverStreamStruct, clientStreamStruct string, scope *codegen.NameScope) *InterceptorData {
+ return &InterceptorData{
+ Name: codegen.Goify(i.Name, true),
+ DesignName: i.Name,
+ UnexportedName: codegen.Goify(i.Name, false),
+ Description: i.Description,
+ PayloadRef: payloadRef,
+ ResultRef: resultRef,
+ ServerStreamInputStruct: serverStreamStruct,
+ ClientStreamInputStruct: clientStreamStruct,
+ ReadPayload: collectAttributes(i.ReadPayload, m.Payload, scope),
+ WritePayload: collectAttributes(i.WritePayload, m.Payload, scope),
+ ReadResult: collectAttributes(i.ReadResult, m.Result, scope),
+ WriteResult: collectAttributes(i.WriteResult, m.Result, scope),
+ }
+ }
+ var serverEndpointStruct, clientEndpointStruct string
+ if data.ServerStream != nil {
+ serverEndpointStruct = data.ServerStream.EndpointStruct
+ }
+ if data.ClientStream != nil {
+ clientEndpointStruct = data.ClientStream.EndpointStruct
+ }
+ for _, i := range m.ServerInterceptors {
+ data.ServerInterceptors = append(data.ServerInterceptors, buildInterceptorData(i, serverEndpointStruct, clientEndpointStruct, scope))
+ }
+ for _, i := range m.ClientInterceptors {
+ data.ClientInterceptors = append(data.ClientInterceptors, buildInterceptorData(i, serverEndpointStruct, clientEndpointStruct, scope))
+ }
+}
+
// BuildSchemeData builds the scheme data for the given scheme and method expr.
func BuildSchemeData(s *expr.SchemeExpr, m *expr.MethodExpr) *SchemeData {
if !expr.IsObject(m.Payload.Type) {
@@ -1184,6 +1306,30 @@ func BuildSchemeData(s *expr.SchemeExpr, m *expr.MethodExpr) *SchemeData {
return nil
}
+// collectAttributes builds AttributeData from an AttributeExpr
+func collectAttributes(attrNames, parent *expr.AttributeExpr, scope *codegen.NameScope) []*AttributeData {
+ if attrNames == nil {
+ return nil
+ }
+ obj := expr.AsObject(attrNames.Type)
+ if obj == nil {
+ return nil
+ }
+ data := make([]*AttributeData, len(*obj))
+ for i, nat := range *obj {
+ parentAttr := parent.Find(nat.Name)
+ if parentAttr == nil {
+ continue
+ }
+ data[i] = &AttributeData{
+ Name: codegen.Goify(nat.Name, true),
+ TypeRef: scope.GoTypeRef(parentAttr),
+ FieldPointer: parent.IsPrimitivePointer(nat.Name, true),
+ }
+ }
+ return data
+}
+
// collectProjectedTypes builds a projected type for every user type found
// when recursing through the attributes. The projected types live in the views
// package and support the marshaling and unmarshalling of result types that
diff --git a/codegen/service/templates/client_interceptor_wrappers.go.tpl b/codegen/service/templates/client_interceptor_wrappers.go.tpl
new file mode 100644
index 0000000000..f2f63223db
--- /dev/null
+++ b/codegen/service/templates/client_interceptor_wrappers.go.tpl
@@ -0,0 +1,18 @@
+{{- range .ClientInterceptors }}
+{{ comment (printf "wrapClient%s applies the %s interceptor to endpoints." .Name .DesignName) }}
+func wrapClient{{ .Name }}(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &{{ .Name }}Info{
+ Service: "{{ $.Service }}",
+ Method: method,
+ Endpoint: endpoint,
+ {{- if .ClientStreamInputStruct }}
+ RawPayload: req.(*{{ .ClientStreamInputStruct }}).Payload,
+ {{- else }}
+ RawPayload: req,
+ {{- end }}
+ }
+ return i.{{ .Name }}(ctx, info, endpoint)
+ }
+}
+{{- end }}
diff --git a/codegen/service/templates/client_interceptors.go.tpl b/codegen/service/templates/client_interceptors.go.tpl
new file mode 100644
index 0000000000..9528e3d123
--- /dev/null
+++ b/codegen/service/templates/client_interceptors.go.tpl
@@ -0,0 +1,12 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+{{- range .ClientInterceptors }}
+{{- if .Description }}
+ {{ comment .Description }}
+{{- end }}
+ {{ .Name }}(ctx context.Context, info *{{ .Name }}Info, next goa.Endpoint) (any, error)
+{{- end }}
+}
diff --git a/codegen/service/templates/client_wrappers.go.tpl b/codegen/service/templates/client_wrappers.go.tpl
index dd52071dd9..6b05597c00 100644
--- a/codegen/service/templates/client_wrappers.go.tpl
+++ b/codegen/service/templates/client_wrappers.go.tpl
@@ -5,25 +5,3 @@ func Wrap{{ .MethodVarName }}ClientEndpoint(endpoint goa.Endpoint, i ClientInter
{{- end }}
return endpoint
}
-
-{{- range .ClientInterceptors }}
-{{ comment (printf "wrapClient%s applies the %s interceptor to endpoints." .Name .DesignName) }}
-func wrapClient{{ .Name }}(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &{{ .Name }}Info{
- Service: "{{ $.Service }}",
- Method: method,
- Endpoint: endpoint,
- {{- if .ClientStreamInputStruct }}
- RawPayload: req.(*{{ .ClientStreamInputStruct }}).Payload,
- {{- else }}
- RawPayload: req,
- {{- end }}
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.{{ .Name }}(ctx, info, next)
- }
-}
-{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/endpoint_wrappers.go.tpl b/codegen/service/templates/endpoint_wrappers.go.tpl
index 63dcf39e1e..8c82741cc9 100644
--- a/codegen/service/templates/endpoint_wrappers.go.tpl
+++ b/codegen/service/templates/endpoint_wrappers.go.tpl
@@ -5,26 +5,3 @@ func Wrap{{ .MethodVarName }}Endpoint(endpoint goa.Endpoint, i ServerInterceptor
{{- end }}
return endpoint
}
-
-{{- range .ServerInterceptors }}
-{{ comment (printf "wrap%s applies the %s interceptor to endpoints." .Name .DesignName) }}
-func wrap{{ .Name }}(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &{{ .Name }}Info{
- Service: "{{ $.Service }}",
- Method: method,
- Endpoint: endpoint,
- {{- if .ServerStreamInputStruct }}
- RawPayload: req.(*{{ .ServerStreamInputStruct }}).Payload,
- {{- else }}
- RawPayload: req,
- {{- end }}
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.{{ .Name }}(ctx, info, next)
- }
-}
-
-{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/example_client_interceptor.go.tpl b/codegen/service/templates/example_client_interceptor.go.tpl
new file mode 100644
index 0000000000..455c74c9f4
--- /dev/null
+++ b/codegen/service/templates/example_client_interceptor.go.tpl
@@ -0,0 +1,24 @@
+// {{ .StructName }}ClientInterceptors implements the client interceptors for the {{ .ServiceName }} service.
+type {{ .StructName }}ClientInterceptors struct {
+}
+
+// New{{ .StructName }}ClientInterceptors creates a new client interceptor for the {{ .ServiceName }} service.
+func New{{ .StructName }}ClientInterceptors() *{{ .StructName }}ClientInterceptors {
+ return &{{ .StructName }}ClientInterceptors{}
+}
+
+{{- range .ClientInterceptors }}
+{{- if .Description }}
+{{ comment .Description }}
+{{- end }}
+func (i *{{ $.StructName }}ClientInterceptors) {{ .Name }}(ctx context.Context, info *{{ $.PkgName }}.{{ .Name }}Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[{{ .Name }}] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[{{ .Name }}] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[{{ .Name }}] Received response: %v", resp)
+ return resp, nil
+}
+{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/security_authfuncs.go.tpl b/codegen/service/templates/example_security_authfuncs.go.tpl
similarity index 100%
rename from codegen/service/templates/security_authfuncs.go.tpl
rename to codegen/service/templates/example_security_authfuncs.go.tpl
diff --git a/codegen/service/templates/example_server_interceptor.go.tpl b/codegen/service/templates/example_server_interceptor.go.tpl
new file mode 100644
index 0000000000..dfbb7d93bb
--- /dev/null
+++ b/codegen/service/templates/example_server_interceptor.go.tpl
@@ -0,0 +1,24 @@
+// {{ .StructName }}ServerInterceptors implements the server interceptor for the {{ .ServiceName }} service.
+type {{ .StructName }}ServerInterceptors struct {
+}
+
+// New{{ .StructName }}ServerInterceptors creates a new server interceptor for the {{ .ServiceName }} service.
+func New{{ .StructName }}ServerInterceptors() *{{ .StructName }}ServerInterceptors {
+ return &{{ .StructName }}ServerInterceptors{}
+}
+
+{{- range .ServerInterceptors }}
+{{- if .Description }}
+{{ comment .Description }}
+{{- end }}
+func (i *{{ $.StructName }}ServerInterceptors) {{ .Name }}(ctx context.Context, info *{{ $.PkgName }}.{{ .Name }}Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[{{ .Name }}] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[{{ .Name }}] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[{{ .Name }}] Response: %v", resp)
+ return resp, nil
+}
+{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/service_init.go.tpl b/codegen/service/templates/example_service_init.go.tpl
similarity index 100%
rename from codegen/service/templates/service_init.go.tpl
rename to codegen/service/templates/example_service_init.go.tpl
diff --git a/codegen/service/templates/service_struct.go.tpl b/codegen/service/templates/example_service_struct.go.tpl
similarity index 100%
rename from codegen/service/templates/service_struct.go.tpl
rename to codegen/service/templates/example_service_struct.go.tpl
diff --git a/codegen/service/templates/interceptors.go.tpl b/codegen/service/templates/interceptors.go.tpl
index 46dfceedb0..5f2024cf33 100644
--- a/codegen/service/templates/interceptors.go.tpl
+++ b/codegen/service/templates/interceptors.go.tpl
@@ -1,32 +1,7 @@
-{{- if .ServerInterceptors -}}
-// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
-type ServerInterceptors interface {
-{{- range .ServerInterceptors }}
- {{ comment .Description }}
- {{ .Name }}(context.Context, *{{ .Name }}Info, goa.NextFunc) (any, error)
-{{- end }}
-}
-{{- end }}
-
-{{- if .ClientInterceptors -}}
-// ClientInterceptors defines the interface for all client-side interceptors.
-// Client interceptors execute after the payload is encoded and before the request
-// is sent to the server (request interceptors) or after the response is decoded
-// and before the result is returned to the client (response interceptors).
-type ClientInterceptors interface {
-{{- range .ClientInterceptors }}
- {{ comment .Description }}
- {{ .Name }}(context.Context, *{{ .Name }}Info, goa.NextFunc) (any, error)
-{{- end }}
-}
-{{- end }}
// Access interfaces for interceptor payloads and results
type (
-{{- range .AllInterceptors }}
+{{- range . }}
// {{ .Name }}Info provides metadata about the current interception.
// It includes service name, method name, and access to the endpoint.
{{ .Name }}Info goa.InterceptorInfo
@@ -61,11 +36,11 @@ type (
{{- end }}
)
-{{- if .HasPrivateImplementationTypes }}
+{{- if hasPrivateImplementationTypes . }}
// Private implementation types
type (
- {{- range .AllInterceptors }}
+ {{- range . }}
{{- if or .ReadPayload .WritePayload }}
{{ .UnexportedName }}PayloadAccess struct {
payload {{ .PayloadRef }}
@@ -81,7 +56,7 @@ type (
)
// Public accessor methods for Info types
-{{- range .AllInterceptors }}
+{{- range . }}
{{- if or .ReadPayload .WritePayload }}
// Payload returns a type-safe accessor for the method payload.
func (info *{{ .Name }}Info) Payload() {{ .Name }}PayloadAccess {
@@ -98,7 +73,7 @@ func (info *{{ .Name }}Info) Result(res any) {{ .Name }}ResultAccess {
{{- end }}
// Private implementation methods
-{{- range .AllInterceptors }}
+{{- range . }}
{{- $interceptor := . }}
{{- range .ReadPayload }}
func (p *{{ $interceptor.UnexportedName }}PayloadAccess) {{ .Name }}() {{ .TypeRef }} {
diff --git a/codegen/service/templates/server_interceptor_wrappers.go.tpl b/codegen/service/templates/server_interceptor_wrappers.go.tpl
new file mode 100644
index 0000000000..4971d6510e
--- /dev/null
+++ b/codegen/service/templates/server_interceptor_wrappers.go.tpl
@@ -0,0 +1,18 @@
+{{- range .ServerInterceptors }}
+{{ comment (printf "wrap%s applies the %s interceptor to endpoints." .Name .DesignName) }}
+func wrap{{ .Name }}(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &{{ .Name }}Info{
+ Service: "{{ $.Service }}",
+ Method: method,
+ Endpoint: endpoint,
+ {{- if .ServerStreamInputStruct }}
+ RawPayload: req.(*{{ .ServerStreamInputStruct }}).Payload,
+ {{- else }}
+ RawPayload: req,
+ {{- end }}
+ }
+ return i.{{ .Name }}(ctx, info, endpoint)
+ }
+}
+{{- end }}
diff --git a/codegen/service/templates/server_interceptors.go.tpl b/codegen/service/templates/server_interceptors.go.tpl
new file mode 100644
index 0000000000..c19597a383
--- /dev/null
+++ b/codegen/service/templates/server_interceptors.go.tpl
@@ -0,0 +1,12 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
+type ServerInterceptors interface {
+{{- range .ServerInterceptors }}
+{{- if .Description }}
+ {{ comment .Description }}
+{{- end }}
+ {{ .Name }}(ctx context.Context, info *{{ .Name }}Info, next goa.Endpoint) (any, error)
+{{- end }}
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/client_code.go b/codegen/service/testdata/client_code.go
index 8153742480..a13cacde3a 100644
--- a/codegen/service/testdata/client_code.go
+++ b/codegen/service/testdata/client_code.go
@@ -307,26 +307,4 @@ func (c *Client) Method(ctx context.Context, p string) (res string, err error) {
}
return ires.(string), nil
}
-
-// WrapMethodClientEndpoint wraps the Method endpoint with the client
-// interceptors defined in the design.
-func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientTracing(endpoint, i, "Method")
- return endpoint
-}
-
-// wrapClientTracing applies the tracing interceptor to endpoints.
-func wrapClientTracing(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &TracingInfo{
- Service: "ServiceWithClientInterceptor",
- Method: method,
- Endpoint: endpoint,
- RawPayload: req,
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.Tracing(ctx, info, next)
- }
-}`
+`
diff --git a/codegen/service/testdata/endpoint_code.go b/codegen/service/testdata/endpoint_code.go
index 98db14f0d1..324d104ae8 100644
--- a/codegen/service/testdata/endpoint_code.go
+++ b/codegen/service/testdata/endpoint_code.go
@@ -543,29 +543,7 @@ func NewMethodEndpoint(s Service) goa.Endpoint {
return s.Method(ctx, p)
}
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
- return endpoint
-}
-
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &LoggingInfo{
- Service: "ServiceWithServerInterceptor",
- Method: method,
- Endpoint: endpoint,
- RawPayload: req,
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.Logging(ctx, info, next)
- }
-}`
+`
var EndpointWithMultipleInterceptors = `// Endpoints wraps the "ServiceWithMultipleInterceptors" service endpoints.
type Endpoints struct {
@@ -596,46 +574,7 @@ func NewMethodEndpoint(s Service) goa.Endpoint {
return s.Method(ctx, p)
}
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
- endpoint = wrapMetrics(endpoint, i, "Method")
- return endpoint
-}
-
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &LoggingInfo{
- Service: "ServiceWithMultipleInterceptors",
- Method: method,
- Endpoint: endpoint,
- RawPayload: req,
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.Logging(ctx, info, next)
- }
-}
-
-// wrapMetrics applies the metrics interceptor to endpoints.
-func wrapMetrics(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &MetricsInfo{
- Service: "ServiceWithMultipleInterceptors",
- Method: method,
- Endpoint: endpoint,
- RawPayload: req,
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.Metrics(ctx, info, next)
- }
-}`
+`
var EndpointStreamingWithInterceptor = `// Endpoints wraps the "ServiceStreamingWithInterceptor" service endpoints.
type Endpoints struct {
@@ -671,26 +610,4 @@ func NewMethodEndpoint(s Service) goa.Endpoint {
return nil, s.Method(ctx, ep.Stream)
}
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
- return endpoint
-}
-
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
- return func(ctx context.Context, req any) (any, error) {
- info := &LoggingInfo{
- Service: "ServiceStreamingWithInterceptor",
- Method: method,
- Endpoint: endpoint,
- RawPayload: req.(*MethodEndpointInput).Payload,
- }
- next := func(ctx context.Context) (any, error) {
- return endpoint(ctx, req)
- }
- return i.Logging(ctx, info, next)
- }
-}`
+`
diff --git a/codegen/service/testdata/example_interceptors/api_interceptor_service_client.golden b/codegen/service/testdata/example_interceptors/api_interceptor_service_client.golden
new file mode 100644
index 0000000000..5188cca366
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/api_interceptor_service_client.golden
@@ -0,0 +1,35 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// APIInterceptorService example client interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ apiinterceptorservice "api_interceptor_service"
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+)
+
+// APIInterceptorServiceClientInterceptors implements the client interceptors for the APIInterceptorService service.
+type APIInterceptorServiceClientInterceptors struct {
+}
+
+// NewAPIInterceptorServiceClientInterceptors creates a new client interceptor for the APIInterceptorService service.
+func NewAPIInterceptorServiceClientInterceptors() *APIInterceptorServiceClientInterceptors {
+ return &APIInterceptorServiceClientInterceptors{}
+}
+func (i *APIInterceptorServiceClientInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[API] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[API] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[API] Received response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/api_interceptor_service_server.golden b/codegen/service/testdata/example_interceptors/api_interceptor_service_server.golden
new file mode 100644
index 0000000000..ff86a4cee7
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/api_interceptor_service_server.golden
@@ -0,0 +1,35 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// APIInterceptorService example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ apiinterceptorservice "api_interceptor_service"
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+)
+
+// APIInterceptorServiceServerInterceptors implements the server interceptor for the APIInterceptorService service.
+type APIInterceptorServiceServerInterceptors struct {
+}
+
+// NewAPIInterceptorServiceServerInterceptors creates a new server interceptor for the APIInterceptorService service.
+func NewAPIInterceptorServiceServerInterceptors() *APIInterceptorServiceServerInterceptors {
+ return &APIInterceptorServiceServerInterceptors{}
+}
+func (i *APIInterceptorServiceServerInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[API] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[API] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[API] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden b/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden
new file mode 100644
index 0000000000..bb1b3811fb
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden
@@ -0,0 +1,55 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// ChainedInterceptorService example client interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ chainedinterceptorservice "chained_interceptor_service"
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+)
+
+// ChainedInterceptorServiceClientInterceptors implements the client interceptors for the ChainedInterceptorService service.
+type ChainedInterceptorServiceClientInterceptors struct {
+}
+
+// NewChainedInterceptorServiceClientInterceptors creates a new client interceptor for the ChainedInterceptorService service.
+func NewChainedInterceptorServiceClientInterceptors() *ChainedInterceptorServiceClientInterceptors {
+ return &ChainedInterceptorServiceClientInterceptors{}
+}
+func (i *ChainedInterceptorServiceClientInterceptors) Method(ctx context.Context, info *interceptors.MethodInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Method] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Method] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Method] Received response: %v", resp)
+ return resp, nil
+}
+func (i *ChainedInterceptorServiceClientInterceptors) Service(ctx context.Context, info *interceptors.ServiceInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Service] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Service] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Service] Received response: %v", resp)
+ return resp, nil
+}
+func (i *ChainedInterceptorServiceClientInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[API] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[API] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[API] Received response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden b/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden
new file mode 100644
index 0000000000..b6cb4245f7
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden
@@ -0,0 +1,55 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// ChainedInterceptorService example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ chainedinterceptorservice "chained_interceptor_service"
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+)
+
+// ChainedInterceptorServiceServerInterceptors implements the server interceptor for the ChainedInterceptorService service.
+type ChainedInterceptorServiceServerInterceptors struct {
+}
+
+// NewChainedInterceptorServiceServerInterceptors creates a new server interceptor for the ChainedInterceptorService service.
+func NewChainedInterceptorServiceServerInterceptors() *ChainedInterceptorServiceServerInterceptors {
+ return &ChainedInterceptorServiceServerInterceptors{}
+}
+func (i *ChainedInterceptorServiceServerInterceptors) Method(ctx context.Context, info *interceptors.MethodInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Method] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Method] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Method] Response: %v", resp)
+ return resp, nil
+}
+func (i *ChainedInterceptorServiceServerInterceptors) Service(ctx context.Context, info *interceptors.ServiceInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Service] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Service] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Service] Response: %v", resp)
+ return resp, nil
+}
+func (i *ChainedInterceptorServiceServerInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[API] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[API] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[API] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/client_interceptor_service_client.golden b/codegen/service/testdata/example_interceptors/client_interceptor_service_client.golden
new file mode 100644
index 0000000000..f64ea4bd10
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/client_interceptor_service_client.golden
@@ -0,0 +1,35 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// ClientInterceptorService example client interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ clientinterceptorservice "client_interceptor_service"
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+)
+
+// ClientInterceptorServiceClientInterceptors implements the client interceptors for the ClientInterceptorService service.
+type ClientInterceptorServiceClientInterceptors struct {
+}
+
+// NewClientInterceptorServiceClientInterceptors creates a new client interceptor for the ClientInterceptorService service.
+func NewClientInterceptorServiceClientInterceptors() *ClientInterceptorServiceClientInterceptors {
+ return &ClientInterceptorServiceClientInterceptors{}
+}
+func (i *ClientInterceptorServiceClientInterceptors) Test(ctx context.Context, info *interceptors.TestInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test] Received response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/multiple_interceptors_service_client.golden b/codegen/service/testdata/example_interceptors/multiple_interceptors_service_client.golden
new file mode 100644
index 0000000000..3086ebce99
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/multiple_interceptors_service_client.golden
@@ -0,0 +1,45 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// MultipleInterceptorsService example client interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ multipleinterceptorsservice "multiple_interceptors_service"
+)
+
+// MultipleInterceptorsServiceClientInterceptors implements the client interceptors for the MultipleInterceptorsService service.
+type MultipleInterceptorsServiceClientInterceptors struct {
+}
+
+// NewMultipleInterceptorsServiceClientInterceptors creates a new client interceptor for the MultipleInterceptorsService service.
+func NewMultipleInterceptorsServiceClientInterceptors() *MultipleInterceptorsServiceClientInterceptors {
+ return &MultipleInterceptorsServiceClientInterceptors{}
+}
+func (i *MultipleInterceptorsServiceClientInterceptors) Test2(ctx context.Context, info *interceptors.Test2Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test2] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test2] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test2] Received response: %v", resp)
+ return resp, nil
+}
+func (i *MultipleInterceptorsServiceClientInterceptors) Test4(ctx context.Context, info *interceptors.Test4Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test4] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test4] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test4] Received response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/multiple_interceptors_service_server.golden b/codegen/service/testdata/example_interceptors/multiple_interceptors_service_server.golden
new file mode 100644
index 0000000000..75f774ef53
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/multiple_interceptors_service_server.golden
@@ -0,0 +1,45 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// MultipleInterceptorsService example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ multipleinterceptorsservice "multiple_interceptors_service"
+)
+
+// MultipleInterceptorsServiceServerInterceptors implements the server interceptor for the MultipleInterceptorsService service.
+type MultipleInterceptorsServiceServerInterceptors struct {
+}
+
+// NewMultipleInterceptorsServiceServerInterceptors creates a new server interceptor for the MultipleInterceptorsService service.
+func NewMultipleInterceptorsServiceServerInterceptors() *MultipleInterceptorsServiceServerInterceptors {
+ return &MultipleInterceptorsServiceServerInterceptors{}
+}
+func (i *MultipleInterceptorsServiceServerInterceptors) Test(ctx context.Context, info *interceptors.TestInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test] Response: %v", resp)
+ return resp, nil
+}
+func (i *MultipleInterceptorsServiceServerInterceptors) Test3(ctx context.Context, info *interceptors.Test3Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test3] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test3] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test3] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_client.golden b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_client.golden
new file mode 100644
index 0000000000..ff9d84033d
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_client.golden
@@ -0,0 +1,45 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// MultipleServicesInterceptorsService2 example client interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ multipleservicesinterceptorsservice2 "multiple_services_interceptors_service2"
+)
+
+// MultipleServicesInterceptorsService2ClientInterceptors implements the client interceptors for the MultipleServicesInterceptorsService2 service.
+type MultipleServicesInterceptorsService2ClientInterceptors struct {
+}
+
+// NewMultipleServicesInterceptorsService2ClientInterceptors creates a new client interceptor for the MultipleServicesInterceptorsService2 service.
+func NewMultipleServicesInterceptorsService2ClientInterceptors() *MultipleServicesInterceptorsService2ClientInterceptors {
+ return &MultipleServicesInterceptorsService2ClientInterceptors{}
+}
+func (i *MultipleServicesInterceptorsService2ClientInterceptors) Test2(ctx context.Context, info *interceptors.Test2Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test2] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test2] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test2] Received response: %v", resp)
+ return resp, nil
+}
+func (i *MultipleServicesInterceptorsService2ClientInterceptors) Test4(ctx context.Context, info *interceptors.Test4Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test4] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test4] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test4] Received response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_server.golden b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_server.golden
new file mode 100644
index 0000000000..173144f090
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service2_server.golden
@@ -0,0 +1,45 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// MultipleServicesInterceptorsService2 example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ multipleservicesinterceptorsservice2 "multiple_services_interceptors_service2"
+)
+
+// MultipleServicesInterceptorsService2ServerInterceptors implements the server interceptor for the MultipleServicesInterceptorsService2 service.
+type MultipleServicesInterceptorsService2ServerInterceptors struct {
+}
+
+// NewMultipleServicesInterceptorsService2ServerInterceptors creates a new server interceptor for the MultipleServicesInterceptorsService2 service.
+func NewMultipleServicesInterceptorsService2ServerInterceptors() *MultipleServicesInterceptorsService2ServerInterceptors {
+ return &MultipleServicesInterceptorsService2ServerInterceptors{}
+}
+func (i *MultipleServicesInterceptorsService2ServerInterceptors) Test(ctx context.Context, info *interceptors.TestInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test] Response: %v", resp)
+ return resp, nil
+}
+func (i *MultipleServicesInterceptorsService2ServerInterceptors) Test3(ctx context.Context, info *interceptors.Test3Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test3] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test3] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test3] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_client.golden b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_client.golden
new file mode 100644
index 0000000000..72c48c6bc8
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_client.golden
@@ -0,0 +1,45 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// MultipleServicesInterceptorsService example client interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ multipleservicesinterceptorsservice "multiple_services_interceptors_service"
+)
+
+// MultipleServicesInterceptorsServiceClientInterceptors implements the client interceptors for the MultipleServicesInterceptorsService service.
+type MultipleServicesInterceptorsServiceClientInterceptors struct {
+}
+
+// NewMultipleServicesInterceptorsServiceClientInterceptors creates a new client interceptor for the MultipleServicesInterceptorsService service.
+func NewMultipleServicesInterceptorsServiceClientInterceptors() *MultipleServicesInterceptorsServiceClientInterceptors {
+ return &MultipleServicesInterceptorsServiceClientInterceptors{}
+}
+func (i *MultipleServicesInterceptorsServiceClientInterceptors) Test2(ctx context.Context, info *interceptors.Test2Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test2] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test2] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test2] Received response: %v", resp)
+ return resp, nil
+}
+func (i *MultipleServicesInterceptorsServiceClientInterceptors) Test4(ctx context.Context, info *interceptors.Test4Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test4] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test4] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test4] Received response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_server.golden b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_server.golden
new file mode 100644
index 0000000000..4e780407ea
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/multiple_services_interceptors_service_server.golden
@@ -0,0 +1,45 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// MultipleServicesInterceptorsService example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ multipleservicesinterceptorsservice "multiple_services_interceptors_service"
+)
+
+// MultipleServicesInterceptorsServiceServerInterceptors implements the server interceptor for the MultipleServicesInterceptorsService service.
+type MultipleServicesInterceptorsServiceServerInterceptors struct {
+}
+
+// NewMultipleServicesInterceptorsServiceServerInterceptors creates a new server interceptor for the MultipleServicesInterceptorsService service.
+func NewMultipleServicesInterceptorsServiceServerInterceptors() *MultipleServicesInterceptorsServiceServerInterceptors {
+ return &MultipleServicesInterceptorsServiceServerInterceptors{}
+}
+func (i *MultipleServicesInterceptorsServiceServerInterceptors) Test(ctx context.Context, info *interceptors.TestInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test] Response: %v", resp)
+ return resp, nil
+}
+func (i *MultipleServicesInterceptorsServiceServerInterceptors) Test3(ctx context.Context, info *interceptors.Test3Info, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test3] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test3] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test3] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/server_interceptor_by_name_service_server.golden b/codegen/service/testdata/example_interceptors/server_interceptor_by_name_service_server.golden
new file mode 100644
index 0000000000..2660f90abe
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/server_interceptor_by_name_service_server.golden
@@ -0,0 +1,35 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// ServerInterceptorByNameService example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ serverinterceptorbynameservice "server_interceptor_by_name_service"
+)
+
+// ServerInterceptorByNameServiceServerInterceptors implements the server interceptor for the ServerInterceptorByNameService service.
+type ServerInterceptorByNameServiceServerInterceptors struct {
+}
+
+// NewServerInterceptorByNameServiceServerInterceptors creates a new server interceptor for the ServerInterceptorByNameService service.
+func NewServerInterceptorByNameServiceServerInterceptors() *ServerInterceptorByNameServiceServerInterceptors {
+ return &ServerInterceptorByNameServiceServerInterceptors{}
+}
+func (i *ServerInterceptorByNameServiceServerInterceptors) Test(ctx context.Context, info *interceptors.TestInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors/server_interceptor_service_server.golden b/codegen/service/testdata/example_interceptors/server_interceptor_service_server.golden
new file mode 100644
index 0000000000..517fdbc27e
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors/server_interceptor_service_server.golden
@@ -0,0 +1,35 @@
+// Code generated by goa v3.19.1, DO NOT EDIT.
+//
+// ServerInterceptorService example server interceptors
+//
+// Command:
+// goa
+
+package interceptors
+
+import (
+ "context"
+ "fmt"
+ "goa.design/clue/log"
+ goa "goa.design/goa/v3/pkg"
+ serverinterceptorservice "server_interceptor_service"
+)
+
+// ServerInterceptorServiceServerInterceptors implements the server interceptor for the ServerInterceptorService service.
+type ServerInterceptorServiceServerInterceptors struct {
+}
+
+// NewServerInterceptorServiceServerInterceptors creates a new server interceptor for the ServerInterceptorService service.
+func NewServerInterceptorServiceServerInterceptors() *ServerInterceptorServiceServerInterceptors {
+ return &ServerInterceptorServiceServerInterceptors{}
+}
+func (i *ServerInterceptorServiceServerInterceptors) Test(ctx context.Context, info *interceptors.TestInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[Test] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[Test] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[Test] Response: %v", resp)
+ return resp, nil
+}
diff --git a/codegen/service/testdata/example_interceptors_dsls.go b/codegen/service/testdata/example_interceptors_dsls.go
new file mode 100644
index 0000000000..42ecbb21ef
--- /dev/null
+++ b/codegen/service/testdata/example_interceptors_dsls.go
@@ -0,0 +1,96 @@
+package testdata
+
+import (
+ . "goa.design/goa/v3/dsl"
+)
+
+var NoInterceptorExampleDSL = func() {
+ var _ = Service("NoInterceptorService", func() {
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var ServerInterceptorExampleDSL = func() {
+ var SInterceptor = Interceptor("test")
+ var _ = Service("ServerInterceptorService", func() {
+ ServerInterceptor(SInterceptor)
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var ClientInterceptorExampleDSL = func() {
+ var CInterceptor = Interceptor("test")
+ var _ = Service("ClientInterceptorService", func() {
+ ClientInterceptor(CInterceptor)
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var ServerInterceptorByNameExampleDSL = func() {
+ var _ = Interceptor("test")
+ var _ = Service("ServerInterceptorByNameService", func() {
+ ServerInterceptor("test")
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var MultipleInterceptorsExampleDSL = func() {
+ var _ = Interceptor("test")
+ var _ = Interceptor("test2")
+ var SInterceptor = Interceptor("test3")
+ var CInterceptor = Interceptor("test4")
+ var _ = Service("MultipleInterceptorsService", func() {
+ ServerInterceptor("test", SInterceptor)
+ ClientInterceptor("test2", CInterceptor)
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var MultipleServicesInterceptorsExampleDSL = func() {
+ var _ = Interceptor("test")
+ var _ = Interceptor("test2")
+ var SInterceptor = Interceptor("test3")
+ var CInterceptor = Interceptor("test4")
+ var _ = Service("MultipleServicesInterceptorsService", func() {
+ ServerInterceptor("test", SInterceptor)
+ ClientInterceptor("test2", CInterceptor)
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+ var _ = Service("MultipleServicesInterceptorsService2", func() {
+ ServerInterceptor("test", SInterceptor)
+ ClientInterceptor("test2", CInterceptor)
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var APIInterceptorExampleDSL = func() {
+ var APIInterceptor = Interceptor("api")
+ var _ = API("test", func() {
+ ServerInterceptor(APIInterceptor)
+ ClientInterceptor(APIInterceptor)
+ })
+ var _ = Service("APIInterceptorService", func() {
+ Method("Method", func() { HTTP(func() { GET("/") }) })
+ })
+}
+
+var ChainedInterceptorExampleDSL = func() {
+ var APIInterceptor = Interceptor("api")
+ var ServiceInterceptor = Interceptor("service")
+ var MethodInterceptor = Interceptor("method")
+
+ var _ = API("test", func() {
+ ServerInterceptor(APIInterceptor)
+ ClientInterceptor(APIInterceptor)
+ })
+
+ var _ = Service("ChainedInterceptorService", func() {
+ ServerInterceptor(ServiceInterceptor)
+ ClientInterceptor(ServiceInterceptor)
+ Method("Method", func() {
+ ServerInterceptor(MethodInterceptor)
+ ClientInterceptor(MethodInterceptor)
+ HTTP(func() { GET("/") })
+ })
+ })
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden
new file mode 100644
index 0000000000..f1a4d2805c
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden
@@ -0,0 +1,14 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error)
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientValidation(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..ffbd9bf910
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden
@@ -0,0 +1,26 @@
+
+// wrapValidation applies the validation interceptor to endpoints.
+func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &ValidationInfo{
+ Service: "InterceptorWithReadPayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Validation(ctx, info, endpoint)
+ }
+}
+
+// wrapClientValidation applies the validation interceptor to endpoints.
+func wrapClientValidation(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &ValidationInfo{
+ Service: "InterceptorWithReadPayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Validation(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden
similarity index 70%
rename from codegen/service/testdata/interceptors/interceptor-with-read-payload.golden
rename to codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden
index eb5152b895..e3edffbad9 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-payload.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Validation(context.Context, *ValidationInfo, goa.NextFunc) (any, error)
+ Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -38,4 +38,9 @@ func (p *validationPayloadAccess) Name() string {
return p.payload.Name
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapValidation(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden
new file mode 100644
index 0000000000..76648aff03
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden
@@ -0,0 +1,14 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error)
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientCaching(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..c4ad755b66
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden
@@ -0,0 +1,26 @@
+
+// wrapCaching applies the caching interceptor to endpoints.
+func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &CachingInfo{
+ Service: "InterceptorWithReadResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Caching(ctx, info, endpoint)
+ }
+}
+
+// wrapClientCaching applies the caching interceptor to endpoints.
+func wrapClientCaching(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &CachingInfo{
+ Service: "InterceptorWithReadResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Caching(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden
similarity index 69%
rename from codegen/service/testdata/interceptors/interceptor-with-read-result.golden
rename to codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden
index 254d1ddaf0..de70b2f1b3 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-result.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Caching(context.Context, *CachingInfo, goa.NextFunc) (any, error)
+ Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -38,4 +38,9 @@ func (r *cachingResultAccess) Data() string {
return r.result.Data
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapCaching(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden
new file mode 100644
index 0000000000..f1a4d2805c
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden
@@ -0,0 +1,14 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error)
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientValidation(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..c9947a357a
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden
@@ -0,0 +1,26 @@
+
+// wrapValidation applies the validation interceptor to endpoints.
+func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &ValidationInfo{
+ Service: "InterceptorWithReadWritePayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Validation(ctx, info, endpoint)
+ }
+}
+
+// wrapClientValidation applies the validation interceptor to endpoints.
+func wrapClientValidation(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &ValidationInfo{
+ Service: "InterceptorWithReadWritePayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Validation(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden
similarity index 71%
rename from codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden
rename to codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden
index 07d0f938cb..f4242b3d97 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Validation(context.Context, *ValidationInfo, goa.NextFunc) (any, error)
+ Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -42,4 +42,9 @@ func (p *validationPayloadAccess) SetName(v string) {
p.payload.Name = v
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapValidation(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden
new file mode 100644
index 0000000000..76648aff03
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden
@@ -0,0 +1,14 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error)
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientCaching(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..65bbf27b56
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden
@@ -0,0 +1,26 @@
+
+// wrapCaching applies the caching interceptor to endpoints.
+func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &CachingInfo{
+ Service: "InterceptorWithReadWriteResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Caching(ctx, info, endpoint)
+ }
+}
+
+// wrapClientCaching applies the caching interceptor to endpoints.
+func wrapClientCaching(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &CachingInfo{
+ Service: "InterceptorWithReadWriteResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Caching(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden
similarity index 71%
rename from codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden
rename to codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden
index 202260177d..ef8f84f67f 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-result.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Caching(context.Context, *CachingInfo, goa.NextFunc) (any, error)
+ Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -42,4 +42,9 @@ func (r *cachingResultAccess) SetData(v string) {
r.result.Data = v
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapCaching(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden
new file mode 100644
index 0000000000..f1a4d2805c
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden
@@ -0,0 +1,14 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error)
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientValidation(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..34e9a283bd
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden
@@ -0,0 +1,26 @@
+
+// wrapValidation applies the validation interceptor to endpoints.
+func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &ValidationInfo{
+ Service: "InterceptorWithWritePayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Validation(ctx, info, endpoint)
+ }
+}
+
+// wrapClientValidation applies the validation interceptor to endpoints.
+func wrapClientValidation(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &ValidationInfo{
+ Service: "InterceptorWithWritePayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Validation(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden
similarity index 70%
rename from codegen/service/testdata/interceptors/interceptor-with-write-payload.golden
rename to codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden
index 7f0eabeb04..e54173d388 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-payload.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Validation(context.Context, *ValidationInfo, goa.NextFunc) (any, error)
+ Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -38,4 +38,9 @@ func (p *validationPayloadAccess) SetName(v string) {
p.payload.Name = v
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapValidation(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden
new file mode 100644
index 0000000000..76648aff03
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden
@@ -0,0 +1,14 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error)
+}
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientCaching(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..fad8d4bbe7
--- /dev/null
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden
@@ -0,0 +1,26 @@
+
+// wrapCaching applies the caching interceptor to endpoints.
+func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &CachingInfo{
+ Service: "InterceptorWithWriteResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Caching(ctx, info, endpoint)
+ }
+}
+
+// wrapClientCaching applies the caching interceptor to endpoints.
+func wrapClientCaching(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &CachingInfo{
+ Service: "InterceptorWithWriteResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Caching(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden
similarity index 69%
rename from codegen/service/testdata/interceptors/interceptor-with-write-result.golden
rename to codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden
index 362ae77d6a..1efc553c96 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-result.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Caching(context.Context, *CachingInfo, goa.NextFunc) (any, error)
+ Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -38,4 +38,9 @@ func (r *cachingResultAccess) SetData(v string) {
r.result.Data = v
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapCaching(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors.golden b/codegen/service/testdata/interceptors/multiple-interceptors.golden
deleted file mode 100644
index 111d148050..0000000000
--- a/codegen/service/testdata/interceptors/multiple-interceptors.golden
+++ /dev/null
@@ -1,30 +0,0 @@
-// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
-type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
-
- Tracing(context.Context, *TracingInfo, goa.NextFunc) (any, error)
-} // ClientInterceptors defines the interface for all client-side interceptors.
-// Client interceptors execute after the payload is encoded and before the request
-// is sent to the server (request interceptors) or after the response is decoded
-// and before the result is returned to the client (response interceptors).
-type ClientInterceptors interface {
- Metrics(context.Context, *MetricsInfo, goa.NextFunc) (any, error)
-}
-
-// Access interfaces for interceptor payloads and results
-type (
- // LoggingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- LoggingInfo goa.InterceptorInfo
- // TracingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- TracingInfo goa.InterceptorInfo
- // MetricsInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- MetricsInfo goa.InterceptorInfo
-)
-
-
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden b/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden
new file mode 100644
index 0000000000..b0f78bbbb5
--- /dev/null
+++ b/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden
@@ -0,0 +1,26 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Test2(ctx context.Context, info *Test2Info, next goa.Endpoint) (any, error)
+ Test4(ctx context.Context, info *Test4Info, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // Test2Info provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ Test2Info goa.InterceptorInfo
+ // Test4Info provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ Test4Info goa.InterceptorInfo
+)
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientTest2(endpoint, i, "Method")
+ endpoint = wrapClientTest4(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..dd2afccd42
--- /dev/null
+++ b/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden
@@ -0,0 +1,52 @@
+
+// wrapTest applies the test interceptor to endpoints.
+func wrapTest(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &TestInfo{
+ Service: "MultipleInterceptorsService",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Test(ctx, info, endpoint)
+ }
+}
+
+// wrapTest3 applies the test3 interceptor to endpoints.
+func wrapTest3(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &Test3Info{
+ Service: "MultipleInterceptorsService",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Test3(ctx, info, endpoint)
+ }
+}
+
+// wrapClientTest2 applies the test2 interceptor to endpoints.
+func wrapClientTest2(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &Test2Info{
+ Service: "MultipleInterceptorsService",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Test2(ctx, info, endpoint)
+ }
+}
+
+// wrapClientTest4 applies the test4 interceptor to endpoints.
+func wrapClientTest4(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &Test4Info{
+ Service: "MultipleInterceptorsService",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Test4(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden b/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden
new file mode 100644
index 0000000000..7d8c68e506
--- /dev/null
+++ b/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden
@@ -0,0 +1,26 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
+type ServerInterceptors interface {
+ Test(ctx context.Context, info *TestInfo, next goa.Endpoint) (any, error)
+ Test3(ctx context.Context, info *Test3Info, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // TestInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ TestInfo goa.InterceptorInfo
+ // Test3Info provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ Test3Info goa.InterceptorInfo
+)
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapTest(endpoint, i, "Method")
+ endpoint = wrapTest3(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/single-api-server-interceptor.golden b/codegen/service/testdata/interceptors/single-api-server-interceptor.golden
deleted file mode 100644
index ae003b0548..0000000000
--- a/codegen/service/testdata/interceptors/single-api-server-interceptor.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
-type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
-}
-
-// Access interfaces for interceptor payloads and results
-type (
- // LoggingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- LoggingInfo goa.InterceptorInfo
-)
-
-
diff --git a/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..1decfd7599
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapLogging applies the logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "SingleAPIServerInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden b/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden
new file mode 100644
index 0000000000..a5af7d39d5
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden
@@ -0,0 +1,28 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
+type ServerInterceptors interface {
+ Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
+
+// WrapMethod2Endpoint wraps the Method2 endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethod2Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method2")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/single-client-interceptor.golden b/codegen/service/testdata/interceptors/single-client-interceptor.golden
deleted file mode 100644
index 0fc4e0f1a9..0000000000
--- a/codegen/service/testdata/interceptors/single-client-interceptor.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// ClientInterceptors defines the interface for all client-side interceptors.
-// Client interceptors execute after the payload is encoded and before the request
-// is sent to the server (request interceptors) or after the response is decoded
-// and before the result is returned to the client (response interceptors).
-type ClientInterceptors interface {
- Tracing(context.Context, *TracingInfo, goa.NextFunc) (any, error)
-}
-
-// Access interfaces for interceptor payloads and results
-type (
- // TracingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- TracingInfo goa.InterceptorInfo
-)
-
-
diff --git a/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden b/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden
new file mode 100644
index 0000000000..41f9b16414
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden
@@ -0,0 +1,21 @@
+// ClientInterceptors defines the interface for all client-side interceptors.
+// Client interceptors execute after the payload is encoded and before the request
+// is sent to the server. The implementation is responsible for calling next to
+// complete the request.
+type ClientInterceptors interface {
+ Tracing(ctx context.Context, info *TracingInfo, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // TracingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ TracingInfo goa.InterceptorInfo
+)
+
+// WrapMethodClientEndpoint wraps the Method endpoint with the client
+// interceptors defined in the design.
+func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
+ endpoint = wrapClientTracing(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..4744840adf
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapClientTracing applies the tracing interceptor to endpoints.
+func wrapClientTracing(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &TracingInfo{
+ Service: "SingleClientInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Tracing(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/single-method-server-interceptor.golden b/codegen/service/testdata/interceptors/single-method-server-interceptor.golden
deleted file mode 100644
index ae003b0548..0000000000
--- a/codegen/service/testdata/interceptors/single-method-server-interceptor.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
-type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
-}
-
-// Access interfaces for interceptor payloads and results
-type (
- // LoggingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- LoggingInfo goa.InterceptorInfo
-)
-
-
diff --git a/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..57c9dce068
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapLogging applies the logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "SingleMethodServerInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden b/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden
new file mode 100644
index 0000000000..05e445a7f2
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden
@@ -0,0 +1,21 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
+type ServerInterceptors interface {
+ Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/single-service-server-interceptor.golden b/codegen/service/testdata/interceptors/single-service-server-interceptor.golden
deleted file mode 100644
index ae003b0548..0000000000
--- a/codegen/service/testdata/interceptors/single-service-server-interceptor.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
-type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
-}
-
-// Access interfaces for interceptor payloads and results
-type (
- // LoggingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- LoggingInfo goa.InterceptorInfo
-)
-
-
diff --git a/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..6e8b74a57c
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapLogging applies the logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "SingleServerInterceptor",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden b/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden
new file mode 100644
index 0000000000..a5af7d39d5
--- /dev/null
+++ b/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden
@@ -0,0 +1,28 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
+type ServerInterceptors interface {
+ Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
+
+// WrapMethod2Endpoint wraps the Method2 endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethod2Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method2")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..f5a3f738df
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapLogging applies the logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "StreamingInterceptorsWithReadPayload",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req.(*MethodEndpointInput).Payload,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden
similarity index 71%
rename from codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden
rename to codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden
index 68f1cf20cb..8f6ed7f94c 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+ Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -42,4 +42,9 @@ func (p *loggingPayloadAccess) Initial() string {
return *p.payload.Initial
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..2cb886bc02
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapLogging applies the logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "StreamingInterceptorsWithReadResult",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req.(*MethodEndpointInput).Payload,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden
similarity index 70%
rename from codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden
rename to codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden
index 4dec14cb90..bc5516387c 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden
@@ -1,9 +1,9 @@
// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
+ Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error)
}
// Access interfaces for interceptor payloads and results
@@ -42,4 +42,9 @@ func (r *loggingResultAccess) Data() string {
return *r.result.Data
}
-
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors.golden b/codegen/service/testdata/interceptors/streaming-interceptors.golden
deleted file mode 100644
index ae003b0548..0000000000
--- a/codegen/service/testdata/interceptors/streaming-interceptors.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// ServerInterceptors defines the interface for all server-side interceptors.
-// Server interceptors execute after the request is decoded and before the payload
-// is sent to the service (request interceptors) or after the service returns and
-// before the response is encoded (response interceptors).
-type ServerInterceptors interface {
- Logging(context.Context, *LoggingInfo, goa.NextFunc) (any, error)
-}
-
-// Access interfaces for interceptor payloads and results
-type (
- // LoggingInfo provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- LoggingInfo goa.InterceptorInfo
-)
-
-
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
new file mode 100644
index 0000000000..e0841bc4be
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
@@ -0,0 +1,13 @@
+
+// wrapLogging applies the logging interceptor to endpoints.
+func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "StreamingInterceptors",
+ Method: method,
+ Endpoint: endpoint,
+ RawPayload: req.(*MethodEndpointInput).Payload,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden
new file mode 100644
index 0000000000..05e445a7f2
--- /dev/null
+++ b/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden
@@ -0,0 +1,21 @@
+// ServerInterceptors defines the interface for all server-side interceptors.
+// Server interceptors execute after the request is decoded and before the
+// payload is sent to the service. The implementation is responsible for calling
+// next to complete the request.
+type ServerInterceptors interface {
+ Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error)
+}
+
+// Access interfaces for interceptor payloads and results
+type (
+ // LoggingInfo provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ LoggingInfo goa.InterceptorInfo
+)
+
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapLogging(endpoint, i, "Method")
+ return endpoint
+}
diff --git a/dsl/method_test.go b/dsl/method_test.go
index 30030fdbfd..d450ecf2f0 100644
--- a/dsl/method_test.go
+++ b/dsl/method_test.go
@@ -45,15 +45,14 @@ func TestMethod(t *testing.T) {
t.Fatalf("b: expected 1 method, got %d", len(methods))
}
method := methods[0]
- doc := method.Docs
- if doc == nil {
+ if method.Docs == nil {
t.Fatalf("b: method docs is nil")
}
- if doc.Description != desc {
- t.Errorf("b: expected docs description '%s' to match '%s' ", desc, doc.Description)
+ if method.Docs.Description != desc {
+ t.Errorf("b: expected docs description '%s' to match '%s' ", desc, method.Docs.Description)
}
- if doc.URL != url {
- t.Errorf("b: expected docs url '%s' to match '%s' ", url, doc.URL)
+ if method.Docs.URL != url {
+ t.Errorf("b: expected docs url '%s' to match '%s' ", url, method.Docs.URL)
}
},
},
@@ -74,19 +73,20 @@ func TestMethod(t *testing.T) {
method := methods[0]
if method == nil {
t.Fatalf("c: method is nil")
+ return // Make linter happy
}
- payload := method.Payload
- if payload == nil {
+ if method.Payload == nil {
t.Fatalf("c: method payload is nil")
+ return // Make linter happy
}
- if payload.Description != desc {
- t.Errorf("c: expected payload description '%s' to match '%s' ", desc, payload.Description)
+ if method.Payload.Description != desc {
+ t.Errorf("c: expected payload description '%s' to match '%s' ", desc, method.Payload.Description)
}
- obj := expr.AsObject(payload.Type)
+ obj := expr.AsObject(method.Payload.Type)
if att := obj.Attribute("required"); att == nil {
t.Errorf("c: expected a payload field with key required")
}
- if !payload.IsRequired("required") {
+ if !method.Payload.IsRequired("required") {
t.Errorf("c: expected the required field to be required")
}
},
diff --git a/expr/http_endpoint_test.go b/expr/http_endpoint_test.go
index 605438087c..b1d40f45b5 100644
--- a/expr/http_endpoint_test.go
+++ b/expr/http_endpoint_test.go
@@ -215,8 +215,8 @@ func TestHTTPEndpointParentRequired(t *testing.T) {
t.Fatal(`unexpected error, service "Child" not found`)
}
m := svc.Method("Method")
- if m == nil {
- t.Fatal(`unexpected error, method "Method" not found`)
+ if m == nil || m.Payload == nil {
+ t.Fatal(`unexpected error, method "Method" or its payload not found`)
}
if !m.Payload.IsRequired("ancestor_id") {
t.Errorf(`expected "ancestor_id" is required, but not so`)
diff --git a/expr/method_test.go b/expr/method_test.go
index bd2bef8611..d23d58ddc7 100644
--- a/expr/method_test.go
+++ b/expr/method_test.go
@@ -154,3 +154,103 @@ func TestMethodExprIsPayloadStreaming(t *testing.T) {
}
}
}
+
+func TestMethodExprPrepare(t *testing.T) {
+ var (
+ apiInterceptor = &expr.InterceptorExpr{Name: "api"}
+ svcInterceptor = &expr.InterceptorExpr{Name: "service"}
+ mtdInterceptor = &expr.InterceptorExpr{Name: "method"}
+ )
+
+ cases := map[string]struct {
+ api *expr.APIExpr
+ service *expr.ServiceExpr
+ method *expr.MethodExpr
+ expectedServer []*expr.InterceptorExpr
+ expectedClient []*expr.InterceptorExpr
+ }{
+ "no-interceptors": {
+ api: &expr.APIExpr{},
+ service: &expr.ServiceExpr{},
+ method: &expr.MethodExpr{},
+ expectedServer: []*expr.InterceptorExpr{},
+ expectedClient: []*expr.InterceptorExpr{},
+ },
+ "api-only": {
+ api: &expr.APIExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ service: &expr.ServiceExpr{},
+ method: &expr.MethodExpr{},
+ expectedServer: []*expr.InterceptorExpr{apiInterceptor},
+ expectedClient: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ "service-only": {
+ api: &expr.APIExpr{},
+ service: &expr.ServiceExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{svcInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{svcInterceptor},
+ },
+ method: &expr.MethodExpr{},
+ expectedServer: []*expr.InterceptorExpr{svcInterceptor},
+ expectedClient: []*expr.InterceptorExpr{svcInterceptor},
+ },
+ "method-only": {
+ api: &expr.APIExpr{},
+ service: &expr.ServiceExpr{},
+ method: &expr.MethodExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
+ },
+ expectedServer: []*expr.InterceptorExpr{mtdInterceptor},
+ expectedClient: []*expr.InterceptorExpr{mtdInterceptor},
+ },
+ "chained-interceptors": {
+ api: &expr.APIExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ service: &expr.ServiceExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{svcInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{svcInterceptor},
+ },
+ method: &expr.MethodExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
+ },
+ expectedServer: []*expr.InterceptorExpr{mtdInterceptor, svcInterceptor, apiInterceptor},
+ expectedClient: []*expr.InterceptorExpr{mtdInterceptor, svcInterceptor, apiInterceptor},
+ },
+ "duplicate-interceptors": {
+ api: &expr.APIExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ service: &expr.ServiceExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor}, // Same as API
+ ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ method: &expr.MethodExpr{
+ ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor}, // Same as API
+ ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ expectedServer: []*expr.InterceptorExpr{apiInterceptor}, // Only one copy
+ expectedClient: []*expr.InterceptorExpr{apiInterceptor},
+ },
+ }
+
+ for name, tc := range cases {
+ t.Run(name, func(t *testing.T) {
+ expr.Root.API = tc.api
+ tc.method.Service = tc.service
+ tc.method.Prepare()
+
+ assert.Equal(t, tc.expectedServer, tc.method.ServerInterceptors)
+ assert.Equal(t, tc.expectedClient, tc.method.ClientInterceptors)
+ assert.NotNil(t, tc.method.Payload)
+ assert.NotNil(t, tc.method.StreamingPayload)
+ assert.NotNil(t, tc.method.Result)
+ })
+ }
+}
diff --git a/grpc/codegen/example_cli.go b/grpc/codegen/example_cli.go
index 36eee89f7b..8e346f5c2c 100644
--- a/grpc/codegen/example_cli.go
+++ b/grpc/codegen/example_cli.go
@@ -24,9 +24,10 @@ func ExampleCLIFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
// exampleCLI returns an example client tool HTTP implementation for the given
// server expression.
-func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
+func exampleCLI(genpkg string, _ *expr.RootExpr, svr *expr.ServerExpr) *codegen.File {
var (
mainPath string
+ rootPath string
svrdata = example.Servers.Get(svr)
)
@@ -35,22 +36,11 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
if _, err := os.Stat(mainPath); !os.IsNotExist(err) {
return nil // file already exists, skip it.
}
- }
-
- var (
- rootPath string
- apiPkg string
-
- scope = codegen.NewNameScope()
- )
- {
- // genpkg is created by path.Join so the separator is / regardless of operating system
idx := strings.LastIndex(genpkg, string("/"))
rootPath = "."
if idx > 0 {
rootPath = genpkg[:idx]
}
- apiPkg = scope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
}
var (
@@ -68,11 +58,18 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
{Path: "time"},
codegen.GoaImport(""),
codegen.GoaNamedImport("grpc", "goagrpc"),
- {Path: rootPath, Name: apiPkg},
+ {Path: rootPath + "/interceptors"},
{Path: path.Join(genpkg, "grpc", "cli", svrdata.Dir), Name: "cli"},
}
}
+ var svcData []*ServiceData
+ for _, svc := range svr.Services {
+ if data := GRPCServices.Get(svc); data != nil {
+ svcData = append(svcData, data)
+ }
+ }
+
var (
sections []*codegen.SectionTemplate
)
@@ -82,7 +79,11 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
{
Name: "do-grpc-cli",
Source: readTemplate("do_grpc_cli"),
- Data: svrdata,
+ Data: map[string]any{
+ "DefaultTransport": svrdata.DefaultTransport(),
+ "Services": svcData,
+ "InterceptorsPkg": "interceptors",
+ },
},
}
}
diff --git a/grpc/codegen/example_cli_test.go b/grpc/codegen/example_cli_test.go
index 9a9b59a787..aeb5fffe35 100644
--- a/grpc/codegen/example_cli_test.go
+++ b/grpc/codegen/example_cli_test.go
@@ -2,6 +2,7 @@ package codegen
import (
"bytes"
+ "path/filepath"
"testing"
"github.com/stretchr/testify/require"
@@ -9,6 +10,7 @@ import (
"goa.design/goa/v3/codegen/example"
ctestdata "goa.design/goa/v3/codegen/example/testdata"
"goa.design/goa/v3/expr"
+ "goa.design/goa/v3/grpc/codegen/testdata"
)
func TestExampleCLIFiles(t *testing.T) {
@@ -23,6 +25,7 @@ func TestExampleCLIFiles(t *testing.T) {
{"no-server-pkgpath", ctestdata.NoServerDSL, "my/pkg/path"},
{"server-hosting-service-subset-pkgpath", ctestdata.ServerHostingServiceSubsetDSL, "my/pkg/path"},
{"server-hosting-multiple-services-pkgpath", ctestdata.ServerHostingMultipleServicesDSL, "my/pkg/path"},
+ {"interceptors", testdata.InterceptorsDSL, ""},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
@@ -37,7 +40,8 @@ func TestExampleCLIFiles(t *testing.T) {
require.NoError(t, s.Write(&buf))
}
code := codegen.FormatTestCode(t, buf.String())
- compareOrUpdateGolden(t, code, "client-"+c.Name+".golden")
+ golden := filepath.Join("testdata", "client-"+c.Name+".golden")
+ compareOrUpdateGolden(t, code, golden)
})
}
}
diff --git a/grpc/codegen/templates/do_grpc_cli.go.tpl b/grpc/codegen/templates/do_grpc_cli.go.tpl
index 8802e20c69..bdc52ca3cb 100644
--- a/grpc/codegen/templates/do_grpc_cli.go.tpl
+++ b/grpc/codegen/templates/do_grpc_cli.go.tpl
@@ -3,7 +3,19 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+{{- range .Services }}
+ {{- if .Service.ClientInterceptors }}
+ {{ .Service.VarName }}Interceptors := {{ $.InterceptorsPkg }}.New{{ .Service.StructName }}ClientInterceptors()
+ {{- end }}
+{{- end }}
+ return cli.ParseEndpoint(
+ conn,
+{{- range .Services }}
+ {{- if .Service.ClientInterceptors }}
+ {{ .Service.VarName }}Interceptors,
+ {{- end }}
+{{- end }}
+ )
}
{{ if eq .DefaultTransport.Type "grpc" }}
diff --git a/grpc/codegen/templates/parse_endpoint.go.tpl b/grpc/codegen/templates/parse_endpoint.go.tpl
index b7f3eed3ad..3cf69cde5a 100644
--- a/grpc/codegen/templates/parse_endpoint.go.tpl
+++ b/grpc/codegen/templates/parse_endpoint.go.tpl
@@ -1,6 +1,12 @@
// ParseEndpoint returns the endpoint and payload as specified on the command
// line.
-func ParseEndpoint(cc *grpc.ClientConn, opts ...grpc.CallOption) (goa.Endpoint, any, error) {
+func ParseEndpoint(
+ cc *grpc.ClientConn,
+ {{- if .Interceptors }}
+ {{ .Interceptors.VarName }} {{ .Interceptors.PkgName }}.ClientInterceptors,
+ {{- end }}
+ opts ...grpc.CallOption,
+) (goa.Endpoint, any, error) {
{{ .FlagsCode }}
var (
data any
@@ -13,9 +19,14 @@ func ParseEndpoint(cc *grpc.ClientConn, opts ...grpc.CallOption) (goa.Endpoint,
case "{{ .Name }}":
c := {{ .PkgName }}.NewClient(cc, opts...)
switch epn {
- {{- $pkgName := .PkgName }}{{ range .Subcommands }}
+ {{- $pkgName := .PkgName }}
+ {{- $interceptors := .Interceptors }}
+ {{ range .Subcommands }}
case "{{ .Name }}":
endpoint = c.{{ .MethodVarName }}()
+ {{- if $interceptors }}
+ endpoint = {{ $interceptors.PkgName }}.Wrap{{ .MethodVarName }}ClientEndpoint(endpoint, {{ $interceptors.VarName }})
+ {{- end }}
{{- if .BuildFunction }}
data, err = {{ $pkgName}}.{{ .BuildFunction.Name }}({{ range .BuildFunction.ActualParams }}*{{ . }}Flag, {{ end }})
{{- else if .Conversion }}
diff --git a/grpc/codegen/testdata/client-interceptors.golden b/grpc/codegen/testdata/client-interceptors.golden
new file mode 100644
index 0000000000..f21f374e1e
--- /dev/null
+++ b/grpc/codegen/testdata/client-interceptors.golden
@@ -0,0 +1,30 @@
+import (
+ "fmt"
+ cli "grpc/cli/test_api"
+ "os"
+
+ "./interceptors"
+ goa "goa.design/goa/v3/pkg"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+)
+
+func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
+ conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials()))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
+ }
+ serviceWithInterceptorsInterceptors := interceptors.NewServiceWithInterceptorsClientInterceptors()
+ return cli.ParseEndpoint(
+ conn,
+ serviceWithInterceptorsInterceptors,
+ )
+}
+
+func grpcUsageCommands() string {
+ return cli.UsageCommands()
+}
+
+func grpcUsageExamples() string {
+ return cli.UsageExamples()
+}
diff --git a/grpc/codegen/client-no-server-pkgpath.golden b/grpc/codegen/testdata/client-no-server-pkgpath.golden
similarity index 91%
rename from grpc/codegen/client-no-server-pkgpath.golden
rename to grpc/codegen/testdata/client-no-server-pkgpath.golden
index 56ca0253c8..d1033172ff 100644
--- a/grpc/codegen/client-no-server-pkgpath.golden
+++ b/grpc/codegen/testdata/client-no-server-pkgpath.golden
@@ -13,5 +13,7 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+ return cli.ParseEndpoint(
+ conn,
+ )
}
diff --git a/grpc/codegen/client-no-server.golden b/grpc/codegen/testdata/client-no-server.golden
similarity index 91%
rename from grpc/codegen/client-no-server.golden
rename to grpc/codegen/testdata/client-no-server.golden
index b6a7c341da..e9259418ae 100644
--- a/grpc/codegen/client-no-server.golden
+++ b/grpc/codegen/testdata/client-no-server.golden
@@ -13,5 +13,7 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+ return cli.ParseEndpoint(
+ conn,
+ )
}
diff --git a/grpc/codegen/client-server-hosting-multiple-services-pkgpath.golden b/grpc/codegen/testdata/client-server-hosting-multiple-services-pkgpath.golden
similarity index 92%
rename from grpc/codegen/client-server-hosting-multiple-services-pkgpath.golden
rename to grpc/codegen/testdata/client-server-hosting-multiple-services-pkgpath.golden
index 22ea9249b5..5324d085af 100644
--- a/grpc/codegen/client-server-hosting-multiple-services-pkgpath.golden
+++ b/grpc/codegen/testdata/client-server-hosting-multiple-services-pkgpath.golden
@@ -13,5 +13,7 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+ return cli.ParseEndpoint(
+ conn,
+ )
}
diff --git a/grpc/codegen/client-server-hosting-multiple-services.golden b/grpc/codegen/testdata/client-server-hosting-multiple-services.golden
similarity index 91%
rename from grpc/codegen/client-server-hosting-multiple-services.golden
rename to grpc/codegen/testdata/client-server-hosting-multiple-services.golden
index 6956659be3..dcfe3180eb 100644
--- a/grpc/codegen/client-server-hosting-multiple-services.golden
+++ b/grpc/codegen/testdata/client-server-hosting-multiple-services.golden
@@ -13,5 +13,7 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+ return cli.ParseEndpoint(
+ conn,
+ )
}
diff --git a/grpc/codegen/client-server-hosting-service-subset-pkgpath.golden b/grpc/codegen/testdata/client-server-hosting-service-subset-pkgpath.golden
similarity index 92%
rename from grpc/codegen/client-server-hosting-service-subset-pkgpath.golden
rename to grpc/codegen/testdata/client-server-hosting-service-subset-pkgpath.golden
index 22ea9249b5..5324d085af 100644
--- a/grpc/codegen/client-server-hosting-service-subset-pkgpath.golden
+++ b/grpc/codegen/testdata/client-server-hosting-service-subset-pkgpath.golden
@@ -13,5 +13,7 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+ return cli.ParseEndpoint(
+ conn,
+ )
}
diff --git a/grpc/codegen/client-server-hosting-service-subset.golden b/grpc/codegen/testdata/client-server-hosting-service-subset.golden
similarity index 91%
rename from grpc/codegen/client-server-hosting-service-subset.golden
rename to grpc/codegen/testdata/client-server-hosting-service-subset.golden
index 6956659be3..dcfe3180eb 100644
--- a/grpc/codegen/client-server-hosting-service-subset.golden
+++ b/grpc/codegen/testdata/client-server-hosting-service-subset.golden
@@ -13,5 +13,7 @@ func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) {
if err != nil {
fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err)
}
- return cli.ParseEndpoint(conn)
+ return cli.ParseEndpoint(
+ conn,
+ )
}
diff --git a/grpc/codegen/testdata/dsls.go b/grpc/codegen/testdata/dsls.go
index f7c7c5c7ca..743ce71cb8 100644
--- a/grpc/codegen/testdata/dsls.go
+++ b/grpc/codegen/testdata/dsls.go
@@ -1009,3 +1009,27 @@ var CustomMessageNameDSL = func() {
})
})
}
+
+var InterceptorsDSL = func() {
+ var LogInterceptor = Interceptor("Log", func() {
+ Description("Logs request and response details")
+ })
+ var MetricsInterceptor = Interceptor("Metrics", func() {
+ Description("Collects metrics for the operation")
+ })
+
+ Service("ServiceWithInterceptors", func() {
+ ClientInterceptor(LogInterceptor)
+ Method("MethodA", func() {
+ ClientInterceptor(MetricsInterceptor)
+ Payload(String)
+ Result(String)
+ GRPC(func() {})
+ })
+ Method("MethodB", func() {
+ Payload(Int)
+ Result(Int)
+ GRPC(func() {})
+ })
+ })
+}
diff --git a/http/codegen/client_cli.go b/http/codegen/client_cli.go
index 73a787e54a..9cf67f2888 100644
--- a/http/codegen/client_cli.go
+++ b/http/codegen/client_cli.go
@@ -127,6 +127,13 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
Path: genpkg + "/http/" + sd.Service.PathName + "/client",
Name: sd.Service.PkgName + "c",
})
+ // Add interceptors import if service has client interceptors
+ if len(sd.Service.ClientInterceptors) > 0 {
+ specs = append(specs, &codegen.ImportSpec{
+ Path: genpkg + "/" + sd.Service.PathName,
+ Name: sd.Service.PkgName,
+ })
+ }
}
cliData := make([]*cli.CommandData, len(data))
diff --git a/http/codegen/example_cli.go b/http/codegen/example_cli.go
index fe7b6e4968..c6d57c8340 100644
--- a/http/codegen/example_cli.go
+++ b/http/codegen/example_cli.go
@@ -7,6 +7,7 @@ import (
"goa.design/goa/v3/codegen"
"goa.design/goa/v3/codegen/example"
+ "goa.design/goa/v3/codegen/service"
"goa.design/goa/v3/expr"
)
@@ -30,20 +31,10 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
if _, err := os.Stat(path); !os.IsNotExist(err) {
return nil // file already exists, skip it.
}
- var (
- rootPath string
- apiPkg string
-
- scope = codegen.NewNameScope()
- )
- {
- // genpkg is created by path.Join so the separator is / regardless of operating system
- idx := strings.LastIndex(genpkg, string("/"))
- rootPath = "."
- if idx > 0 {
- rootPath = genpkg[:idx]
- }
- apiPkg = scope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
+ idx := strings.LastIndex(genpkg, string("/"))
+ rootPath := "."
+ if idx > 0 {
+ rootPath = genpkg[:idx]
}
specs := []*codegen.ImportSpec{
{Path: "context"},
@@ -59,8 +50,17 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
codegen.GoaImport(""),
codegen.GoaNamedImport("http", "goahttp"),
{Path: genpkg + "/http/cli/" + svrdata.Dir, Name: "cli"},
- {Path: rootPath, Name: apiPkg},
}
+ importScope := codegen.NewNameScope()
+ for _, svc := range root.Services {
+ data := service.Services.Get(svc.Name)
+ specs = append(specs, &codegen.ImportSpec{Path: genpkg + "/" + data.PkgName})
+ importScope.Unique(data.PkgName)
+ }
+ interceptorsPkg := importScope.Unique("interceptors", "ex")
+ specs = append(specs, &codegen.ImportSpec{Path: rootPath + "/interceptors", Name: interceptorsPkg})
+ apiPkg := importScope.Unique(strings.ToLower(codegen.Goify(root.API.Name, false)), "api")
+ specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: apiPkg})
var svcData []*ServiceData
for _, svc := range svr.Services {
@@ -73,6 +73,10 @@ func exampleCLI(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr) *codeg
{
Name: "cli-http-start",
Source: readTemplate("cli_start"),
+ Data: map[string]any{
+ "Services": svcData,
+ "InterceptorsPkg": interceptorsPkg,
+ },
},
{
Name: "cli-http-streaming",
diff --git a/http/codegen/templates/cli_end.go.tpl b/http/codegen/templates/cli_end.go.tpl
index f94e8b7878..31c52c9eb3 100644
--- a/http/codegen/templates/cli_end.go.tpl
+++ b/http/codegen/templates/cli_end.go.tpl
@@ -5,20 +5,25 @@ return cli.ParseEndpoint(
goahttp.RequestEncoder,
goahttp.ResponseDecoder,
debug,
- {{- if needStream .Services }}
+{{- if needStream .Services }}
dialer,
- {{- range $svc := .Services }}
- {{- if hasWebSocket $svc }}
- nil,
- {{- end }}
- {{- end }}
+ {{- range $svc := .Services }}
+ {{- if hasWebSocket $svc }}
+ nil,
{{- end }}
- {{- range .Services }}
- {{- range .Endpoints }}
- {{- if .MultipartRequestDecoder }}
+ {{- end }}
+{{- end }}
+{{- range .Services }}
+ {{- range .Endpoints }}
+ {{- if .MultipartRequestDecoder }}
{{ $.APIPkg }}.{{ .MultipartRequestEncoder.FuncName }},
- {{- end }}
- {{- end }}
{{- end }}
+ {{- end }}
+{{- end }}
+{{- range .Services }}
+ {{- if .Service.ClientInterceptors }}
+ {{ .Service.VarName }}Interceptors,
+ {{- end }}
+{{- end }}
)
-}
+}
\ No newline at end of file
diff --git a/http/codegen/templates/cli_start.go.tpl b/http/codegen/templates/cli_start.go.tpl
index c068dd6f7a..8ca80e51f8 100644
--- a/http/codegen/templates/cli_start.go.tpl
+++ b/http/codegen/templates/cli_start.go.tpl
@@ -1,10 +1,20 @@
func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) {
var (
doer goahttp.Doer
+{{- range .Services }}
+ {{- if .Service.ClientInterceptors }}
+ {{ .Service.VarName }}Interceptors {{ .Service.PkgName }}.ClientInterceptors
+ {{- end }}
+{{- end }}
)
{
doer = &http.Client{Timeout: time.Duration(timeout) * time.Second}
if debug {
doer = goahttp.NewDebugDoer(doer)
}
+{{- range .Services }}
+ {{- if .Service.ClientInterceptors }}
+ {{ .Service.VarName }}Interceptors = {{ $.InterceptorsPkg }}.New{{ .Service.StructName }}ClientInterceptors()
+ {{- end }}
+{{- end }}
}
diff --git a/http/codegen/templates/parse_endpoint.go.tpl b/http/codegen/templates/parse_endpoint.go.tpl
index d967bcfdcb..fc55279552 100644
--- a/http/codegen/templates/parse_endpoint.go.tpl
+++ b/http/codegen/templates/parse_endpoint.go.tpl
@@ -14,12 +14,15 @@ func ParseEndpoint(
{{- end }}
{{- end }}
{{- end }}
- {{- range $c := .Commands }}
+ {{- range $i, $c := .Commands }}
{{- range .Subcommands }}
{{- if .MultipartVarName }}
{{ .MultipartVarName }} {{ $c.PkgName }}.{{ .MultipartFuncName }},
{{- end }}
{{- end }}
+ {{- if .Interceptors }}
+ {{ .Interceptors.VarName }} {{ .Interceptors.PkgName }}.ClientInterceptors,
+ {{- end }}
{{- end }}
) (goa.Endpoint, any, error) {
{{ .FlagsCode }}
@@ -34,11 +37,16 @@ func ParseEndpoint(
case "{{ .Name }}":
c := {{ .PkgName }}.NewClient(scheme, host, doer, enc, dec, restore{{ if .NeedStream }}, dialer, {{ .VarName }}Configurer{{ end }})
switch epn {
- {{- $pkgName := .PkgName }}{{ range .Subcommands }}
+ {{- $pkgName := .PkgName }}
+ {{- $interceptors := .Interceptors }}
+ {{- range .Subcommands }}
case "{{ .Name }}":
endpoint = c.{{ .MethodVarName }}({{ if .MultipartVarName }}{{ .MultipartVarName }}{{ end }})
+ {{- if $interceptors }}
+ endpoint = {{ $interceptors.PkgName }}.Wrap{{ .MethodVarName }}ClientEndpoint(endpoint, {{ $interceptors.VarName }})
+ {{- end }}
{{- if .BuildFunction }}
- data, err = {{ $pkgName}}.{{ .BuildFunction.Name }}({{ range .BuildFunction.ActualParams }}*{{ . }}Flag, {{ end }})
+ data, err = {{ $pkgName }}.{{ .BuildFunction.Name }}({{ range .BuildFunction.ActualParams }}*{{ . }}Flag, {{ end }})
{{- else if .Conversion }}
{{ .Conversion }}
{{- end }}
diff --git a/pkg/interceptor.go b/pkg/interceptor.go
index 022d7a4de6..c784f14b88 100644
--- a/pkg/interceptor.go
+++ b/pkg/interceptor.go
@@ -1,7 +1,5 @@
package goa
-import "context"
-
type (
// InterceptorInfo contains information about the request shared between
// all interceptors in the service chain. It provides access to the service name,
@@ -16,7 +14,4 @@ type (
// Payload of request
RawPayload any
}
-
- // NextFunc is a function that will continue the request processing chain.
- NextFunc func(ctx context.Context) (any, error)
)
From 1c5f19c8c0e530998d38683b1d196a80aceb1902 Mon Sep 17 00:00:00 2001
From: Raphael Simon
Date: Sun, 19 Jan 2025 10:52:14 -0800
Subject: [PATCH 5/6] Finalize initial interceptor implementation
---
codegen/cli/cli.go | 3 +-
.../templates/server_interceptors.go.tpl | 4 -
codegen/service/client.go | 2 +-
codegen/service/endpoint.go | 25 +-
codegen/service/example_interceptors.go | 4 +-
codegen/service/interceptors.go | 84 +++---
codegen/service/interceptors.md | 149 +++++++++++
codegen/service/interceptors_test.go | 8 +-
codegen/service/service.go | 2 +-
codegen/service/service_data.go | 246 +++++++++++-------
.../client_interceptor_wrappers.go.tpl | 12 +-
.../service/templates/client_wrappers.go.tpl | 5 +-
.../templates/endpoint_wrappers.go.tpl | 4 +-
codegen/service/templates/interceptors.go.tpl | 144 ++++------
.../templates/interceptors_types.go.tpl | 62 +++++
.../server_interceptor_wrappers.go.tpl | 12 +-
.../templates/server_interceptors.go.tpl | 4 +-
dsl/interceptor.go | 4 +
expr/method.go | 59 ++---
expr/method_test.go | 114 ++------
expr/testdata/interceptors_validate_dsls.go | 83 ++++++
grpc/codegen/client.go | 2 +-
grpc/codegen/client_cli.go | 9 +-
grpc/codegen/parse_endpoint_test.go | 40 +++
grpc/codegen/templates/parse_endpoint.go.tpl | 2 +
.../testdata/client-interceptors.golden | 2 +-
grpc/codegen/testdata/dsls.go | 7 +
...endpoint-endpoint-with-interceptors.golden | 178 +++++++++++++
http/codegen/client.go | 2 +-
29 files changed, 889 insertions(+), 383 deletions(-)
create mode 100644 codegen/service/interceptors.md
create mode 100644 codegen/service/templates/interceptors_types.go.tpl
create mode 100644 expr/testdata/interceptors_validate_dsls.go
create mode 100644 grpc/codegen/parse_endpoint_test.go
create mode 100644 grpc/codegen/testdata/endpoint-endpoint-with-interceptors.golden
diff --git a/codegen/cli/cli.go b/codegen/cli/cli.go
index 1135424ae3..6c852cc5e8 100644
--- a/codegen/cli/cli.go
+++ b/codegen/cli/cli.go
@@ -702,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:
diff --git a/codegen/example/templates/server_interceptors.go.tpl b/codegen/example/templates/server_interceptors.go.tpl
index c8bf2215b7..c2b09fcae3 100644
--- a/codegen/example/templates/server_interceptors.go.tpl
+++ b/codegen/example/templates/server_interceptors.go.tpl
@@ -8,16 +8,12 @@
{{- end }}
{{- end }}
)
- {{- end }}
- {{- if .HasInterceptors }}
{
- {{- end }}
{{- range .Services }}
{{- if and .Methods .ServerInterceptors }}
{{ .VarName }}Interceptors = {{ $.InterPkg }}.New{{ .StructName }}ServerInterceptors()
{{- end }}
{{- end }}
- {{- if .HasInterceptors }}
}
{{- end }}
{{- end }}
\ No newline at end of file
diff --git a/codegen/service/client.go b/codegen/service/client.go
index a088de0d37..8cc61b2dc4 100644
--- a/codegen/service/client.go
+++ b/codegen/service/client.go
@@ -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
diff --git a/codegen/service/endpoint.go b/codegen/service/endpoint.go
index b499a9e6f7..8ca7156704 100644
--- a/codegen/service/endpoint.go
+++ b/codegen/service/endpoint.go
@@ -50,10 +50,6 @@ type (
ServiceName string
// ServiceVarName is the name of the owner service Go interface.
ServiceVarName string
- // ServerInterceptors contains the server-side interceptors for this method
- ServerInterceptors []*InterceptorData
- // ClientInterceptors contains the client-side interceptors for this method
- ClientInterceptors []*InterceptorData
}
)
@@ -71,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
)
@@ -138,25 +134,22 @@ 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 {
methods[i] = &EndpointMethodData{
- MethodData: m,
- ArgName: codegen.Goify(m.VarName, false),
- ServiceName: svc.Name,
- ServiceVarName: serviceInterfaceName,
- ClientVarName: clientStructName,
- ServerInterceptors: m.ServerInterceptors,
- ClientInterceptors: m.ClientInterceptors,
+ MethodData: m,
+ ArgName: codegen.Goify(m.VarName, false),
+ ServiceName: svc.Name,
+ ServiceVarName: serviceInterfaceName,
+ ClientVarName: clientStructName,
}
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,
+ Name: svc.Name,
Description: desc,
VarName: endpointsStructName,
ClientVarName: clientStructName,
diff --git a/codegen/service/example_interceptors.go b/codegen/service/example_interceptors.go
index c348beae57..7c24b08bea 100644
--- a/codegen/service/example_interceptors.go
+++ b/codegen/service/example_interceptors.go
@@ -49,7 +49,7 @@ func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.Fi
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
}),
{
- Name: "server-interceptor",
+ Name: "exmaple-server-interceptor",
Source: readTemplate("example_server_interceptor"),
Data: data,
},
@@ -73,7 +73,7 @@ func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.Fi
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
}),
{
- Name: "client-interceptor",
+ Name: "example-client-interceptor",
Source: readTemplate("example_client_interceptor"),
Data: data,
},
diff --git a/codegen/service/interceptors.go b/codegen/service/interceptors.go
index 78ddb543ca..8da90f9f1e 100644
--- a/codegen/service/interceptors.go
+++ b/codegen/service/interceptors.go
@@ -29,35 +29,41 @@ func InterceptorsFiles(genpkg string, service *expr.ServiceExpr) []*codegen.File
}
// interceptorFile returns the file defining the interceptors.
+// This method is called twice, once for the server and once for the client.
func interceptorFile(svc *Data, server bool) *codegen.File {
filename := "client_interceptors.go"
template := "client_interceptors"
- section := "client-interceptors"
+ section := "client-interceptors-type"
desc := "Client Interceptors"
- var data []*InterceptorData
if server {
filename = "service_interceptors.go"
template = "server_interceptors"
- section = "server-interceptors"
+ section = "server-interceptors-type"
desc = "Server Interceptors"
- data = svc.ServerInterceptors
}
desc = svc.Name + desc
path := filepath.Join(codegen.Gendir, svc.PathName, filename)
+ interceptors := svc.ServerInterceptors
if !server {
- // We don't want to generate duplicate interceptor info data structures for
- // interceptors that are both server and client side.
- serverInterceptors := make(map[string]struct{}, len(svc.ServerInterceptors))
- for _, intr := range svc.ServerInterceptors {
- serverInterceptors[intr.Name] = struct{}{}
+ interceptors = svc.ClientInterceptors
+ }
+
+ // We don't want to generate duplicate interceptor info data structures for
+ // interceptors that are both server and client side so remove interceptors
+ // that are both server and client side when generating the client.
+ if !server {
+ names := make(map[string]struct{}, len(svc.ServerInterceptors))
+ for _, sin := range svc.ServerInterceptors {
+ names[sin.Name] = struct{}{}
}
- for _, intr := range svc.ClientInterceptors {
- if _, ok := serverInterceptors[intr.Name]; ok {
- continue
+ filtered := make([]*InterceptorData, 0, len(interceptors))
+ for _, in := range interceptors {
+ if _, ok := names[in.Name]; !ok {
+ filtered = append(filtered, in)
}
- data = append(data, intr)
}
+ interceptors = filtered
}
sections := []*codegen.SectionTemplate{
@@ -66,45 +72,59 @@ func interceptorFile(svc *Data, server bool) *codegen.File {
codegen.GoaImport(""),
}),
{
- Name: "section" + "-struct",
+ Name: section,
Source: readTemplate(template),
Data: svc,
},
}
- if len(data) > 0 {
+ if len(interceptors) > 0 {
sections = append(sections, &codegen.SectionTemplate{
- Name: section,
- Source: readTemplate("interceptors"),
- Data: data,
+ Name: "interceptor-types",
+ Source: readTemplate("interceptors_types"),
+ Data: interceptors,
FuncMap: map[string]any{
"hasPrivateImplementationTypes": hasPrivateImplementationTypes,
},
})
}
- // Add wrapper sections for each method that has interceptors
+ template = "endpoint_wrappers"
+ section = "endpoint-wrapper"
+ if !server {
+ template = "client_wrappers"
+ section = "client-wrapper"
+ }
for _, m := range svc.Methods {
- if server && len(m.ServerInterceptors) == 0 || !server && len(m.ClientInterceptors) == 0 {
- continue
- }
- template := "endpoint_wrappers"
- templateName := "endpoint-wrapper"
+ ints := m.ServerInterceptors
if !server {
- template = "client_wrappers"
- templateName = "client-wrapper"
+ ints = m.ClientInterceptors
+ }
+ if len(ints) == 0 {
+ continue
}
sections = append(sections, &codegen.SectionTemplate{
- Name: templateName,
+ Name: section,
Source: readTemplate(template),
Data: map[string]interface{}{
- "MethodVarName": codegen.Goify(m.Name, true),
- "Method": m.Name,
- "Service": svc.Name,
- "ServerInterceptors": m.ServerInterceptors,
- "ClientInterceptors": m.ClientInterceptors,
+ "MethodVarName": m.VarName,
+ "Method": m.Name,
+ "Service": svc.Name,
+ "Interceptors": ints,
},
})
}
+
+ if len(interceptors) > 0 {
+ sections = append(sections, &codegen.SectionTemplate{
+ Name: "interceptors",
+ Source: readTemplate("interceptors"),
+ Data: interceptors,
+ FuncMap: map[string]any{
+ "hasPrivateImplementationTypes": hasPrivateImplementationTypes,
+ },
+ })
+ }
+
return &codegen.File{Path: path, SectionTemplates: sections}
}
diff --git a/codegen/service/interceptors.md b/codegen/service/interceptors.md
new file mode 100644
index 0000000000..80795d3d56
--- /dev/null
+++ b/codegen/service/interceptors.md
@@ -0,0 +1,149 @@
+# Interceptors Code Generation
+
+Goa generates interceptor code to enable request/response interception and payload/result access.
+
+---
+
+## 1. Client & Server Interceptors
+
+### Where They Are Generated
+
+Client and server interceptor code is generated in:
+
+- `gen/client_interceptors.go`
+- `gen/service_interceptors.go`
+
+### Templates Used
+
+1. **`server_interceptors.go.tpl`** and **`client_interceptors.go.tpl`**
+ Define the server and client interceptor interface types.
+ Each template takes a `Data` struct as input.
+
+2. **`interceptors.go.tpl`**
+ Generates the payload/result access interfaces and accompanying info structs.
+ This template takes a slice of `InterceptorData` as input.
+
+3. **`client_wrappers.go.tpl`** and **`endpoint_wrappers.go.tpl`**
+ Generate code that wraps client and service endpoints with interceptor callbacks.
+ Each template takes a map with:
+ ```go
+ map[string]any{
+ "MethodVarName":
+ "Method":
+ "Service":
+ "Interceptors":
+ }
+ ```
+
+---
+
+## 2. Client & Server Endpoint Wrapper Code
+
+### Where It Is Generated
+
+Endpoint wrapper code for both client and server interceptors is generated in:
+
+- `gen/interceptor_wrappers.go`
+
+### Templates Used
+
+1. **`server_interceptor_wrappers.go.tpl`**
+ Generates server-specific wrapper implementations. This template receives a map with:
+ ```go
+ map[string]any{
+ "Service": svc.Name,
+ "ServerInterceptors": svc.ServerInterceptors,
+ }
+ ```
+
+2. **`client_interceptor_wrappers.go.tpl`**
+ Generates client-specific wrapper implementations. This template receives a map with:
+ ```go
+ map[string]any{
+ "Service": svc.Name,
+ "ClientInterceptors": svc.ClientInterceptors,
+ }
+ ```
+
+## 3. Example Interceptors
+
+### Where They Are Generated
+
+Example interceptors are generated by the example command in an interceptors sub-package of the user’s service package:
+
+* Client interceptors: `_client.go`
+* Server interceptors: `_server.go`
+
+### Templates Used
+
+1. **`example_client_interceptor.go.tpl` and `example_server_interceptor.go.tpl`**
+ Generate example interceptor implementations. Each template takes a map with:
+ ```go
+ map[string]any{
+ "StructName":
+ "ServiceName":
+ "PkgName":
+ "ServerInterceptors":
+ "ClientInterceptors":
+ }
+ ```
+
+2. **`example_service_init.go.tpl`**
+ Generates an example service implementation. This template takes a Data struct.
+
+### Generated Example Features
+
+The example interceptors demonstrate common interception patterns:
+
+* Logging of request/response data
+* Error handling and error logging
+* Type-safe payload and result access through the generated info structs
+* Context propagation across requests and responses
+
+## 4. Data Structures
+
+The templates above share a common set of data structures that describe
+interceptor behavior, payload, result access, and streaming. These data
+structures are defined as follows:
+
+### `Data`
+
+A service-level structure containing information for both client and server interceptors:
+
+* `ServerInterceptors`: A slice of InterceptorData for server-side interceptors
+* `ClientInterceptors`: A slice of InterceptorData for client-side interceptors
+
+### `InterceptorData`
+
+The main structure describing each interceptor’s metadata and requirements:
+
+* `Name`: Generated Go name of the interceptor (CamelCase)
+* `DesignName`: Original name from the design
+* `Description`: Interceptor description from the design
+* `HasPayloadAccess`: Indicates if any method requires payload access
+* `HasResultAccess`: Indicates if any method requires result access
+* `ReadPayload`: List of readable payload fields ([]AttributeData)
+* `WritePayload`: List of writable payload fields ([]AttributeData)
+* `ReadResult`: List of readable result fields ([]AttributeData)
+* `WriteResult`: List of writable result fields ([]AttributeData)
+* `Methods`: A list of MethodInterceptorData containing method-specific interceptor information
+* `ServerStreamInputStruct`: Server stream variable name (used if streaming)
+* `ClientStreamInputStruct`: Client stream variable name (used if streaming)
+
+### `MethodInterceptorData`
+
+Stores per-method interceptor configuration:
+
+* `MethodName`: The method’s Go variable name
+* `PayloadAccess`: Name of the payload access type
+* `ResultAccess`: Name of the result access type
+* `PayloadRef`: Reference to the method's payload type
+* `ResultRef`: Reference to the method's result type
+
+### `AttributeData`
+
+Represents per-field access configuration:
+
+* `Name`: The field accessor method name
+* `TypeRef`: Go type reference for the field
+* `Type`: Underlying attribute type information
diff --git a/codegen/service/interceptors_test.go b/codegen/service/interceptors_test.go
index e484535c9a..6fa7c3ab14 100644
--- a/codegen/service/interceptors_test.go
+++ b/codegen/service/interceptors_test.go
@@ -125,7 +125,7 @@ func TestCollectAttributes(t *testing.T) {
Validation: &expr.ValidationExpr{Required: []string{"name"}},
},
want: []*AttributeData{
- {Name: "Name", TypeRef: "string", FieldPointer: false},
+ {Name: "Name", TypeRef: "string", Pointer: false},
},
},
{
@@ -141,7 +141,7 @@ func TestCollectAttributes(t *testing.T) {
},
},
want: []*AttributeData{
- {Name: "Age", TypeRef: "int", FieldPointer: true},
+ {Name: "Age", TypeRef: "int", Pointer: true},
},
},
{
@@ -160,8 +160,8 @@ func TestCollectAttributes(t *testing.T) {
Validation: &expr.ValidationExpr{Required: []string{"name"}},
},
want: []*AttributeData{
- {Name: "Name", TypeRef: "string", FieldPointer: false},
- {Name: "Age", TypeRef: "int", FieldPointer: true},
+ {Name: "Name", TypeRef: "string", Pointer: false},
+ {Name: "Age", TypeRef: "int", Pointer: true},
},
},
{
diff --git a/codegen/service/service.go b/codegen/service/service.go
index 5fc68eb860..706ea255ad 100644
--- a/codegen/service/service.go
+++ b/codegen/service/service.go
@@ -197,7 +197,7 @@ func Files(genpkg string, service *expr.ServiceExpr, userTypePkgs map[string][]s
// service and client interceptors
files = append(files, InterceptorsFiles(genpkg, service)...)
-
+
// user types
paths := make([]string, len(typeDefSections))
i := 0
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index 9451d05df7..59d7886ea9 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -64,11 +64,11 @@ type (
Methods []*MethodData
// Schemes is the list of security schemes required by the service methods.
Schemes SchemesData
- // ServerInterceptors is the union of the server interceptors defined
- // at the methods, service and the API level.
+ // ServerInterceptors contains the data needed to render the server-side
+ // interceptors code.
ServerInterceptors []*InterceptorData
- // ClientInterceptors is the union of the client interceptors defined
- // at the methods, service and the API level.
+ // ClientInterceptors contains the data needed to render the client-side
+ // interceptors code.
ClientInterceptors []*InterceptorData
// Scope initialized with all the service types.
Scope *codegen.NameScope
@@ -158,10 +158,10 @@ type (
Schemes SchemesData
// ServerInterceptors list the server interceptors that apply to this
// method.
- ServerInterceptors []*InterceptorData
+ ServerInterceptors []string
// ClientInterceptors list the client interceptors that apply to this
// method.
- ClientInterceptors []*InterceptorData
+ ClientInterceptors []string
// ViewedResult contains the data required to generate the code handling
// views if any.
ViewedResult *ViewedResultTypeData
@@ -247,41 +247,56 @@ type (
Fault bool
}
- // InterceptorData describes a single interceptor that can read and write
- // payload and result attributes.
+ // InterceptorData contains the data required to render the service-level
+ // interceptor code. interceptors.go.tpl
InterceptorData struct {
// Name is the name of the interceptor used in the generated code.
Name string
// DesignName is the name of the interceptor as defined in the design.
DesignName string
- // UnexportedName is the private version of the interceptor name used in
- // generated code.
- UnexportedName string
// Description is the description of the interceptor from the design.
Description string
- // PayloadRef is the reference to the payload type that the interceptor
- // can access.
- PayloadRef string
- // ResultRef is the reference to the result type that the interceptor
- // can access.
- ResultRef string
- // ReadPayload lists the payload attributes that the interceptor can
- // read. These are defined using ReadPayload() in the design DSL.
+ // Methods
+ Methods []*MethodInterceptorData
+ // ReadPayload contains payload attributes that the interceptor can
+ // read.
ReadPayload []*AttributeData
- // WritePayload lists the payload attributes that the interceptor can
- // write. These are defined using WritePayload() in the design DSL.
+ // WritePayload contains payload attributes that the interceptor can
+ // write.
WritePayload []*AttributeData
- // ReadResult lists the result attributes that the interceptor can read.
- // These are defined using ReadResult() in the design DSL.
+ // ReadResult contains result attributes that the interceptor can read.
ReadResult []*AttributeData
- // WriteResult lists the result attributes that the interceptor can
- // write. These are defined using WriteResult() in the design DSL.
+ // WriteResult contains result attributes that the interceptor can
+ // write.
WriteResult []*AttributeData
- // ServerStreamInputStruct is the name of the server stream input struct
- // type used when the interceptor is applied to a streaming endpoint.
+ // HasPayloadAccess indicates that the interceptor info object has a
+ // payload access interface.
+ HasPayloadAccess bool
+ // HasResultAccess indicates that the interceptor info object has a
+ // result access interface.
+ HasResultAccess bool
+ }
+
+ // MethodInterceptorData contains the data required to render the
+ // method-level interceptor code.
+ MethodInterceptorData struct {
+ // Name is the name of the interceptor.
+ Name string
+ // MethodName is the name of the method.
+ MethodName string
+ // PayloadAccess is the name of the payload access struct.
+ PayloadAccess string
+ // ResultAccess is the name of the result access struct.
+ ResultAccess string
+ // PayloadRef is the reference to the method payload type.
+ PayloadRef string
+ // ResultRef is the reference to the method result type.
+ ResultRef string
+ // ServerStreamInputStruct is the name of the server stream input
+ // struct if the endpoint defines a server stream.
ServerStreamInputStruct string
- // ClientStreamInputStruct is the name of the client stream input struct
- // type used when the interceptor is applied to a streaming endpoint.
+ // ClientStreamInputStruct is the name of the client stream input
+ // struct if the endpoint defines a client stream.
ClientStreamInputStruct string
}
@@ -291,8 +306,8 @@ type (
Name string
// TypeRef is the reference to the attribute type.
TypeRef string
- // FieldPointer is true if the attribute is a pointer.
- FieldPointer bool
+ // Pointer is true if the attribute is a pointer.
+ Pointer bool
}
// RequirementsData is the list of security requirements.
@@ -806,17 +821,6 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
}
}
- var (
- serverInterceptors []*InterceptorData
- clientInterceptors []*InterceptorData
- seenServer = make(map[string]struct{})
- seenClient = make(map[string]struct{})
- )
- for _, m := range methods {
- serverInterceptors = append(serverInterceptors, collectInterceptors(m.ServerInterceptors, seenServer)...)
- clientInterceptors = append(clientInterceptors, collectInterceptors(m.ClientInterceptors, seenClient)...)
- }
-
var (
desc string
)
@@ -840,8 +844,8 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
ViewsPkg: viewspkg,
Methods: methods,
Schemes: schemes,
- ServerInterceptors: serverInterceptors,
- ClientInterceptors: clientInterceptors,
+ ServerInterceptors: collectInterceptors(service, methods, scope, true),
+ ClientInterceptors: collectInterceptors(service, methods, scope, false),
Scope: scope,
ViewScope: viewScope,
errorTypes: errTypes,
@@ -852,11 +856,47 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
viewedResultTypes: viewedRTs,
unionValueMethods: unionMethods,
}
+
d[service.Name] = data
return data
}
+// collectInterceptors returns the set of interceptors defined on the given
+// service including any interceptor defined on specific service methods or API.
+func collectInterceptors(svc *expr.ServiceExpr, methods []*MethodData, scope *codegen.NameScope, server bool) []*InterceptorData {
+ var ints []*expr.InterceptorExpr
+ if server {
+ ints = expr.Root.API.ServerInterceptors
+ ints = append(ints, svc.ServerInterceptors...)
+ for _, m := range svc.Methods {
+ ints = append(ints, m.ServerInterceptors...)
+ }
+ } else {
+ ints = expr.Root.API.ClientInterceptors
+ ints = append(ints, svc.ClientInterceptors...)
+ for _, m := range svc.Methods {
+ ints = append(ints, m.ClientInterceptors...)
+ }
+ }
+ // remove duplicate interceptors
+ sort.Slice(ints, func(i, j int) bool {
+ return ints[i].Name < ints[j].Name
+ })
+ for i := 1; i < len(ints); i++ {
+ if ints[i-1].Name == ints[i].Name {
+ ints = append(ints[:i], ints[i+1:]...)
+ i--
+ }
+ }
+
+ res := make([]*InterceptorData, 0, len(ints))
+ for _, i := range ints {
+ res = append(res, buildInterceptorData(svc, methods, i, scope, server))
+ }
+ return res
+}
+
// typeContext returns a contextual attribute for service types. Service types
// are Go types and uses non-pointers to hold attributes having default values.
func typeContext(pkg string, scope *codegen.NameScope) *codegen.AttributeContext {
@@ -949,18 +989,6 @@ func collectUnionMethods(att *expr.AttributeExpr, scope *codegen.NameScope, loc
return
}
-// collectInterceptors traverses the interceptors to build a unique set.
-func collectInterceptors(interceptors []*InterceptorData, seen map[string]struct{}) (data []*InterceptorData) {
- for _, i := range interceptors {
- if _, ok := seen[i.Name]; ok {
- continue
- }
- seen[i.Name] = struct{}{}
- data = append(data, i)
- }
- return
-}
-
// buildErrorInitData creates the data needed to generate code around endpoint error return values.
func buildErrorInitData(er *expr.ErrorExpr, scope *codegen.NameScope) *ErrorInitData {
_, temporary := er.AttributeExpr.Meta["goa:error:temporary"]
@@ -1087,7 +1115,6 @@ func buildMethodData(m *expr.MethodExpr, scope *codegen.NameScope) *MethodData {
ResponseStruct: vname + "ResponseData",
}
initStreamData(data, m, vname, rname, resultRef, scope)
- initInterceptorsData(data, m, payloadRef, resultRef, scope)
return data
}
@@ -1169,38 +1196,77 @@ func initStreamData(data *MethodData, m *expr.MethodExpr, vname, rname, resultRe
data.StreamingPayloadEx = spayloadEx
}
-func initInterceptorsData(data *MethodData, m *expr.MethodExpr, payloadRef, resultRef string, scope *codegen.NameScope) {
- if len(m.ServerInterceptors) == 0 && len(m.ClientInterceptors) == 0 {
- return
- }
- buildInterceptorData := func(i *expr.InterceptorExpr, serverStreamStruct, clientStreamStruct string, scope *codegen.NameScope) *InterceptorData {
- return &InterceptorData{
- Name: codegen.Goify(i.Name, true),
- DesignName: i.Name,
- UnexportedName: codegen.Goify(i.Name, false),
- Description: i.Description,
- PayloadRef: payloadRef,
- ResultRef: resultRef,
- ServerStreamInputStruct: serverStreamStruct,
- ClientStreamInputStruct: clientStreamStruct,
- ReadPayload: collectAttributes(i.ReadPayload, m.Payload, scope),
- WritePayload: collectAttributes(i.WritePayload, m.Payload, scope),
- ReadResult: collectAttributes(i.ReadResult, m.Result, scope),
- WriteResult: collectAttributes(i.WriteResult, m.Result, scope),
+// buildInterceptorData creates the data needed to generate interceptor code.
+func buildInterceptorData(svc *expr.ServiceExpr, methods []*MethodData, i *expr.InterceptorExpr, scope *codegen.NameScope, server bool) *InterceptorData {
+ data := &InterceptorData{
+ Name: codegen.Goify(i.Name, true),
+ DesignName: i.Name,
+ Description: i.Description,
+ }
+ if len(svc.Methods) > 0 {
+ payload, result := svc.Methods[0].Payload, svc.Methods[0].Result
+ data.ReadPayload = collectAttributes(i.ReadPayload, payload, scope)
+ data.WritePayload = collectAttributes(i.WritePayload, payload, scope)
+ data.ReadResult = collectAttributes(i.ReadResult, result, scope)
+ data.WriteResult = collectAttributes(i.WriteResult, result, scope)
+ if len(data.ReadPayload) > 0 || len(data.WritePayload) > 0 {
+ data.HasPayloadAccess = true
+ }
+ if len(data.ReadResult) > 0 || len(data.WriteResult) > 0 {
+ data.HasResultAccess = true
+ }
+ for _, m := range svc.Methods {
+ applies := false
+ intExprs := m.ServerInterceptors
+ if !server {
+ intExprs = m.ClientInterceptors
+ }
+ for _, in := range intExprs {
+ if in.Name == i.Name {
+ applies = true
+ break
+ }
+ }
+ if !applies {
+ continue
+ }
+ var md *MethodData
+ for _, mt := range methods {
+ if m.Name == mt.Name {
+ md = mt
+ break
+ }
+ }
+ data.Methods = append(data.Methods, buildInterceptorMethodData(i, md))
+ if server {
+ md.ServerInterceptors = append(md.ServerInterceptors, i.Name)
+ } else {
+ md.ClientInterceptors = append(md.ClientInterceptors, i.Name)
+ }
}
}
- var serverEndpointStruct, clientEndpointStruct string
- if data.ServerStream != nil {
- serverEndpointStruct = data.ServerStream.EndpointStruct
- }
- if data.ClientStream != nil {
- clientEndpointStruct = data.ClientStream.EndpointStruct
- }
- for _, i := range m.ServerInterceptors {
- data.ServerInterceptors = append(data.ServerInterceptors, buildInterceptorData(i, serverEndpointStruct, clientEndpointStruct, scope))
- }
- for _, i := range m.ClientInterceptors {
- data.ClientInterceptors = append(data.ClientInterceptors, buildInterceptorData(i, serverEndpointStruct, clientEndpointStruct, scope))
+ return data
+}
+
+// buildIntercetorMethodData creates the data needed to generate interceptor
+// method code.
+func buildInterceptorMethodData(i *expr.InterceptorExpr, md *MethodData) *MethodInterceptorData {
+ var serverStream, clientStream string
+ if md.ServerStream != nil {
+ serverStream = md.ServerStream.VarName
+ }
+ if md.ClientStream != nil {
+ clientStream = md.ClientStream.VarName
+ }
+ return &MethodInterceptorData{
+ Name: i.Name,
+ MethodName: md.VarName,
+ PayloadAccess: codegen.Goify(i.Name, false) + md.VarName + "Payload",
+ ResultAccess: codegen.Goify(i.Name, false) + md.VarName + "Result",
+ PayloadRef: md.PayloadRef,
+ ResultRef: md.ResultRef,
+ ClientStreamInputStruct: clientStream,
+ ServerStreamInputStruct: serverStream,
}
}
@@ -1322,9 +1388,9 @@ func collectAttributes(attrNames, parent *expr.AttributeExpr, scope *codegen.Nam
continue
}
data[i] = &AttributeData{
- Name: codegen.Goify(nat.Name, true),
- TypeRef: scope.GoTypeRef(parentAttr),
- FieldPointer: parent.IsPrimitivePointer(nat.Name, true),
+ Name: codegen.Goify(nat.Name, true),
+ TypeRef: scope.GoTypeRef(parentAttr),
+ Pointer: parent.IsPrimitivePointer(nat.Name, true),
}
}
return data
diff --git a/codegen/service/templates/client_interceptor_wrappers.go.tpl b/codegen/service/templates/client_interceptor_wrappers.go.tpl
index f2f63223db..5cd5f9d9d1 100644
--- a/codegen/service/templates/client_interceptor_wrappers.go.tpl
+++ b/codegen/service/templates/client_interceptor_wrappers.go.tpl
@@ -1,10 +1,13 @@
{{- range .ClientInterceptors }}
-{{ comment (printf "wrapClient%s applies the %s interceptor to endpoints." .Name .DesignName) }}
-func wrapClient{{ .Name }}(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+{{- $interceptor := . }}
+{{- range .Methods }}
+
+{{ comment (printf "wrapClient%s%s applies the %s client interceptor to endpoints." $interceptor.Name .MethodName $interceptor.DesignName) }}
+func wrapClient{{ .MethodName }}{{ $interceptor.Name }}(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
- info := &{{ .Name }}Info{
+ info := &{{ $interceptor.Name }}Info{
Service: "{{ $.Service }}",
- Method: method,
+ Method: "{{ .MethodName }}",
Endpoint: endpoint,
{{- if .ClientStreamInputStruct }}
RawPayload: req.(*{{ .ClientStreamInputStruct }}).Payload,
@@ -15,4 +18,5 @@ func wrapClient{{ .Name }}(endpoint goa.Endpoint, i ClientInterceptors, method s
return i.{{ .Name }}(ctx, info, endpoint)
}
}
+{{ end }}
{{- end }}
diff --git a/codegen/service/templates/client_wrappers.go.tpl b/codegen/service/templates/client_wrappers.go.tpl
index 6b05597c00..9287d25b35 100644
--- a/codegen/service/templates/client_wrappers.go.tpl
+++ b/codegen/service/templates/client_wrappers.go.tpl
@@ -1,7 +1,8 @@
+
{{ comment (printf "Wrap%sClientEndpoint wraps the %s endpoint with the client interceptors defined in the design." .MethodVarName .Method) }}
func Wrap{{ .MethodVarName }}ClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- {{- range .ClientInterceptors }}
- endpoint = wrapClient{{ .Name }}(endpoint, i, "{{ $.Method }}")
+ {{- range .Interceptors }}
+ endpoint = wrapClient{{ $.MethodVarName }}{{ . }}(endpoint, i)
{{- end }}
return endpoint
}
diff --git a/codegen/service/templates/endpoint_wrappers.go.tpl b/codegen/service/templates/endpoint_wrappers.go.tpl
index 8c82741cc9..deffd14fc9 100644
--- a/codegen/service/templates/endpoint_wrappers.go.tpl
+++ b/codegen/service/templates/endpoint_wrappers.go.tpl
@@ -1,7 +1,7 @@
{{ comment (printf "Wrap%sEndpoint wraps the %s endpoint with the server-side interceptors defined in the design." .MethodVarName .Method) }}
func Wrap{{ .MethodVarName }}Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- {{- range .ServerInterceptors }}
- endpoint = wrap{{ .Name }}(endpoint, i, "{{ $.Method }}")
+ {{- range .Interceptors }}
+ endpoint = wrap{{ $.MethodVarName }}{{ . }}(endpoint, i)
{{- end }}
return endpoint
}
diff --git a/codegen/service/templates/interceptors.go.tpl b/codegen/service/templates/interceptors.go.tpl
index 5f2024cf33..ea3b0b5e86 100644
--- a/codegen/service/templates/interceptors.go.tpl
+++ b/codegen/service/templates/interceptors.go.tpl
@@ -1,128 +1,96 @@
-
-// Access interfaces for interceptor payloads and results
-type (
-{{- range . }}
- // {{ .Name }}Info provides metadata about the current interception.
- // It includes service name, method name, and access to the endpoint.
- {{ .Name }}Info goa.InterceptorInfo
- {{- if or .ReadPayload .WritePayload }}
-
- // {{ .Name }}PayloadAccess provides type-safe access to the method payload.
- // It allows reading and writing specific fields of the payload as defined
- // in the design.
- {{ .Name }}PayloadAccess interface {
- {{- range .ReadPayload }}
- {{ .Name }}() {{ .TypeRef }}
- {{- end }}
- {{- range .WritePayload }}
- Set{{ .Name }}({{ .TypeRef }})
- {{- end }}
- }
- {{- end }}
- {{- if or .ReadResult .WriteResult }}
-
- // {{ .Name }}ResultAccess provides type-safe access to the method result.
- // It allows reading and writing specific fields of the result as defined
- // in the design.
- {{ .Name }}ResultAccess interface {
- {{- range .ReadResult }}
- {{ .Name }}() {{ .TypeRef }}
- {{- end }}
- {{- range .WriteResult }}
- Set{{ .Name }}({{ .TypeRef }})
- {{- end }}
- }
- {{- end }}
-{{- end }}
-)
-
{{- if hasPrivateImplementationTypes . }}
-
-// Private implementation types
-type (
- {{- range . }}
- {{- if or .ReadPayload .WritePayload }}
- {{ .UnexportedName }}PayloadAccess struct {
- payload {{ .PayloadRef }}
- }
- {{- end }}
-
- {{- if or .ReadResult .WriteResult }}
- {{ .UnexportedName }}ResultAccess struct {
- result {{ .ResultRef }}
- }
- {{- end }}
- {{- end }}
-)
-
// Public accessor methods for Info types
{{- range . }}
- {{- if or .ReadPayload .WritePayload }}
+ {{- if .HasPayloadAccess }}
+
// Payload returns a type-safe accessor for the method payload.
-func (info *{{ .Name }}Info) Payload() {{ .Name }}PayloadAccess {
- return &{{ .UnexportedName }}PayloadAccess{payload: info.RawPayload.({{ .PayloadRef }})}
+func (info *{{ .Name }}Info) Payload() {{ .Name }}Payload {
+ {{- if gt (len .Methods) 1 }}
+ switch info.Method {
+ {{- range .Methods }}
+ case "{{ .MethodName }}":
+ return &{{ .PayloadAccess }}{payload: info.RawPayload.({{ .PayloadRef }})}
+ {{- end }}
+ default:
+ return nil
+ }
+ {{- else }}
+ return &{{ (index .Methods 0).PayloadAccess }}{payload: info.RawPayload.({{ (index .Methods 0).PayloadRef }})}
+ {{- end }}
}
{{- end }}
- {{- if or .ReadResult .WriteResult }}
+ {{- if .HasResultAccess }}
// Result returns a type-safe accessor for the method result.
-func (info *{{ .Name }}Info) Result(res any) {{ .Name }}ResultAccess {
- return &{{ .UnexportedName }}ResultAccess{result: res.({{ .ResultRef }})}
+func (info *{{ .Name }}Info) Result(res any) {{ .Name }}Result {
+ {{- if gt (len .Methods) 1 }}
+ switch info.Method {
+ {{- range .Methods }}
+ case "{{ .MethodName }}":
+ return &{{ .ResultAccess }}{result: res.({{ .ResultRef }})}
+ {{- end }}
+ default:
+ return nil
+ }
+ {{- else }}
+ return &{{ (index .Methods 0).ResultAccess }}{result: res.({{ (index .Methods 0).ResultRef }})}
+ {{- end }}
}
{{- end }}
{{- end }}
// Private implementation methods
{{- range . }}
- {{- $interceptor := . }}
- {{- range .ReadPayload }}
-func (p *{{ $interceptor.UnexportedName }}PayloadAccess) {{ .Name }}() {{ .TypeRef }} {
- {{- if .FieldPointer }}
+ {{ $interceptor := . }}
+ {{- range .Methods }}
+ {{- $method := . }}
+ {{- range $interceptor.ReadPayload }}
+func (p *{{ $method.PayloadAccess }}) {{ .Name }}() {{ .TypeRef }} {
+ {{- if .Pointer }}
if p.payload.{{ .Name }} == nil {
var zero {{ .TypeRef }}
return zero
}
return *p.payload.{{ .Name }}
- {{- else }}
+ {{- else }}
return p.payload.{{ .Name }}
- {{- end }}
+ {{- end }}
}
- {{- end }}
+ {{- end }}
- {{- range .WritePayload }}
-func (p *{{ $interceptor.UnexportedName }}PayloadAccess) Set{{ .Name }}(v {{ .TypeRef }}) {
- {{- if .FieldPointer }}
+ {{- range $interceptor.WritePayload }}
+func (p *{{ $method.PayloadAccess }}) Set{{ .Name }}(v {{ .TypeRef }}) {
+ {{- if .Pointer }}
p.payload.{{ .Name }} = &v
- {{- else }}
+ {{- else }}
p.payload.{{ .Name }} = v
- {{- end }}
+ {{- end }}
}
- {{- end }}
+ {{- end }}
- {{- range .ReadResult }}
-func (r *{{ $interceptor.UnexportedName }}ResultAccess) {{ .Name }}() {{ .TypeRef }} {
- {{- if .FieldPointer }}
+ {{- range $interceptor.ReadResult }}
+func (r *{{ $method.ResultAccess }}) {{ .Name }}() {{ .TypeRef }} {
+ {{- if .Pointer }}
if r.result.{{ .Name }} == nil {
var zero {{ .TypeRef }}
return zero
}
return *r.result.{{ .Name }}
- {{- else }}
+ {{- else }}
return r.result.{{ .Name }}
- {{- end }}
+ {{- end }}
}
- {{- end }}
+ {{- end }}
- {{- range .WriteResult }}
-func (r *{{ $interceptor.UnexportedName }}ResultAccess) Set{{ .Name }}(v {{ .TypeRef }}) {
- {{- if .FieldPointer }}
+ {{- range $interceptor.WriteResult }}
+func (r *{{ $method.ResultAccess }}) Set{{ .Name }}(v {{ .TypeRef }}) {
+ {{- if .Pointer }}
r.result.{{ .Name }} = &v
- {{- else }}
+ {{- else }}
r.result.{{ .Name }} = v
- {{- end }}
+ {{- end }}
}
+ {{- end }}
{{- end }}
{{- end }}
{{- end }}
-
-
diff --git a/codegen/service/templates/interceptors_types.go.tpl b/codegen/service/templates/interceptors_types.go.tpl
new file mode 100644
index 0000000000..73a159d170
--- /dev/null
+++ b/codegen/service/templates/interceptors_types.go.tpl
@@ -0,0 +1,62 @@
+
+// Access interfaces for interceptor payloads and results
+type (
+{{- range . }}
+ // {{ .Name }}Info provides metadata about the current interception.
+ // It includes service name, method name, and access to the endpoint.
+ {{ .Name }}Info goa.InterceptorInfo
+ {{- if .HasPayloadAccess }}
+
+ // {{ .Name }}Payload provides type-safe access to the method payload.
+ // It allows reading and writing specific fields of the payload as defined
+ // in the design.
+ {{ .Name }}Payload interface {
+ {{- range .ReadPayload }}
+ {{ .Name }}() {{ .TypeRef }}
+ {{- end }}
+ {{- range .WritePayload }}
+ Set{{ .Name }}({{ .TypeRef }})
+ {{- end }}
+ }
+ {{- end }}
+ {{- if .HasResultAccess }}
+
+ // {{ .Name }}Result provides type-safe access to the method result.
+ // It allows reading and writing specific fields of the result as defined
+ // in the design.
+ {{ .Name }}Result interface {
+ {{- range .ReadResult }}
+ {{ .Name }}() {{ .TypeRef }}
+ {{- end }}
+ {{- range .WriteResult }}
+ Set{{ .Name }}({{ .TypeRef }})
+ {{- end }}
+ }
+ {{- end }}
+{{- end }}
+)
+{{- if hasPrivateImplementationTypes . }}
+
+// Private implementation types
+type (
+ {{- range . }}
+ {{- range .Methods }}
+ {{- if .PayloadAccess }}
+ {{ .PayloadAccess }} struct {
+ payload {{ .PayloadRef }}
+ }
+ {{- end }}
+ {{- end }}
+ {{- end }}
+
+ {{- range . }}
+ {{- range .Methods }}
+ {{- if .ResultAccess }}
+ {{ .ResultAccess }} struct {
+ result {{ .ResultRef }}
+ }
+ {{- end }}
+ {{- end }}
+ {{- end }}
+)
+{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/server_interceptor_wrappers.go.tpl b/codegen/service/templates/server_interceptor_wrappers.go.tpl
index 4971d6510e..3086dae3cf 100644
--- a/codegen/service/templates/server_interceptor_wrappers.go.tpl
+++ b/codegen/service/templates/server_interceptor_wrappers.go.tpl
@@ -1,10 +1,13 @@
{{- range .ServerInterceptors }}
-{{ comment (printf "wrap%s applies the %s interceptor to endpoints." .Name .DesignName) }}
-func wrap{{ .Name }}(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+{{- $interceptor := . }}
+{{- range .Methods }}
+
+{{ comment (printf "wrap%s%s applies the %s server interceptor to endpoints." $interceptor.Name .MethodName $interceptor.DesignName) }}
+func wrap{{ .MethodName }}{{ $interceptor.Name }}(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
- info := &{{ .Name }}Info{
+ info := &{{ $interceptor.Name }}Info{
Service: "{{ $.Service }}",
- Method: method,
+ Method: "{{ .MethodName }}",
Endpoint: endpoint,
{{- if .ServerStreamInputStruct }}
RawPayload: req.(*{{ .ServerStreamInputStruct }}).Payload,
@@ -16,3 +19,4 @@ func wrap{{ .Name }}(endpoint goa.Endpoint, i ServerInterceptors, method string)
}
}
{{- end }}
+{{- end }}
\ No newline at end of file
diff --git a/codegen/service/templates/server_interceptors.go.tpl b/codegen/service/templates/server_interceptors.go.tpl
index c19597a383..cdfb9287ec 100644
--- a/codegen/service/templates/server_interceptors.go.tpl
+++ b/codegen/service/templates/server_interceptors.go.tpl
@@ -4,9 +4,9 @@
// next to complete the request.
type ServerInterceptors interface {
{{- range .ServerInterceptors }}
-{{- if .Description }}
+ {{- if .Description }}
{{ comment .Description }}
-{{- end }}
+ {{- end }}
{{ .Name }}(ctx context.Context, info *{{ .Name }}Info, next goa.Endpoint) (any, error)
{{- end }}
}
\ No newline at end of file
diff --git a/dsl/interceptor.go b/dsl/interceptor.go
index 28b99ce7dd..fdb42fa1b8 100644
--- a/dsl/interceptor.go
+++ b/dsl/interceptor.go
@@ -251,6 +251,10 @@ func addInterceptors(interceptors []any, client bool) {
case *expr.InterceptorExpr:
ints = append(ints, i)
case string:
+ if i == "" {
+ eval.ReportError("%s: interceptor name cannot be empty", kind)
+ return
+ }
var found bool
for _, in := range expr.Root.Interceptors {
if in.Name == i {
diff --git a/expr/method.go b/expr/method.go
index 94e3e59356..0183545e0e 100644
--- a/expr/method.go
+++ b/expr/method.go
@@ -100,39 +100,6 @@ func (m *MethodExpr) Prepare() {
if m.Result == nil {
m.Result = &AttributeExpr{Type: Empty}
}
-
- m.ClientInterceptors = mergeInterceptors(m.ClientInterceptors, m.Service.ClientInterceptors, Root.API.ClientInterceptors)
- m.ServerInterceptors = mergeInterceptors(m.ServerInterceptors, m.Service.ServerInterceptors, Root.API.ServerInterceptors)
-}
-
-// mergeInterceptors merges interceptors from different levels (method, service, API)
-// while avoiding duplicates. The order of precedence is: method > service > API.
-func mergeInterceptors(methodLevel, serviceLevel, apiLevel []*InterceptorExpr) []*InterceptorExpr {
- existing := make(map[string]struct{})
- result := make([]*InterceptorExpr, 0, len(methodLevel)+len(serviceLevel)+len(apiLevel))
-
- // Add method-level interceptors
- for _, i := range methodLevel {
- existing[i.Name] = struct{}{}
- result = append(result, i)
- }
-
- // Add service-level interceptors
- for _, i := range serviceLevel {
- if _, ok := existing[i.Name]; !ok {
- result = append(result, i)
- existing[i.Name] = struct{}{}
- }
- }
-
- // Add API-level interceptors
- for _, i := range apiLevel {
- if _, ok := existing[i.Name]; !ok {
- result = append(result, i)
- }
- }
-
- return result
}
// Validate validates the method payloads, results, errors, security
@@ -272,15 +239,41 @@ func (m *MethodExpr) validateErrors() *eval.ValidationErrors {
// validateInterceptors validates the method interceptors.
func (m *MethodExpr) validateInterceptors() *eval.ValidationErrors {
verr := new(eval.ValidationErrors)
+ m.ClientInterceptors = mergeInterceptors(m.ClientInterceptors, m.Service.ClientInterceptors, Root.API.ClientInterceptors)
for _, i := range m.ClientInterceptors {
verr.Merge(i.validate(m))
}
+ m.ServerInterceptors = mergeInterceptors(m.ServerInterceptors, m.Service.ServerInterceptors, Root.API.ServerInterceptors)
for _, i := range m.ServerInterceptors {
verr.Merge(i.validate(m))
}
return verr
}
+// mergeInterceptors merges interceptors from different levels (method, service, API)
+// while avoiding duplicates. The order of precedence is: method > service > API.
+func mergeInterceptors(methodLevel, serviceLevel, apiLevel []*InterceptorExpr) []*InterceptorExpr {
+ existing := make(map[string]struct{})
+ result := make([]*InterceptorExpr, 0, len(methodLevel)+len(serviceLevel)+len(apiLevel))
+
+ for _, i := range methodLevel {
+ existing[i.Name] = struct{}{}
+ result = append(result, i)
+ }
+ for _, i := range serviceLevel {
+ if _, ok := existing[i.Name]; !ok {
+ result = append(result, i)
+ existing[i.Name] = struct{}{}
+ }
+ }
+ for _, i := range apiLevel {
+ if _, ok := existing[i.Name]; !ok {
+ result = append(result, i)
+ }
+ }
+ return result
+}
+
// hasTag is a helper function that traverses the given attribute and all its
// bases recursively looking for an attribute with the given tag meta. This
// recursion is only needed for attributes that have not been finalized yet.
diff --git a/expr/method_test.go b/expr/method_test.go
index d23d58ddc7..6d57459665 100644
--- a/expr/method_test.go
+++ b/expr/method_test.go
@@ -155,102 +155,30 @@ func TestMethodExprIsPayloadStreaming(t *testing.T) {
}
}
-func TestMethodExprPrepare(t *testing.T) {
- var (
- apiInterceptor = &expr.InterceptorExpr{Name: "api"}
- svcInterceptor = &expr.InterceptorExpr{Name: "service"}
- mtdInterceptor = &expr.InterceptorExpr{Name: "method"}
- )
-
- cases := map[string]struct {
- api *expr.APIExpr
- service *expr.ServiceExpr
- method *expr.MethodExpr
- expectedServer []*expr.InterceptorExpr
- expectedClient []*expr.InterceptorExpr
+func TestMethodExprValidateInterceptors(t *testing.T) {
+ cases := []struct {
+ Name string
+ DSL func()
+ Error string
}{
- "no-interceptors": {
- api: &expr.APIExpr{},
- service: &expr.ServiceExpr{},
- method: &expr.MethodExpr{},
- expectedServer: []*expr.InterceptorExpr{},
- expectedClient: []*expr.InterceptorExpr{},
- },
- "api-only": {
- api: &expr.APIExpr{
- ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- },
- service: &expr.ServiceExpr{},
- method: &expr.MethodExpr{},
- expectedServer: []*expr.InterceptorExpr{apiInterceptor},
- expectedClient: []*expr.InterceptorExpr{apiInterceptor},
- },
- "service-only": {
- api: &expr.APIExpr{},
- service: &expr.ServiceExpr{
- ServerInterceptors: []*expr.InterceptorExpr{svcInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{svcInterceptor},
- },
- method: &expr.MethodExpr{},
- expectedServer: []*expr.InterceptorExpr{svcInterceptor},
- expectedClient: []*expr.InterceptorExpr{svcInterceptor},
- },
- "method-only": {
- api: &expr.APIExpr{},
- service: &expr.ServiceExpr{},
- method: &expr.MethodExpr{
- ServerInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
- },
- expectedServer: []*expr.InterceptorExpr{mtdInterceptor},
- expectedClient: []*expr.InterceptorExpr{mtdInterceptor},
- },
- "chained-interceptors": {
- api: &expr.APIExpr{
- ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- },
- service: &expr.ServiceExpr{
- ServerInterceptors: []*expr.InterceptorExpr{svcInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{svcInterceptor},
- },
- method: &expr.MethodExpr{
- ServerInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{mtdInterceptor},
- },
- expectedServer: []*expr.InterceptorExpr{mtdInterceptor, svcInterceptor, apiInterceptor},
- expectedClient: []*expr.InterceptorExpr{mtdInterceptor, svcInterceptor, apiInterceptor},
- },
- "duplicate-interceptors": {
- api: &expr.APIExpr{
- ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- },
- service: &expr.ServiceExpr{
- ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor}, // Same as API
- ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- },
- method: &expr.MethodExpr{
- ServerInterceptors: []*expr.InterceptorExpr{apiInterceptor}, // Same as API
- ClientInterceptors: []*expr.InterceptorExpr{apiInterceptor},
- },
- expectedServer: []*expr.InterceptorExpr{apiInterceptor}, // Only one copy
- expectedClient: []*expr.InterceptorExpr{apiInterceptor},
- },
+ {"no-interceptors", testdata.NoInterceptorsDSL, ""},
+ {"valid-interceptors", testdata.ValidInterceptorsDSL, ""},
+ {"duplicate-interceptors", testdata.DuplicateInterceptorsDSL, ""}, // Duplicates are handled by merging
+ {"mixed-interceptors", testdata.MixedInterceptorsDSL, ""},
+ {"undefined-interceptor", testdata.UndefinedInterceptorDSL,
+ `ServerInterceptor: interceptor "undefined" not found in service "Service" method "Method"`},
+ {"empty-interceptor-name", testdata.EmptyInterceptorNameDSL,
+ `ServerInterceptor: interceptor name cannot be empty`},
}
- for name, tc := range cases {
- t.Run(name, func(t *testing.T) {
- expr.Root.API = tc.api
- tc.method.Service = tc.service
- tc.method.Prepare()
-
- assert.Equal(t, tc.expectedServer, tc.method.ServerInterceptors)
- assert.Equal(t, tc.expectedClient, tc.method.ClientInterceptors)
- assert.NotNil(t, tc.method.Payload)
- assert.NotNil(t, tc.method.StreamingPayload)
- assert.NotNil(t, tc.method.Result)
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ if c.Error == "" {
+ expr.RunDSL(t, c.DSL)
+ return
+ }
+ err := expr.RunInvalidDSL(t, c.DSL)
+ assert.Contains(t, err.Error(), c.Error)
})
}
}
diff --git a/expr/testdata/interceptors_validate_dsls.go b/expr/testdata/interceptors_validate_dsls.go
new file mode 100644
index 0000000000..32d1d99d76
--- /dev/null
+++ b/expr/testdata/interceptors_validate_dsls.go
@@ -0,0 +1,83 @@
+package testdata
+
+import . "goa.design/goa/v3/dsl"
+
+var NoInterceptorsDSL = func() {
+ Service("Service", func() {
+ Method("Method", func() {
+ HTTP(func() {
+ GET("/")
+ })
+ })
+ })
+}
+
+var ValidInterceptorsDSL = func() {
+ Interceptor("api", func() {})
+ API("API", func() {
+ ServerInterceptor("api")
+ })
+
+ Service("Service", func() {
+ Interceptor("service", func() {})
+ ServerInterceptor("service")
+
+ Method("Method", func() {
+ Interceptor("method", func() {})
+ ServerInterceptor("method")
+ })
+ })
+}
+
+var DuplicateInterceptorsDSL = func() {
+ Interceptor("duplicate", func() {})
+ API("API", func() {
+ ServerInterceptor("duplicate")
+ })
+
+ Service("Service", func() {
+ ServerInterceptor("duplicate")
+ Method("Method", func() {
+ ServerInterceptor("duplicate")
+ })
+ })
+}
+
+var MixedInterceptorsDSL = func() {
+ Interceptor("api", func() {})
+ Interceptor("api-client", func() {})
+ API("API", func() {
+ ServerInterceptor("api")
+ ClientInterceptor("api-client")
+ })
+
+ Service("Service", func() {
+ Interceptor("service", func() {})
+ Interceptor("service-client", func() {})
+ ServerInterceptor("service")
+ ClientInterceptor("service-client")
+
+ Method("Method", func() {
+ Interceptor("method", func() {})
+ Interceptor("method-client", func() {})
+ ServerInterceptor("method")
+ ClientInterceptor("method-client")
+ })
+ })
+}
+
+var UndefinedInterceptorDSL = func() {
+ Service("Service", func() {
+ Method("Method", func() {
+ ServerInterceptor("undefined")
+ })
+ })
+}
+
+var EmptyInterceptorNameDSL = func() {
+ Service("Service", func() {
+ Method("Method", func() {
+ ServerInterceptor("")
+ })
+ })
+}
diff --git a/grpc/codegen/client.go b/grpc/codegen/client.go
index fd77174a32..f6669772f2 100644
--- a/grpc/codegen/client.go
+++ b/grpc/codegen/client.go
@@ -65,7 +65,7 @@ func client(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
}
}
sections = append(sections, &codegen.SectionTemplate{
- Name: "client-init",
+ Name: "grpc-client-init",
Source: readTemplate("client_init"),
Data: data,
})
diff --git a/grpc/codegen/client_cli.go b/grpc/codegen/client_cli.go
index 275828a953..1135455c3c 100644
--- a/grpc/codegen/client_cli.go
+++ b/grpc/codegen/client_cli.go
@@ -80,6 +80,13 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
Name: svcName + pbPkgName,
})
specs = append(specs, sd.Service.UserTypeImports...)
+ // Add interceptors import if service has client interceptors
+ if len(sd.Service.ClientInterceptors) > 0 {
+ specs = append(specs, &codegen.ImportSpec{
+ Path: genpkg + "/" + sd.Service.PathName,
+ Name: sd.Service.PkgName,
+ })
+ }
}
sections := []*codegen.SectionTemplate{
@@ -87,7 +94,7 @@ func endpointParser(genpkg string, root *expr.RootExpr, svr *expr.ServerExpr, da
cli.UsageCommands(data),
cli.UsageExamples(data),
{
- Name: "parse-endpoint",
+ Name: "parse-endpoint-grpc",
Source: readTemplate("parse_endpoint"),
Data: struct {
FlagsCode string
diff --git a/grpc/codegen/parse_endpoint_test.go b/grpc/codegen/parse_endpoint_test.go
new file mode 100644
index 0000000000..3a459e4b97
--- /dev/null
+++ b/grpc/codegen/parse_endpoint_test.go
@@ -0,0 +1,40 @@
+package codegen
+
+import (
+ "bytes"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "goa.design/goa/v3/codegen"
+ "goa.design/goa/v3/expr"
+ "goa.design/goa/v3/grpc/codegen/testdata"
+)
+
+func TestParseEndpointWithInterceptors(t *testing.T) {
+ cases := []struct {
+ Name string
+ DSL func()
+ }{
+ {
+ Name: "endpoint-with-interceptors",
+ DSL: testdata.InterceptorsDSL,
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ RunGRPCDSL(t, c.DSL)
+ fs := ClientCLIFiles("", expr.Root)
+ require.Greater(t, len(fs), 1, "expected at least 2 files")
+ require.NotEmpty(t, fs[0].SectionTemplates)
+ var buf bytes.Buffer
+ for _, s := range fs[0].SectionTemplates {
+ require.NoError(t, s.Write(&buf))
+ }
+ code := codegen.FormatTestCode(t, buf.String())
+ golden := filepath.Join("testdata", "endpoint-"+c.Name+".golden")
+ compareOrUpdateGolden(t, code, golden)
+ })
+ }
+}
diff --git a/grpc/codegen/templates/parse_endpoint.go.tpl b/grpc/codegen/templates/parse_endpoint.go.tpl
index 3cf69cde5a..43f98aec9f 100644
--- a/grpc/codegen/templates/parse_endpoint.go.tpl
+++ b/grpc/codegen/templates/parse_endpoint.go.tpl
@@ -2,9 +2,11 @@
// line.
func ParseEndpoint(
cc *grpc.ClientConn,
+{{- range .Commands }}
{{- if .Interceptors }}
{{ .Interceptors.VarName }} {{ .Interceptors.PkgName }}.ClientInterceptors,
{{- end }}
+{{- end }}
opts ...grpc.CallOption,
) (goa.Endpoint, any, error) {
{{ .FlagsCode }}
diff --git a/grpc/codegen/testdata/client-interceptors.golden b/grpc/codegen/testdata/client-interceptors.golden
index f21f374e1e..44dadcce53 100644
--- a/grpc/codegen/testdata/client-interceptors.golden
+++ b/grpc/codegen/testdata/client-interceptors.golden
@@ -1,6 +1,6 @@
import (
"fmt"
- cli "grpc/cli/test_api"
+ cli "grpc/cli/test"
"os"
"./interceptors"
diff --git a/grpc/codegen/testdata/dsls.go b/grpc/codegen/testdata/dsls.go
index 743ce71cb8..8bdd24b8e6 100644
--- a/grpc/codegen/testdata/dsls.go
+++ b/grpc/codegen/testdata/dsls.go
@@ -1018,6 +1018,13 @@ var InterceptorsDSL = func() {
Description("Collects metrics for the operation")
})
+ API("TestAPI", func() {
+ Title("Test API")
+ Server("Test", func() {
+ Description("Test server")
+ })
+ })
+
Service("ServiceWithInterceptors", func() {
ClientInterceptor(LogInterceptor)
Method("MethodA", func() {
diff --git a/grpc/codegen/testdata/endpoint-endpoint-with-interceptors.golden b/grpc/codegen/testdata/endpoint-endpoint-with-interceptors.golden
new file mode 100644
index 0000000000..4899bed507
--- /dev/null
+++ b/grpc/codegen/testdata/endpoint-endpoint-with-interceptors.golden
@@ -0,0 +1,178 @@
+// Test gRPC client CLI support package
+//
+// Command:
+// goa
+
+package cli
+
+import (
+ servicewithinterceptors "/service_with_interceptors"
+ "flag"
+ "fmt"
+ servicewithinterceptorsc "grpc/service_with_interceptors/client"
+ "os"
+
+ goa "goa.design/goa/v3/pkg"
+ grpc "google.golang.org/grpc"
+)
+
+// UsageCommands returns the set of commands and sub-commands using the format
+//
+// command (subcommand1|subcommand2|...)
+func UsageCommands() string {
+ return `service-with-interceptors (method-a|method-b)
+`
+}
+
+// UsageExamples produces an example of a valid invocation of the CLI tool.
+func UsageExamples() string {
+ return os.Args[0] + ` service-with-interceptors method-a --message '{
+ "field": "Sapiente est."
+ }'` + "\n" +
+ ""
+}
+
+// ParseEndpoint returns the endpoint and payload as specified on the command
+// line.
+func ParseEndpoint(
+ cc *grpc.ClientConn,
+ inter servicewithinterceptors.ClientInterceptors,
+ opts ...grpc.CallOption,
+) (goa.Endpoint, any, error) {
+ var (
+ serviceWithInterceptorsFlags = flag.NewFlagSet("service-with-interceptors", flag.ContinueOnError)
+
+ serviceWithInterceptorsMethodAFlags = flag.NewFlagSet("method-a", flag.ExitOnError)
+ serviceWithInterceptorsMethodAMessageFlag = serviceWithInterceptorsMethodAFlags.String("message", "", "")
+
+ serviceWithInterceptorsMethodBFlags = flag.NewFlagSet("method-b", flag.ExitOnError)
+ serviceWithInterceptorsMethodBMessageFlag = serviceWithInterceptorsMethodBFlags.String("message", "", "")
+ )
+ serviceWithInterceptorsFlags.Usage = serviceWithInterceptorsUsage
+ serviceWithInterceptorsMethodAFlags.Usage = serviceWithInterceptorsMethodAUsage
+ serviceWithInterceptorsMethodBFlags.Usage = serviceWithInterceptorsMethodBUsage
+
+ if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
+ return nil, nil, err
+ }
+
+ if flag.NArg() < 2 { // two non flag args are required: SERVICE and ENDPOINT (aka COMMAND)
+ return nil, nil, fmt.Errorf("not enough arguments")
+ }
+
+ var (
+ svcn string
+ svcf *flag.FlagSet
+ )
+ {
+ svcn = flag.Arg(0)
+ switch svcn {
+ case "service-with-interceptors":
+ svcf = serviceWithInterceptorsFlags
+ default:
+ return nil, nil, fmt.Errorf("unknown service %q", svcn)
+ }
+ }
+ if err := svcf.Parse(flag.Args()[1:]); err != nil {
+ return nil, nil, err
+ }
+
+ var (
+ epn string
+ epf *flag.FlagSet
+ )
+ {
+ epn = svcf.Arg(0)
+ switch svcn {
+ case "service-with-interceptors":
+ switch epn {
+ case "method-a":
+ epf = serviceWithInterceptorsMethodAFlags
+
+ case "method-b":
+ epf = serviceWithInterceptorsMethodBFlags
+
+ }
+
+ }
+ }
+ if epf == nil {
+ return nil, nil, fmt.Errorf("unknown %q endpoint %q", svcn, epn)
+ }
+
+ // Parse endpoint flags if any
+ if svcf.NArg() > 1 {
+ if err := epf.Parse(svcf.Args()[1:]); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ var (
+ data any
+ endpoint goa.Endpoint
+ err error
+ )
+ {
+ switch svcn {
+ case "service-with-interceptors":
+ c := servicewithinterceptorsc.NewClient(cc, opts...)
+ switch epn {
+
+ case "method-a":
+ endpoint = c.MethodA()
+ endpoint = servicewithinterceptors.WrapMethodAClientEndpoint(endpoint, inter)
+ data, err = servicewithinterceptorsc.BuildMethodAPayload(*serviceWithInterceptorsMethodAMessageFlag)
+ case "method-b":
+ endpoint = c.MethodB()
+ endpoint = servicewithinterceptors.WrapMethodBClientEndpoint(endpoint, inter)
+ data, err = servicewithinterceptorsc.BuildMethodBPayload(*serviceWithInterceptorsMethodBMessageFlag)
+ }
+ }
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return endpoint, data, nil
+}
+
+// serviceWithInterceptorsUsage displays the usage of the
+// service-with-interceptors command and its subcommands.
+func serviceWithInterceptorsUsage() {
+ fmt.Fprintf(os.Stderr, `Service is the ServiceWithInterceptors service interface.
+Usage:
+ %[1]s [globalflags] service-with-interceptors COMMAND [flags]
+
+COMMAND:
+ method-a: MethodA implements MethodA.
+ method-b: MethodB implements MethodB.
+
+Additional help:
+ %[1]s service-with-interceptors COMMAND --help
+`, os.Args[0])
+}
+func serviceWithInterceptorsMethodAUsage() {
+ fmt.Fprintf(os.Stderr, `%[1]s [flags] service-with-interceptors method-a -message JSON
+
+MethodA implements MethodA.
+ -message JSON:
+
+Example:
+ %[1]s service-with-interceptors method-a --message '{
+ "field": "Sapiente est."
+ }'
+`, os.Args[0])
+}
+
+func serviceWithInterceptorsMethodBUsage() {
+ fmt.Fprintf(os.Stderr, `%[1]s [flags] service-with-interceptors method-b -message JSON
+
+MethodB implements MethodB.
+ -message JSON:
+
+Example:
+ %[1]s service-with-interceptors method-b --message '{
+ "field": 3141052981090487272
+ }'
+`, os.Args[0])
+}
diff --git a/http/codegen/client.go b/http/codegen/client.go
index 52e0248712..5d70b2ed8d 100644
--- a/http/codegen/client.go
+++ b/http/codegen/client.go
@@ -67,7 +67,7 @@ func clientFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
}
sections = append(sections, &codegen.SectionTemplate{
- Name: "client-init",
+ Name: "http-client-init",
Source: readTemplate("client_init"),
Data: data,
FuncMap: map[string]any{"hasWebSocket": hasWebSocket},
From b9cab9e12a4b0c1d38a3d42130b90c21efd996b2 Mon Sep 17 00:00:00 2001
From: Raphael Simon
Date: Sun, 19 Jan 2025 11:33:22 -0800
Subject: [PATCH 6/6] Fix tests
---
codegen/service/service_data.go | 89 ++++++++++---------
.../client_interceptor_wrappers.go.tpl | 2 +-
.../templates/interceptors_types.go.tpl | 2 +-
.../server_interceptor_wrappers.go.tpl | 2 +-
.../chained_interceptor_service_client.golden | 20 ++---
.../chained_interceptor_service_server.golden | 20 ++---
...read-payload_client_interceptors.go.golden | 2 +-
...ead-payload_interceptor_wrappers.go.golden | 15 ++--
...ead-payload_service_interceptors.go.golden | 28 +++---
...-read-result_client_interceptors.go.golden | 2 +-
...read-result_interceptor_wrappers.go.golden | 14 +--
...read-result_service_interceptors.go.golden | 27 +++---
...rite-payload_client_interceptors.go.golden | 2 +-
...ite-payload_interceptor_wrappers.go.golden | 15 ++--
...ite-payload_service_interceptors.go.golden | 30 ++++---
...write-result_client_interceptors.go.golden | 2 +-
...rite-result_interceptor_wrappers.go.golden | 14 +--
...rite-result_service_interceptors.go.golden | 29 +++---
...rite-payload_client_interceptors.go.golden | 2 +-
...ite-payload_interceptor_wrappers.go.golden | 15 ++--
...ite-payload_service_interceptors.go.golden | 28 +++---
...write-result_client_interceptors.go.golden | 2 +-
...rite-result_interceptor_wrappers.go.golden | 14 +--
...rite-result_service_interceptors.go.golden | 27 +++---
...interceptors_client_interceptors.go.golden | 5 +-
...nterceptors_interceptor_wrappers.go.golden | 26 +++---
...nterceptors_service_interceptors.go.golden | 5 +-
...interceptor_interceptor_wrappers.go.golden | 20 ++++-
...interceptor_service_interceptors.go.golden | 5 +-
...-interceptor_client_interceptors.go.golden | 3 +-
...interceptor_interceptor_wrappers.go.golden | 8 +-
...interceptor_interceptor_wrappers.go.golden | 9 +-
...interceptor_service_interceptors.go.golden | 3 +-
...interceptor_interceptor_wrappers.go.golden | 20 ++++-
...interceptor_service_interceptors.go.golden | 5 +-
...ead-payload_interceptor_wrappers.go.golden | 11 +--
...ead-payload_service_interceptors.go.golden | 28 +++---
...read-result_interceptor_wrappers.go.golden | 11 +--
...read-result_service_interceptors.go.golden | 27 +++---
...nterceptors_interceptor_wrappers.go.golden | 11 +--
...nterceptors_service_interceptors.go.golden | 3 +-
41 files changed, 339 insertions(+), 264 deletions(-)
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index 59d7886ea9..7270e1e49b 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -280,8 +280,6 @@ type (
// MethodInterceptorData contains the data required to render the
// method-level interceptor code.
MethodInterceptorData struct {
- // Name is the name of the interceptor.
- Name string
// MethodName is the name of the method.
MethodName string
// PayloadAccess is the name of the payload access struct.
@@ -1203,47 +1201,48 @@ func buildInterceptorData(svc *expr.ServiceExpr, methods []*MethodData, i *expr.
DesignName: i.Name,
Description: i.Description,
}
- if len(svc.Methods) > 0 {
- payload, result := svc.Methods[0].Payload, svc.Methods[0].Result
- data.ReadPayload = collectAttributes(i.ReadPayload, payload, scope)
- data.WritePayload = collectAttributes(i.WritePayload, payload, scope)
- data.ReadResult = collectAttributes(i.ReadResult, result, scope)
- data.WriteResult = collectAttributes(i.WriteResult, result, scope)
- if len(data.ReadPayload) > 0 || len(data.WritePayload) > 0 {
- data.HasPayloadAccess = true
+ if len(svc.Methods) == 0 {
+ return data
+ }
+ payload, result := svc.Methods[0].Payload, svc.Methods[0].Result
+ data.ReadPayload = collectAttributes(i.ReadPayload, payload, scope)
+ data.WritePayload = collectAttributes(i.WritePayload, payload, scope)
+ data.ReadResult = collectAttributes(i.ReadResult, result, scope)
+ data.WriteResult = collectAttributes(i.WriteResult, result, scope)
+ if len(data.ReadPayload) > 0 || len(data.WritePayload) > 0 {
+ data.HasPayloadAccess = true
+ }
+ if len(data.ReadResult) > 0 || len(data.WriteResult) > 0 {
+ data.HasResultAccess = true
+ }
+ for _, m := range svc.Methods {
+ applies := false
+ intExprs := m.ServerInterceptors
+ if !server {
+ intExprs = m.ClientInterceptors
+ }
+ for _, in := range intExprs {
+ if in.Name == i.Name {
+ applies = true
+ break
+ }
}
- if len(data.ReadResult) > 0 || len(data.WriteResult) > 0 {
- data.HasResultAccess = true
+ if !applies {
+ continue
}
- for _, m := range svc.Methods {
- applies := false
- intExprs := m.ServerInterceptors
- if !server {
- intExprs = m.ClientInterceptors
- }
- for _, in := range intExprs {
- if in.Name == i.Name {
- applies = true
- break
- }
- }
- if !applies {
- continue
- }
- var md *MethodData
- for _, mt := range methods {
- if m.Name == mt.Name {
- md = mt
- break
- }
- }
- data.Methods = append(data.Methods, buildInterceptorMethodData(i, md))
- if server {
- md.ServerInterceptors = append(md.ServerInterceptors, i.Name)
- } else {
- md.ClientInterceptors = append(md.ClientInterceptors, i.Name)
+ var md *MethodData
+ for _, mt := range methods {
+ if m.Name == mt.Name {
+ md = mt
+ break
}
}
+ data.Methods = append(data.Methods, buildInterceptorMethodData(i, md))
+ if server {
+ md.ServerInterceptors = append(md.ServerInterceptors, i.Name)
+ } else {
+ md.ClientInterceptors = append(md.ClientInterceptors, i.Name)
+ }
}
return data
}
@@ -1258,11 +1257,17 @@ func buildInterceptorMethodData(i *expr.InterceptorExpr, md *MethodData) *Method
if md.ClientStream != nil {
clientStream = md.ClientStream.VarName
}
+ var payloadAccess, resultAccess string
+ if i.ReadPayload != nil || i.WritePayload != nil {
+ payloadAccess = codegen.Goify(i.Name, false) + md.VarName + "Payload"
+ }
+ if i.ReadResult != nil || i.WriteResult != nil {
+ resultAccess = codegen.Goify(i.Name, false) + md.VarName + "Result"
+ }
return &MethodInterceptorData{
- Name: i.Name,
MethodName: md.VarName,
- PayloadAccess: codegen.Goify(i.Name, false) + md.VarName + "Payload",
- ResultAccess: codegen.Goify(i.Name, false) + md.VarName + "Result",
+ PayloadAccess: payloadAccess,
+ ResultAccess: resultAccess,
PayloadRef: md.PayloadRef,
ResultRef: md.ResultRef,
ClientStreamInputStruct: clientStream,
diff --git a/codegen/service/templates/client_interceptor_wrappers.go.tpl b/codegen/service/templates/client_interceptor_wrappers.go.tpl
index 5cd5f9d9d1..cc0ff52a7b 100644
--- a/codegen/service/templates/client_interceptor_wrappers.go.tpl
+++ b/codegen/service/templates/client_interceptor_wrappers.go.tpl
@@ -15,7 +15,7 @@ func wrapClient{{ .MethodName }}{{ $interceptor.Name }}(endpoint goa.Endpoint, i
RawPayload: req,
{{- end }}
}
- return i.{{ .Name }}(ctx, info, endpoint)
+ return i.{{ $interceptor.Name }}(ctx, info, endpoint)
}
}
{{ end }}
diff --git a/codegen/service/templates/interceptors_types.go.tpl b/codegen/service/templates/interceptors_types.go.tpl
index 73a159d170..0c542b089e 100644
--- a/codegen/service/templates/interceptors_types.go.tpl
+++ b/codegen/service/templates/interceptors_types.go.tpl
@@ -59,4 +59,4 @@ type (
{{- end }}
{{- end }}
)
-{{- end }}
\ No newline at end of file
+{{- end }}
diff --git a/codegen/service/templates/server_interceptor_wrappers.go.tpl b/codegen/service/templates/server_interceptor_wrappers.go.tpl
index 3086dae3cf..cb0f5d9c44 100644
--- a/codegen/service/templates/server_interceptor_wrappers.go.tpl
+++ b/codegen/service/templates/server_interceptor_wrappers.go.tpl
@@ -15,7 +15,7 @@ func wrap{{ .MethodName }}{{ $interceptor.Name }}(endpoint goa.Endpoint, i Serve
RawPayload: req,
{{- end }}
}
- return i.{{ .Name }}(ctx, info, endpoint)
+ return i.{{ $interceptor.Name }}(ctx, info, endpoint)
}
}
{{- end }}
diff --git a/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden b/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden
index bb1b3811fb..4a1eeebce9 100644
--- a/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden
+++ b/codegen/service/testdata/example_interceptors/chained_interceptor_service_client.golden
@@ -23,6 +23,16 @@ type ChainedInterceptorServiceClientInterceptors struct {
func NewChainedInterceptorServiceClientInterceptors() *ChainedInterceptorServiceClientInterceptors {
return &ChainedInterceptorServiceClientInterceptors{}
}
+func (i *ChainedInterceptorServiceClientInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[API] Sending request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[API] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[API] Received response: %v", resp)
+ return resp, nil
+}
func (i *ChainedInterceptorServiceClientInterceptors) Method(ctx context.Context, info *interceptors.MethodInfo, next goa.Endpoint) (any, error) {
log.Printf(ctx, "[Method] Sending request: %v", info.RawPayload)
resp, err := next(ctx, info.RawPayload)
@@ -43,13 +53,3 @@ func (i *ChainedInterceptorServiceClientInterceptors) Service(ctx context.Contex
log.Printf(ctx, "[Service] Received response: %v", resp)
return resp, nil
}
-func (i *ChainedInterceptorServiceClientInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
- log.Printf(ctx, "[API] Sending request: %v", info.RawPayload)
- resp, err := next(ctx, info.RawPayload)
- if err != nil {
- log.Printf(ctx, "[API] Error: %v", err)
- return nil, err
- }
- log.Printf(ctx, "[API] Received response: %v", resp)
- return resp, nil
-}
diff --git a/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden b/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden
index b6cb4245f7..4027c3a597 100644
--- a/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden
+++ b/codegen/service/testdata/example_interceptors/chained_interceptor_service_server.golden
@@ -23,6 +23,16 @@ type ChainedInterceptorServiceServerInterceptors struct {
func NewChainedInterceptorServiceServerInterceptors() *ChainedInterceptorServiceServerInterceptors {
return &ChainedInterceptorServiceServerInterceptors{}
}
+func (i *ChainedInterceptorServiceServerInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
+ log.Printf(ctx, "[API] Processing request: %v", info.RawPayload)
+ resp, err := next(ctx, info.RawPayload)
+ if err != nil {
+ log.Printf(ctx, "[API] Error: %v", err)
+ return nil, err
+ }
+ log.Printf(ctx, "[API] Response: %v", resp)
+ return resp, nil
+}
func (i *ChainedInterceptorServiceServerInterceptors) Method(ctx context.Context, info *interceptors.MethodInfo, next goa.Endpoint) (any, error) {
log.Printf(ctx, "[Method] Processing request: %v", info.RawPayload)
resp, err := next(ctx, info.RawPayload)
@@ -43,13 +53,3 @@ func (i *ChainedInterceptorServiceServerInterceptors) Service(ctx context.Contex
log.Printf(ctx, "[Service] Response: %v", resp)
return resp, nil
}
-func (i *ChainedInterceptorServiceServerInterceptors) API(ctx context.Context, info *interceptors.APIInfo, next goa.Endpoint) (any, error) {
- log.Printf(ctx, "[API] Processing request: %v", info.RawPayload)
- resp, err := next(ctx, info.RawPayload)
- if err != nil {
- log.Printf(ctx, "[API] Error: %v", err)
- return nil, err
- }
- log.Printf(ctx, "[API] Response: %v", resp)
- return resp, nil
-}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden
index f1a4d2805c..5b21e6d339 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden
@@ -9,6 +9,6 @@ type ClientInterceptors interface {
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientValidation(endpoint, i, "Method")
+ endpoint = wrapClientMethodvalidation(endpoint, i)
return endpoint
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden
index ffbd9bf910..1f4ac6ebd3 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapValidation applies the validation interceptor to endpoints.
-func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapValidationMethod applies the validation server interceptor to endpoints.
+func wrapMethodValidation(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &ValidationInfo{
Service: "InterceptorWithReadPayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,15 +13,17 @@ func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string)
}
}
-// wrapClientValidation applies the validation interceptor to endpoints.
-func wrapClientValidation(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientValidationMethod applies the validation client interceptor to
+// endpoints.
+func wrapClientMethodValidation(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &ValidationInfo{
Service: "InterceptorWithReadPayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Validation(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden
index e3edffbad9..7e616c9438 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-payload_service_interceptors.go.golden
@@ -12,35 +12,37 @@ type (
// It includes service name, method name, and access to the endpoint.
ValidationInfo goa.InterceptorInfo
- // ValidationPayloadAccess provides type-safe access to the method payload.
+ // ValidationPayload provides type-safe access to the method payload.
// It allows reading and writing specific fields of the payload as defined
// in the design.
- ValidationPayloadAccess interface {
+ ValidationPayload interface {
Name() string
}
)
// Private implementation types
type (
- validationPayloadAccess struct {
+ validationMethodPayload struct {
payload *MethodPayload
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodvalidation(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
+
// Payload returns a type-safe accessor for the method payload.
-func (info *ValidationInfo) Payload() ValidationPayloadAccess {
- return &validationPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+func (info *ValidationInfo) Payload() ValidationPayload {
+ return &validationMethodPayload{payload: info.RawPayload.(*MethodPayload)}
}
// Private implementation methods
-func (p *validationPayloadAccess) Name() string {
- return p.payload.Name
-}
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapValidation(endpoint, i, "Method")
- return endpoint
+func (p *validationMethodPayload) Name() string {
+ return p.payload.Name
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden
index 76648aff03..7bf0271866 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden
@@ -9,6 +9,6 @@ type ClientInterceptors interface {
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientCaching(endpoint, i, "Method")
+ endpoint = wrapClientMethodcaching(endpoint, i)
return endpoint
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden
index c4ad755b66..5f86fe8455 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapCaching applies the caching interceptor to endpoints.
-func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapCachingMethod applies the caching server interceptor to endpoints.
+func wrapMethodCaching(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &CachingInfo{
Service: "InterceptorWithReadResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,15 +13,16 @@ func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa
}
}
-// wrapClientCaching applies the caching interceptor to endpoints.
-func wrapClientCaching(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientCachingMethod applies the caching client interceptor to endpoints.
+func wrapClientMethodCaching(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &CachingInfo{
Service: "InterceptorWithReadResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Caching(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden
index de70b2f1b3..a658f22d37 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-result_service_interceptors.go.golden
@@ -12,35 +12,36 @@ type (
// It includes service name, method name, and access to the endpoint.
CachingInfo goa.InterceptorInfo
- // CachingResultAccess provides type-safe access to the method result.
+ // CachingResult provides type-safe access to the method result.
// It allows reading and writing specific fields of the result as defined
// in the design.
- CachingResultAccess interface {
+ CachingResult interface {
Data() string
}
)
// Private implementation types
type (
- cachingResultAccess struct {
+ cachingMethodResult struct {
result *MethodResult
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodcaching(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
// Result returns a type-safe accessor for the method result.
-func (info *CachingInfo) Result(res any) CachingResultAccess {
- return &cachingResultAccess{result: res.(*MethodResult)}
+func (info *CachingInfo) Result(res any) CachingResult {
+ return &cachingMethodResult{result: res.(*MethodResult)}
}
// Private implementation methods
-func (r *cachingResultAccess) Data() string {
- return r.result.Data
-}
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapCaching(endpoint, i, "Method")
- return endpoint
+func (r *cachingMethodResult) Data() string {
+ return r.result.Data
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden
index f1a4d2805c..5b21e6d339 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden
@@ -9,6 +9,6 @@ type ClientInterceptors interface {
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientValidation(endpoint, i, "Method")
+ endpoint = wrapClientMethodvalidation(endpoint, i)
return endpoint
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden
index c9947a357a..f628ed5e05 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapValidation applies the validation interceptor to endpoints.
-func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapValidationMethod applies the validation server interceptor to endpoints.
+func wrapMethodValidation(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &ValidationInfo{
Service: "InterceptorWithReadWritePayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,15 +13,17 @@ func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string)
}
}
-// wrapClientValidation applies the validation interceptor to endpoints.
-func wrapClientValidation(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientValidationMethod applies the validation client interceptor to
+// endpoints.
+func wrapClientMethodValidation(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &ValidationInfo{
Service: "InterceptorWithReadWritePayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Validation(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden
index f4242b3d97..0f84e22320 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-payload_service_interceptors.go.golden
@@ -12,10 +12,10 @@ type (
// It includes service name, method name, and access to the endpoint.
ValidationInfo goa.InterceptorInfo
- // ValidationPayloadAccess provides type-safe access to the method payload.
+ // ValidationPayload provides type-safe access to the method payload.
// It allows reading and writing specific fields of the payload as defined
// in the design.
- ValidationPayloadAccess interface {
+ ValidationPayload interface {
Name() string
SetName(string)
}
@@ -23,28 +23,30 @@ type (
// Private implementation types
type (
- validationPayloadAccess struct {
+ validationMethodPayload struct {
payload *MethodPayload
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodvalidation(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
+
// Payload returns a type-safe accessor for the method payload.
-func (info *ValidationInfo) Payload() ValidationPayloadAccess {
- return &validationPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+func (info *ValidationInfo) Payload() ValidationPayload {
+ return &validationMethodPayload{payload: info.RawPayload.(*MethodPayload)}
}
// Private implementation methods
-func (p *validationPayloadAccess) Name() string {
+
+func (p *validationMethodPayload) Name() string {
return p.payload.Name
}
-func (p *validationPayloadAccess) SetName(v string) {
+func (p *validationMethodPayload) SetName(v string) {
p.payload.Name = v
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapValidation(endpoint, i, "Method")
- return endpoint
-}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden
index 76648aff03..7bf0271866 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden
@@ -9,6 +9,6 @@ type ClientInterceptors interface {
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientCaching(endpoint, i, "Method")
+ endpoint = wrapClientMethodcaching(endpoint, i)
return endpoint
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden
index 65bbf27b56..1e5ab9536e 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapCaching applies the caching interceptor to endpoints.
-func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapCachingMethod applies the caching server interceptor to endpoints.
+func wrapMethodCaching(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &CachingInfo{
Service: "InterceptorWithReadWriteResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,15 +13,16 @@ func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa
}
}
-// wrapClientCaching applies the caching interceptor to endpoints.
-func wrapClientCaching(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientCachingMethod applies the caching client interceptor to endpoints.
+func wrapClientMethodCaching(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &CachingInfo{
Service: "InterceptorWithReadWriteResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Caching(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden
index ef8f84f67f..96c934c24e 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-read-write-result_service_interceptors.go.golden
@@ -12,10 +12,10 @@ type (
// It includes service name, method name, and access to the endpoint.
CachingInfo goa.InterceptorInfo
- // CachingResultAccess provides type-safe access to the method result.
+ // CachingResult provides type-safe access to the method result.
// It allows reading and writing specific fields of the result as defined
// in the design.
- CachingResultAccess interface {
+ CachingResult interface {
Data() string
SetData(string)
}
@@ -23,28 +23,29 @@ type (
// Private implementation types
type (
- cachingResultAccess struct {
+ cachingMethodResult struct {
result *MethodResult
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodcaching(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
// Result returns a type-safe accessor for the method result.
-func (info *CachingInfo) Result(res any) CachingResultAccess {
- return &cachingResultAccess{result: res.(*MethodResult)}
+func (info *CachingInfo) Result(res any) CachingResult {
+ return &cachingMethodResult{result: res.(*MethodResult)}
}
// Private implementation methods
-func (r *cachingResultAccess) Data() string {
+
+func (r *cachingMethodResult) Data() string {
return r.result.Data
}
-func (r *cachingResultAccess) SetData(v string) {
+func (r *cachingMethodResult) SetData(v string) {
r.result.Data = v
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapCaching(endpoint, i, "Method")
- return endpoint
-}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden
index f1a4d2805c..5b21e6d339 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden
@@ -9,6 +9,6 @@ type ClientInterceptors interface {
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientValidation(endpoint, i, "Method")
+ endpoint = wrapClientMethodvalidation(endpoint, i)
return endpoint
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden
index 34e9a283bd..70657d6eef 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapValidation applies the validation interceptor to endpoints.
-func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapValidationMethod applies the validation server interceptor to endpoints.
+func wrapMethodValidation(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &ValidationInfo{
Service: "InterceptorWithWritePayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,15 +13,17 @@ func wrapValidation(endpoint goa.Endpoint, i ServerInterceptors, method string)
}
}
-// wrapClientValidation applies the validation interceptor to endpoints.
-func wrapClientValidation(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientValidationMethod applies the validation client interceptor to
+// endpoints.
+func wrapClientMethodValidation(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &ValidationInfo{
Service: "InterceptorWithWritePayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Validation(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden
index e54173d388..c76eb125e0 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-payload_service_interceptors.go.golden
@@ -12,35 +12,37 @@ type (
// It includes service name, method name, and access to the endpoint.
ValidationInfo goa.InterceptorInfo
- // ValidationPayloadAccess provides type-safe access to the method payload.
+ // ValidationPayload provides type-safe access to the method payload.
// It allows reading and writing specific fields of the payload as defined
// in the design.
- ValidationPayloadAccess interface {
+ ValidationPayload interface {
SetName(string)
}
)
// Private implementation types
type (
- validationPayloadAccess struct {
+ validationMethodPayload struct {
payload *MethodPayload
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodvalidation(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
+
// Payload returns a type-safe accessor for the method payload.
-func (info *ValidationInfo) Payload() ValidationPayloadAccess {
- return &validationPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+func (info *ValidationInfo) Payload() ValidationPayload {
+ return &validationMethodPayload{payload: info.RawPayload.(*MethodPayload)}
}
// Private implementation methods
-func (p *validationPayloadAccess) SetName(v string) {
- p.payload.Name = v
-}
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapValidation(endpoint, i, "Method")
- return endpoint
+func (p *validationMethodPayload) SetName(v string) {
+ p.payload.Name = v
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden
index 76648aff03..7bf0271866 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden
@@ -9,6 +9,6 @@ type ClientInterceptors interface {
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientCaching(endpoint, i, "Method")
+ endpoint = wrapClientMethodcaching(endpoint, i)
return endpoint
}
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden
index fad8d4bbe7..bfcca2ab0c 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapCaching applies the caching interceptor to endpoints.
-func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapCachingMethod applies the caching server interceptor to endpoints.
+func wrapMethodCaching(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &CachingInfo{
Service: "InterceptorWithWriteResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,15 +13,16 @@ func wrapCaching(endpoint goa.Endpoint, i ServerInterceptors, method string) goa
}
}
-// wrapClientCaching applies the caching interceptor to endpoints.
-func wrapClientCaching(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientCachingMethod applies the caching client interceptor to endpoints.
+func wrapClientMethodCaching(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &CachingInfo{
Service: "InterceptorWithWriteResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Caching(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden b/codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden
index 1efc553c96..26b2620a97 100644
--- a/codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/interceptor-with-write-result_service_interceptors.go.golden
@@ -12,35 +12,36 @@ type (
// It includes service name, method name, and access to the endpoint.
CachingInfo goa.InterceptorInfo
- // CachingResultAccess provides type-safe access to the method result.
+ // CachingResult provides type-safe access to the method result.
// It allows reading and writing specific fields of the result as defined
// in the design.
- CachingResultAccess interface {
+ CachingResult interface {
SetData(string)
}
)
// Private implementation types
type (
- cachingResultAccess struct {
+ cachingMethodResult struct {
result *MethodResult
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodcaching(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
// Result returns a type-safe accessor for the method result.
-func (info *CachingInfo) Result(res any) CachingResultAccess {
- return &cachingResultAccess{result: res.(*MethodResult)}
+func (info *CachingInfo) Result(res any) CachingResult {
+ return &cachingMethodResult{result: res.(*MethodResult)}
}
// Private implementation methods
-func (r *cachingResultAccess) SetData(v string) {
- r.result.Data = v
-}
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapCaching(endpoint, i, "Method")
- return endpoint
+func (r *cachingMethodResult) SetData(v string) {
+ r.result.Data = v
}
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden b/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden
index b0f78bbbb5..00d3b74592 100644
--- a/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/multiple-interceptors_client_interceptors.go.golden
@@ -20,7 +20,8 @@ type (
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientTest2(endpoint, i, "Method")
- endpoint = wrapClientTest4(endpoint, i, "Method")
+ endpoint = wrapClientMethodtest2(endpoint, i)
+ endpoint = wrapClientMethodtest4(endpoint, i)
return endpoint
}
+
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden
index dd2afccd42..f23c4d7171 100644
--- a/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/multiple-interceptors_interceptor_wrappers.go.golden
@@ -1,10 +1,11 @@
-// wrapTest applies the test interceptor to endpoints.
-func wrapTest(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapTestMethod applies the test server interceptor to endpoints.
+func wrapMethodTest(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &TestInfo{
Service: "MultipleInterceptorsService",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -12,12 +13,12 @@ func wrapTest(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.En
}
}
-// wrapTest3 applies the test3 interceptor to endpoints.
-func wrapTest3(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+// wrapTest3Method applies the test3 server interceptor to endpoints.
+func wrapMethodTest3(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &Test3Info{
Service: "MultipleInterceptorsService",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -25,12 +26,12 @@ func wrapTest3(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.E
}
}
-// wrapClientTest2 applies the test2 interceptor to endpoints.
-func wrapClientTest2(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientTest2Method applies the test2 client interceptor to endpoints.
+func wrapClientMethodTest2(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &Test2Info{
Service: "MultipleInterceptorsService",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
@@ -38,15 +39,16 @@ func wrapClientTest2(endpoint goa.Endpoint, i ClientInterceptors, method string)
}
}
-// wrapClientTest4 applies the test4 interceptor to endpoints.
-func wrapClientTest4(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+// wrapClientTest4Method applies the test4 client interceptor to endpoints.
+func wrapClientMethodTest4(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &Test4Info{
Service: "MultipleInterceptorsService",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Test4(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden b/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden
index 7d8c68e506..1df6e00cf8 100644
--- a/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/multiple-interceptors_service_interceptors.go.golden
@@ -20,7 +20,8 @@ type (
// WrapMethodEndpoint wraps the Method endpoint with the server-side
// interceptors defined in the design.
func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapTest(endpoint, i, "Method")
- endpoint = wrapTest3(endpoint, i, "Method")
+ endpoint = wrapMethodtest(endpoint, i)
+ endpoint = wrapMethodtest3(endpoint, i)
return endpoint
}
+
diff --git a/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden
index 1decfd7599..b7e0eebf4c 100644
--- a/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden
@@ -1,13 +1,27 @@
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapLoggingMethod applies the logging server interceptor to endpoints.
+func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
Service: "SingleAPIServerInterceptor",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Logging(ctx, info, endpoint)
}
}
+
+// wrapLoggingMethod2 applies the logging server interceptor to endpoints.
+func wrapMethod2Logging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "SingleAPIServerInterceptor",
+ Method: "Method2",
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden b/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden
index a5af7d39d5..f4870a7eef 100644
--- a/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/single-api-server-interceptor_service_interceptors.go.golden
@@ -16,13 +16,14 @@ type (
// WrapMethodEndpoint wraps the Method endpoint with the server-side
// interceptors defined in the design.
func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
+ endpoint = wrapMethodlogging(endpoint, i)
return endpoint
}
// WrapMethod2Endpoint wraps the Method2 endpoint with the server-side
// interceptors defined in the design.
func WrapMethod2Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method2")
+ endpoint = wrapMethod2logging(endpoint, i)
return endpoint
}
+
diff --git a/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden b/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden
index 41f9b16414..8f272d85e6 100644
--- a/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/single-client-interceptor_client_interceptors.go.golden
@@ -16,6 +16,7 @@ type (
// WrapMethodClientEndpoint wraps the Method endpoint with the client
// interceptors defined in the design.
func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
- endpoint = wrapClientTracing(endpoint, i, "Method")
+ endpoint = wrapClientMethodtracing(endpoint, i)
return endpoint
}
+
diff --git a/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden
index 4744840adf..b05666e31d 100644
--- a/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden
@@ -1,13 +1,15 @@
-// wrapClientTracing applies the tracing interceptor to endpoints.
-func wrapClientTracing(endpoint goa.Endpoint, i ClientInterceptors, method string) goa.Endpoint {
+
+// wrapClientTracingMethod applies the tracing client interceptor to endpoints.
+func wrapClientMethodTracing(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &TracingInfo{
Service: "SingleClientInterceptor",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Tracing(ctx, info, endpoint)
}
}
+
diff --git a/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden
index 57c9dce068..c06dbb3edd 100644
--- a/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden
@@ -1,13 +1,14 @@
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapLoggingMethod applies the logging server interceptor to endpoints.
+func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
Service: "SingleMethodServerInterceptor",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Logging(ctx, info, endpoint)
}
-}
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden b/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden
index 05e445a7f2..58b647a34a 100644
--- a/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/single-method-server-interceptor_service_interceptors.go.golden
@@ -16,6 +16,7 @@ type (
// WrapMethodEndpoint wraps the Method endpoint with the server-side
// interceptors defined in the design.
func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
+ endpoint = wrapMethodlogging(endpoint, i)
return endpoint
}
+
diff --git a/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden
index 6e8b74a57c..c15ab43987 100644
--- a/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden
@@ -1,13 +1,27 @@
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapLoggingMethod applies the logging server interceptor to endpoints.
+func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
Service: "SingleServerInterceptor",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
RawPayload: req,
}
return i.Logging(ctx, info, endpoint)
}
}
+
+// wrapLoggingMethod2 applies the logging server interceptor to endpoints.
+func wrapMethod2Logging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ return func(ctx context.Context, req any) (any, error) {
+ info := &LoggingInfo{
+ Service: "SingleServerInterceptor",
+ Method: "Method2",
+ Endpoint: endpoint,
+ RawPayload: req,
+ }
+ return i.Logging(ctx, info, endpoint)
+ }
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden b/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden
index a5af7d39d5..f4870a7eef 100644
--- a/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/single-service-server-interceptor_service_interceptors.go.golden
@@ -16,13 +16,14 @@ type (
// WrapMethodEndpoint wraps the Method endpoint with the server-side
// interceptors defined in the design.
func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
+ endpoint = wrapMethodlogging(endpoint, i)
return endpoint
}
// WrapMethod2Endpoint wraps the Method2 endpoint with the server-side
// interceptors defined in the design.
func WrapMethod2Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method2")
+ endpoint = wrapMethod2logging(endpoint, i)
return endpoint
}
+
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden
index f5a3f738df..554dade015 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden
@@ -1,13 +1,14 @@
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapLoggingMethod applies the logging server interceptor to endpoints.
+func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
Service: "StreamingInterceptorsWithReadPayload",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
- RawPayload: req.(*MethodEndpointInput).Payload,
+ RawPayload: req.(*MethodServerStream).Payload,
}
return i.Logging(ctx, info, endpoint)
}
-}
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden
index 8f6ed7f94c..4a173a3ce4 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_service_interceptors.go.golden
@@ -12,39 +12,41 @@ type (
// It includes service name, method name, and access to the endpoint.
LoggingInfo goa.InterceptorInfo
- // LoggingPayloadAccess provides type-safe access to the method payload.
+ // LoggingPayload provides type-safe access to the method payload.
// It allows reading and writing specific fields of the payload as defined
// in the design.
- LoggingPayloadAccess interface {
+ LoggingPayload interface {
Initial() string
}
)
// Private implementation types
type (
- loggingPayloadAccess struct {
+ loggingMethodPayload struct {
payload *MethodPayload
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodlogging(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
+
// Payload returns a type-safe accessor for the method payload.
-func (info *LoggingInfo) Payload() LoggingPayloadAccess {
- return &loggingPayloadAccess{payload: info.RawPayload.(*MethodPayload)}
+func (info *LoggingInfo) Payload() LoggingPayload {
+ return &loggingMethodPayload{payload: info.RawPayload.(*MethodPayload)}
}
// Private implementation methods
-func (p *loggingPayloadAccess) Initial() string {
+
+func (p *loggingMethodPayload) Initial() string {
if p.payload.Initial == nil {
var zero string
return zero
}
return *p.payload.Initial
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
- return endpoint
-}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden
index 2cb886bc02..2fc769d48b 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden
@@ -1,13 +1,14 @@
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapLoggingMethod applies the logging server interceptor to endpoints.
+func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
Service: "StreamingInterceptorsWithReadResult",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
- RawPayload: req.(*MethodEndpointInput).Payload,
+ RawPayload: req.(*MethodServerStream).Payload,
}
return i.Logging(ctx, info, endpoint)
}
-}
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden
index bc5516387c..fc61ae626f 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_service_interceptors.go.golden
@@ -12,39 +12,40 @@ type (
// It includes service name, method name, and access to the endpoint.
LoggingInfo goa.InterceptorInfo
- // LoggingResultAccess provides type-safe access to the method result.
+ // LoggingResult provides type-safe access to the method result.
// It allows reading and writing specific fields of the result as defined
// in the design.
- LoggingResultAccess interface {
+ LoggingResult interface {
Data() string
}
)
// Private implementation types
type (
- loggingResultAccess struct {
+ loggingMethodResult struct {
result *MethodResult
}
)
+// WrapMethodEndpoint wraps the Method endpoint with the server-side
+// interceptors defined in the design.
+func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
+ endpoint = wrapMethodlogging(endpoint, i)
+ return endpoint
+}
+
// Public accessor methods for Info types
// Result returns a type-safe accessor for the method result.
-func (info *LoggingInfo) Result(res any) LoggingResultAccess {
- return &loggingResultAccess{result: res.(*MethodResult)}
+func (info *LoggingInfo) Result(res any) LoggingResult {
+ return &loggingMethodResult{result: res.(*MethodResult)}
}
// Private implementation methods
-func (r *loggingResultAccess) Data() string {
+
+func (r *loggingMethodResult) Data() string {
if r.result.Data == nil {
var zero string
return zero
}
return *r.result.Data
}
-
-// WrapMethodEndpoint wraps the Method endpoint with the server-side
-// interceptors defined in the design.
-func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
- return endpoint
-}
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
index e0841bc4be..0361aa2387 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
@@ -1,13 +1,14 @@
-// wrapLogging applies the logging interceptor to endpoints.
-func wrapLogging(endpoint goa.Endpoint, i ServerInterceptors, method string) goa.Endpoint {
+
+// wrapLoggingMethod applies the logging server interceptor to endpoints.
+func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
return func(ctx context.Context, req any) (any, error) {
info := &LoggingInfo{
Service: "StreamingInterceptors",
- Method: method,
+ Method: "Method",
Endpoint: endpoint,
- RawPayload: req.(*MethodEndpointInput).Payload,
+ RawPayload: req.(*MethodServerStream).Payload,
}
return i.Logging(ctx, info, endpoint)
}
-}
+}
\ No newline at end of file
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden
index 05e445a7f2..58b647a34a 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors_service_interceptors.go.golden
@@ -16,6 +16,7 @@ type (
// WrapMethodEndpoint wraps the Method endpoint with the server-side
// interceptors defined in the design.
func WrapMethodEndpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint {
- endpoint = wrapLogging(endpoint, i, "Method")
+ endpoint = wrapMethodlogging(endpoint, i)
return endpoint
}
+