Skip to content

Commit fc9ba19

Browse files
global subscription context config option (#4237)
Signed-off-by: Steve Coffman <steve@khanacademy.org> # Conflicts: # codegen/field.go # codegen/internal!.gotpl # codegen/object.go # docs/content/reference/subscription-context.md # graphql/event.go # graphql/event_test.go
1 parent 384856c commit fc9ba19

36 files changed

Lines changed: 13187 additions & 67 deletions

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ formatters:
1919
- codegen/testserver/followschema/resolver.go
2020
- codegen/testserver/singlefile/resolver.go
2121
- codegen/testserver/usefunctionsyntaxforexecutioncontext/resolver.go
22+
- codegen/testserver/subscriptioncontextfield/resolver.go
2223
- generated
2324
enable:
2425
- golines

_examples/chat/generated.go

Lines changed: 3 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codegen/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Config struct {
4949
OmitPanicHandler bool `yaml:"omit_panic_handler,omitempty"`
5050
OmitEnumJSONMarshalers bool `yaml:"omit_enum_json_marshalers,omitempty"`
5151
UseFunctionSyntaxForExecutionContext bool `yaml:"use_function_syntax_for_execution_context,omitempty"`
52+
SubscriptionContextField bool `yaml:"subscription_context_field,omitempty"`
5253
// If this is set to true, argument directives that
5354
// decorate a field with a null value will still be called.
5455
//

codegen/directives_.gotpl

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,17 @@ func {{.FuncReceiver}}_mutationMiddleware(ctx context.Context, {{.ECFuncParam}}o
7878
{{ end }}
7979

8080
{{ if .AllDirectives.LocationDirectives "SUBSCRIPTION" }}
81-
func {{$.FuncReceiver}}_subscriptionMiddleware(ctx context.Context, {{$.ECFuncParam}}obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) func(ctx context.Context) graphql.Marshaler {
81+
{{- /* The subscription middleware returns a per-event-context tuple handler when the
82+
subscription_context_field option is on, and a plain marshaler handler otherwise. The shape
83+
is decided once here; the runtime supplies the matching null/error handler so its closure is
84+
not duplicated into the generated executor. */ -}}
85+
{{- $retType := "graphql.Marshaler" -}}
86+
{{- $nullHandler := "graphql.NullStream()" -}}
87+
{{- if $.Config.SubscriptionContextField -}}
88+
{{- $retType = "(context.Context, graphql.Marshaler)" -}}
89+
{{- $nullHandler = "graphql.NullEventStream()" -}}
90+
{{- end -}}
91+
func {{$.FuncReceiver}}_subscriptionMiddleware(ctx context.Context, {{$.ECFuncParam}}obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) func(ctx context.Context) {{$retType}} {
8292
for _, d := range obj.Directives {
8393
switch d.Name {
8494
{{- range $directive := .AllDirectives.LocationDirectives "SUBSCRIPTION" }}
@@ -88,9 +98,7 @@ func {{$.FuncReceiver}}_subscriptionMiddleware(ctx context.Context, {{$.ECFuncPa
8898
args, err := {{$.ECDot}}{{ $directive.ArgsFunc }}(ctx,{{$.ECArg}}rawArgs)
8999
if err != nil {
90100
ec.Error(ctx, err)
91-
return func(ctx context.Context) graphql.Marshaler {
92-
return graphql.Null
93-
}
101+
return {{$nullHandler}}
94102
}
95103
{{- end }}
96104
n := next
@@ -103,17 +111,13 @@ func {{$.FuncReceiver}}_subscriptionMiddleware(ctx context.Context, {{$.ECFuncPa
103111
tmp, err := next(ctx)
104112
if err != nil {
105113
ec.Error(ctx, err)
106-
return func(ctx context.Context) graphql.Marshaler {
107-
return graphql.Null
108-
}
114+
return {{$nullHandler}}
109115
}
110-
if data, ok := tmp.(func(ctx context.Context) graphql.Marshaler); ok {
116+
if data, ok := tmp.(func(ctx context.Context) {{$retType}}); ok {
111117
return data
112118
}
113119
graphql.AddErrorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
114-
return func(ctx context.Context) graphql.Marshaler {
115-
return graphql.Null
116-
}
120+
return {{$nullHandler}}
117121
}
118122
{{ end }}
119123

codegen/field.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ type Field struct {
4141
HasHaser bool // Whether a haser method is available (e.g., HasName())
4242
HaserMethodName string // Name of the haser method
4343
Batch bool // Enable batch resolver for this field
44+
// SubscriptionContextField mirrors the global subscription_context_field config
45+
// option, resolved once at build time so UsesSubscriptionContext and the methods
46+
// that depend on it stay nullary instead of threading the flag through the call chain.
47+
SubscriptionContextField bool
4448
}
4549

4650
func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, error) {
@@ -50,12 +54,13 @@ func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, e
5054
}
5155

5256
f := Field{
53-
FieldDefinition: field,
54-
Object: obj,
55-
Directives: dirs,
56-
GoFieldName: templates.ToGo(field.Name),
57-
GoFieldType: GoFieldVariable,
58-
GoReceiverName: "obj",
57+
FieldDefinition: field,
58+
Object: obj,
59+
Directives: dirs,
60+
GoFieldName: templates.ToGo(field.Name),
61+
GoFieldType: GoFieldVariable,
62+
GoReceiverName: "obj",
63+
SubscriptionContextField: b.Config.SubscriptionContextField,
5964
}
6065

6166
if field.DefaultValue != nil {
@@ -563,14 +568,18 @@ func (f *Field) HasDirectives() bool {
563568
}
564569

565570
// UsesSubscriptionContext reports whether this field is on the Subscription
566-
// root type and is annotated with @subscriptionContext. Codegen uses this to
571+
// root type and is annotated with @subscriptionContext, or the global
572+
// subscription_context_field option is enabled. Codegen uses this to
567573
// emit a resolver returning <-chan graphql.Event[T] instead of <-chan T, and
568574
// to thread per-event context into the AroundResponses interceptor chain.
569575
// Returns false for non-subscription fields even if they carry the directive.
570576
func (f *Field) UsesSubscriptionContext() bool {
571577
if !f.Object.Stream {
572578
return false
573579
}
580+
if f.SubscriptionContextField {
581+
return true
582+
}
574583
for _, d := range f.FieldDefinition.Directives {
575584
if d.Name == config.DirSubscriptionContext {
576585
return true

codegen/field.gotpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ func {{$.FuncReceiver}}_{{$object.Name}}_{{$field.Name}}(ctx context.Context, {{
99
{{- if $field.TypeReference.IsRoot }}
1010
fc, err := {{$.ECDot}}{{ $field.FieldContextFunc }}(ctx, {{$.ECArg}}field)
1111
if err != nil {
12-
return {{ $null }}
12+
return {{- if $field.UsesSubscriptionContext }} ctx,{{- end}} {{ $null }}
1313
}
1414
ctx = graphql.WithFieldContext(ctx, fc)
1515
{{- if not $.Config.OmitPanicHandler }}

codegen/internal!.gotpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
156156
}
157157
}
158158

159-
{{- if and .SubscriptionRoot .SubscriptionRoot.HasSubscriptionContextField }}
159+
{{- if and .SubscriptionRoot (.SubscriptionRoot.HasSubscriptionContextField) }}
160160

161161
// ExecWithEventContext is the per-event-context-aware response handler. The
162162
// graphql executor type-asserts the executable schema for this method when

codegen/object.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ func (o *Object) HasDirectives() bool {
140140
}
141141

142142
// HasSubscriptionContextField reports whether this object is a streaming
143-
// (subscription) root with at least one field annotated @subscriptionContext.
143+
// (subscription) root with at least one field annotated @subscriptionContext,
144+
// or the global subscription_context_field option is enabled.
144145
// Codegen uses this to decide whether to emit the event-context-aware
145146
// dispatcher and the optional ExecWithEventContext method on the
146147
// generated executableSchema. Returns false for non-streaming objects.

codegen/testserver/followschema/root_.generated.go

Lines changed: 3 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codegen/testserver/singlefile/generated.go

Lines changed: 3 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)