Skip to content

Commit 1c5f19c

Browse files
committed
Finalize initial interceptor implementation
1 parent 6d9bc66 commit 1c5f19c

29 files changed

+889
-383
lines changed

codegen/cli/cli.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,8 @@ const parseFlagsT = `var (
702702
`
703703

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

codegen/example/templates/server_interceptors.go.tpl

-4
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,12 @@
88
{{- end }}
99
{{- end }}
1010
)
11-
{{- end }}
12-
{{- if .HasInterceptors }}
1311
{
14-
{{- end }}
1512
{{- range .Services }}
1613
{{- if and .Methods .ServerInterceptors }}
1714
{{ .VarName }}Interceptors = {{ $.InterPkg }}.New{{ .StructName }}ServerInterceptors()
1815
{{- end }}
1916
{{- end }}
20-
{{- if .HasInterceptors }}
2117
}
2218
{{- end }}
2319
{{- end }}

codegen/service/client.go

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

codegen/service/endpoint.go

+9-16
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ type (
5050
ServiceName string
5151
// ServiceVarName is the name of the owner service Go interface.
5252
ServiceVarName string
53-
// ServerInterceptors contains the server-side interceptors for this method
54-
ServerInterceptors []*InterceptorData
55-
// ClientInterceptors contains the client-side interceptors for this method
56-
ClientInterceptors []*InterceptorData
5753
}
5854
)
5955

@@ -71,7 +67,7 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
7167
svc := Services.Get(service.Name)
7268
svcName := svc.PathName
7369
path := filepath.Join(codegen.Gendir, svcName, "endpoints.go")
74-
data := endpointData(service)
70+
data := endpointData(svc)
7571
var (
7672
sections []*codegen.SectionTemplate
7773
)
@@ -138,25 +134,22 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
138134
return &codegen.File{Path: path, SectionTemplates: sections}
139135
}
140136

141-
func endpointData(service *expr.ServiceExpr) *EndpointsData {
142-
svc := Services.Get(service.Name)
137+
func endpointData(svc *Data) *EndpointsData {
143138
methods := make([]*EndpointMethodData, len(svc.Methods))
144139
names := make([]string, len(svc.Methods))
145140
for i, m := range svc.Methods {
146141
methods[i] = &EndpointMethodData{
147-
MethodData: m,
148-
ArgName: codegen.Goify(m.VarName, false),
149-
ServiceName: svc.Name,
150-
ServiceVarName: serviceInterfaceName,
151-
ClientVarName: clientStructName,
152-
ServerInterceptors: m.ServerInterceptors,
153-
ClientInterceptors: m.ClientInterceptors,
142+
MethodData: m,
143+
ArgName: codegen.Goify(m.VarName, false),
144+
ServiceName: svc.Name,
145+
ServiceVarName: serviceInterfaceName,
146+
ClientVarName: clientStructName,
154147
}
155148
names[i] = codegen.Goify(m.VarName, false)
156149
}
157-
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, service.Name)
150+
desc := fmt.Sprintf("%s wraps the %q service endpoints.", endpointsStructName, svc.Name)
158151
return &EndpointsData{
159-
Name: service.Name,
152+
Name: svc.Name,
160153
Description: desc,
161154
VarName: endpointsStructName,
162155
ClientVarName: clientStructName,

codegen/service/example_interceptors.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.Fi
4949
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
5050
}),
5151
{
52-
Name: "server-interceptor",
52+
Name: "exmaple-server-interceptor",
5353
Source: readTemplate("example_server_interceptor"),
5454
Data: data,
5555
},
@@ -73,7 +73,7 @@ func exampleInterceptorsFile(genpkg string, svc *expr.ServiceExpr) []*codegen.Fi
7373
{Path: path.Join(genpkg, sdata.PathName), Name: sdata.PkgName},
7474
}),
7575
{
76-
Name: "client-interceptor",
76+
Name: "example-client-interceptor",
7777
Source: readTemplate("example_client_interceptor"),
7878
Data: data,
7979
},

codegen/service/interceptors.go

+52-32
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,41 @@ func InterceptorsFiles(genpkg string, service *expr.ServiceExpr) []*codegen.File
2929
}
3030

3131
// interceptorFile returns the file defining the interceptors.
32+
// This method is called twice, once for the server and once for the client.
3233
func interceptorFile(svc *Data, server bool) *codegen.File {
3334
filename := "client_interceptors.go"
3435
template := "client_interceptors"
35-
section := "client-interceptors"
36+
section := "client-interceptors-type"
3637
desc := "Client Interceptors"
37-
var data []*InterceptorData
3838
if server {
3939
filename = "service_interceptors.go"
4040
template = "server_interceptors"
41-
section = "server-interceptors"
41+
section = "server-interceptors-type"
4242
desc = "Server Interceptors"
43-
data = svc.ServerInterceptors
4443
}
4544
desc = svc.Name + desc
4645
path := filepath.Join(codegen.Gendir, svc.PathName, filename)
4746

47+
interceptors := svc.ServerInterceptors
4848
if !server {
49-
// We don't want to generate duplicate interceptor info data structures for
50-
// interceptors that are both server and client side.
51-
serverInterceptors := make(map[string]struct{}, len(svc.ServerInterceptors))
52-
for _, intr := range svc.ServerInterceptors {
53-
serverInterceptors[intr.Name] = struct{}{}
49+
interceptors = svc.ClientInterceptors
50+
}
51+
52+
// We don't want to generate duplicate interceptor info data structures for
53+
// interceptors that are both server and client side so remove interceptors
54+
// that are both server and client side when generating the client.
55+
if !server {
56+
names := make(map[string]struct{}, len(svc.ServerInterceptors))
57+
for _, sin := range svc.ServerInterceptors {
58+
names[sin.Name] = struct{}{}
5459
}
55-
for _, intr := range svc.ClientInterceptors {
56-
if _, ok := serverInterceptors[intr.Name]; ok {
57-
continue
60+
filtered := make([]*InterceptorData, 0, len(interceptors))
61+
for _, in := range interceptors {
62+
if _, ok := names[in.Name]; !ok {
63+
filtered = append(filtered, in)
5864
}
59-
data = append(data, intr)
6065
}
66+
interceptors = filtered
6167
}
6268

6369
sections := []*codegen.SectionTemplate{
@@ -66,45 +72,59 @@ func interceptorFile(svc *Data, server bool) *codegen.File {
6672
codegen.GoaImport(""),
6773
}),
6874
{
69-
Name: "section" + "-struct",
75+
Name: section,
7076
Source: readTemplate(template),
7177
Data: svc,
7278
},
7379
}
74-
if len(data) > 0 {
80+
if len(interceptors) > 0 {
7581
sections = append(sections, &codegen.SectionTemplate{
76-
Name: section,
77-
Source: readTemplate("interceptors"),
78-
Data: data,
82+
Name: "interceptor-types",
83+
Source: readTemplate("interceptors_types"),
84+
Data: interceptors,
7985
FuncMap: map[string]any{
8086
"hasPrivateImplementationTypes": hasPrivateImplementationTypes,
8187
},
8288
})
8389
}
8490

85-
// Add wrapper sections for each method that has interceptors
91+
template = "endpoint_wrappers"
92+
section = "endpoint-wrapper"
93+
if !server {
94+
template = "client_wrappers"
95+
section = "client-wrapper"
96+
}
8697
for _, m := range svc.Methods {
87-
if server && len(m.ServerInterceptors) == 0 || !server && len(m.ClientInterceptors) == 0 {
88-
continue
89-
}
90-
template := "endpoint_wrappers"
91-
templateName := "endpoint-wrapper"
98+
ints := m.ServerInterceptors
9299
if !server {
93-
template = "client_wrappers"
94-
templateName = "client-wrapper"
100+
ints = m.ClientInterceptors
101+
}
102+
if len(ints) == 0 {
103+
continue
95104
}
96105
sections = append(sections, &codegen.SectionTemplate{
97-
Name: templateName,
106+
Name: section,
98107
Source: readTemplate(template),
99108
Data: map[string]interface{}{
100-
"MethodVarName": codegen.Goify(m.Name, true),
101-
"Method": m.Name,
102-
"Service": svc.Name,
103-
"ServerInterceptors": m.ServerInterceptors,
104-
"ClientInterceptors": m.ClientInterceptors,
109+
"MethodVarName": m.VarName,
110+
"Method": m.Name,
111+
"Service": svc.Name,
112+
"Interceptors": ints,
105113
},
106114
})
107115
}
116+
117+
if len(interceptors) > 0 {
118+
sections = append(sections, &codegen.SectionTemplate{
119+
Name: "interceptors",
120+
Source: readTemplate("interceptors"),
121+
Data: interceptors,
122+
FuncMap: map[string]any{
123+
"hasPrivateImplementationTypes": hasPrivateImplementationTypes,
124+
},
125+
})
126+
}
127+
108128
return &codegen.File{Path: path, SectionTemplates: sections}
109129
}
110130

codegen/service/interceptors.md

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Interceptors Code Generation
2+
3+
Goa generates interceptor code to enable request/response interception and payload/result access.
4+
5+
---
6+
7+
## 1. Client & Server Interceptors
8+
9+
### Where They Are Generated
10+
11+
Client and server interceptor code is generated in:
12+
13+
- `gen/client_interceptors.go`
14+
- `gen/service_interceptors.go`
15+
16+
### Templates Used
17+
18+
1. **`server_interceptors.go.tpl`** and **`client_interceptors.go.tpl`**
19+
Define the server and client interceptor interface types.
20+
Each template takes a `Data` struct as input.
21+
22+
2. **`interceptors.go.tpl`**
23+
Generates the payload/result access interfaces and accompanying info structs.
24+
This template takes a slice of `InterceptorData` as input.
25+
26+
3. **`client_wrappers.go.tpl`** and **`endpoint_wrappers.go.tpl`**
27+
Generate code that wraps client and service endpoints with interceptor callbacks.
28+
Each template takes a map with:
29+
```go
30+
map[string]any{
31+
"MethodVarName": <Implemented method name>
32+
"Method": <Design method name>
33+
"Service": <Service name>
34+
"Interceptors": <Slice of InterceptorData>
35+
}
36+
```
37+
38+
---
39+
40+
## 2. Client & Server Endpoint Wrapper Code
41+
42+
### Where It Is Generated
43+
44+
Endpoint wrapper code for both client and server interceptors is generated in:
45+
46+
- `gen/interceptor_wrappers.go`
47+
48+
### Templates Used
49+
50+
1. **`server_interceptor_wrappers.go.tpl`**
51+
Generates server-specific wrapper implementations. This template receives a map with:
52+
```go
53+
map[string]any{
54+
"Service": svc.Name,
55+
"ServerInterceptors": svc.ServerInterceptors,
56+
}
57+
```
58+
59+
2. **`client_interceptor_wrappers.go.tpl`**
60+
Generates client-specific wrapper implementations. This template receives a map with:
61+
```go
62+
map[string]any{
63+
"Service": svc.Name,
64+
"ClientInterceptors": svc.ClientInterceptors,
65+
}
66+
```
67+
68+
## 3. Example Interceptors
69+
70+
### Where They Are Generated
71+
72+
Example interceptors are generated by the example command in an interceptors sub-package of the user’s service package:
73+
74+
* Client interceptors: `<service>_client.go`
75+
* Server interceptors: `<service>_server.go`
76+
77+
### Templates Used
78+
79+
1. **`example_client_interceptor.go.tpl` and `example_server_interceptor.go.tpl`**
80+
Generate example interceptor implementations. Each template takes a map with:
81+
```go
82+
map[string]any{
83+
"StructName": <interceptor struct name>
84+
"ServiceName": <service name>
85+
"PkgName": <interceptors package name>
86+
"ServerInterceptors": <server interceptors slice>
87+
"ClientInterceptors": <client interceptors slice>
88+
}
89+
```
90+
91+
2. **`example_service_init.go.tpl`**
92+
Generates an example service implementation. This template takes a Data struct.
93+
94+
### Generated Example Features
95+
96+
The example interceptors demonstrate common interception patterns:
97+
98+
* Logging of request/response data
99+
* Error handling and error logging
100+
* Type-safe payload and result access through the generated info structs
101+
* Context propagation across requests and responses
102+
103+
## 4. Data Structures
104+
105+
The templates above share a common set of data structures that describe
106+
interceptor behavior, payload, result access, and streaming. These data
107+
structures are defined as follows:
108+
109+
### `Data`
110+
111+
A service-level structure containing information for both client and server interceptors:
112+
113+
* `ServerInterceptors`: A slice of InterceptorData for server-side interceptors
114+
* `ClientInterceptors`: A slice of InterceptorData for client-side interceptors
115+
116+
### `InterceptorData`
117+
118+
The main structure describing each interceptor’s metadata and requirements:
119+
120+
* `Name`: Generated Go name of the interceptor (CamelCase)
121+
* `DesignName`: Original name from the design
122+
* `Description`: Interceptor description from the design
123+
* `HasPayloadAccess`: Indicates if any method requires payload access
124+
* `HasResultAccess`: Indicates if any method requires result access
125+
* `ReadPayload`: List of readable payload fields ([]AttributeData)
126+
* `WritePayload`: List of writable payload fields ([]AttributeData)
127+
* `ReadResult`: List of readable result fields ([]AttributeData)
128+
* `WriteResult`: List of writable result fields ([]AttributeData)
129+
* `Methods`: A list of MethodInterceptorData containing method-specific interceptor information
130+
* `ServerStreamInputStruct`: Server stream variable name (used if streaming)
131+
* `ClientStreamInputStruct`: Client stream variable name (used if streaming)
132+
133+
### `MethodInterceptorData`
134+
135+
Stores per-method interceptor configuration:
136+
137+
* `MethodName`: The method’s Go variable name
138+
* `PayloadAccess`: Name of the payload access type
139+
* `ResultAccess`: Name of the result access type
140+
* `PayloadRef`: Reference to the method's payload type
141+
* `ResultRef`: Reference to the method's result type
142+
143+
### `AttributeData`
144+
145+
Represents per-field access configuration:
146+
147+
* `Name`: The field accessor method name
148+
* `TypeRef`: Go type reference for the field
149+
* `Type`: Underlying attribute type information

0 commit comments

Comments
 (0)