Skip to content

Commit 2976b5e

Browse files
committed
feat: send subgraphs who requested fields
Subgraphs now can see why particular fields were requested. This data is provided with request to subgraph in the body.extensions.whoRequestedFields path. Example: { "query": "{accounts {__typename ... on User {some {__typename id}}}}", "extensions": { "whoRequestedFields": [ { "typeName": "User", "fieldName": "id", "requestedBySubgraphs": ["id-2"], "reasonIsKey": true }, { "typeName": "User", "fieldName": "some", "requestedByUser": true }, { "typeName": "User", "fieldName": "id", "requestedByUser": true }, { "typeName": "Query", "fieldName": "accounts", "requestedByUser": true } ] } } This can be enabled via configuration of router. Disabled by default. Always ignored for introspection queries.
1 parent fb585be commit 2976b5e

File tree

10 files changed

+50
-6
lines changed

10 files changed

+50
-6
lines changed

router-tests/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ require (
2727
github.com/wundergraph/cosmo/demo/pkg/subgraphs/projects v0.0.0-20250715110703-10f2e5f9c79e
2828
github.com/wundergraph/cosmo/router v0.0.0-20250820135159-bf8852195d3f
2929
github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e
30-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.220
30+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.221.0.20250822094201-72b8aff43390
3131
go.opentelemetry.io/otel v1.36.0
3232
go.opentelemetry.io/otel/sdk v1.36.0
3333
go.opentelemetry.io/otel/sdk/metric v1.36.0

router-tests/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTB
352352
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
353353
github.com/wundergraph/consul/sdk v0.0.0-20250204115147-ed842a8fd301 h1:EzfKHQoTjFDDcgaECCCR2aTePqMu9QBmPbyhqIYOhV0=
354354
github.com/wundergraph/consul/sdk v0.0.0-20250204115147-ed842a8fd301/go.mod h1:wxI0Nak5dI5RvJuzGyiEK4nZj0O9X+Aw6U0tC1wPKq0=
355-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.220 h1:+imPYcv+XExZ+ofX5jCxtaA7upeys7uWA7RsTZiTTWE=
356-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.220/go.mod h1:DnYY1alnsgzkanSwbFiFIdXKOuf8dHQWQ2P4BzTc6aI=
355+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.221.0.20250822094201-72b8aff43390 h1:nY23hSJDj56SshzrV2Xhv5JMLJo2TTcG/yijCh/xGUI=
356+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.221.0.20250822094201-72b8aff43390/go.mod h1:DnYY1alnsgzkanSwbFiFIdXKOuf8dHQWQ2P4BzTc6aI=
357357
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
358358
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
359359
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=

router-tests/integration_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,41 @@ func TestVariablesRemapping(t *testing.T) {
832832
})
833833
}
834834

835+
func TestPropagateWhoRequestedFields(t *testing.T) {
836+
t.Parallel()
837+
838+
// Simple test to verify that the configuration switch works.
839+
// Multi subgraphs calls are tested in the engine.
840+
testenv.Run(t, &testenv.Config{
841+
ModifyEngineExecutionConfiguration: func(cfg *config.EngineExecutionConfiguration) {
842+
cfg.PropagateWhoRequestedFields = true
843+
},
844+
Subgraphs: testenv.SubgraphsConfig{
845+
Employees: testenv.SubgraphConfig{
846+
Middleware: func(handler http.Handler) http.Handler {
847+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
848+
body, _ := io.ReadAll(r.Body)
849+
var req core.GraphQLRequest
850+
require.NoError(t, json.Unmarshal(body, &req))
851+
852+
require.Equal(t, `query($a: Int!){employee(id: $a){id}}`, req.Query)
853+
require.Equal(t, `{"whoRequestedFields":[{"typeName":"Employee","fieldName":"id","requestedByUser":true},{"typeName":"Query","fieldName":"employee","requestedByUser":true}]}`, string(req.Extensions))
854+
855+
w.WriteHeader(http.StatusOK)
856+
_, _ = w.Write([]byte(`{"data":{"employee":{"id":1}}}`))
857+
})
858+
},
859+
},
860+
},
861+
}, func(t *testing.T, xEnv *testenv.Environment) {
862+
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
863+
Query: `query ($count:Int!) { employee(id:$count) { id } }`,
864+
Variables: json.RawMessage(`{"count":1}`),
865+
})
866+
require.JSONEq(t, `{"data":{"employee":{"id":1}}}`, res.Body)
867+
})
868+
}
869+
835870
func TestAnonymousQuery(t *testing.T) {
836871
t.Parallel()
837872

router/core/executor.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func (b *ExecutorConfigurationBuilder) Build(ctx context.Context, opts *Executor
8585
MaxRecyclableParserSize: opts.RouterEngineConfig.Execution.ResolverMaxRecyclableParserSize,
8686
MultipartSubHeartbeatInterval: opts.HeartbeatInterval,
8787
MaxSubscriptionFetchTimeout: opts.RouterEngineConfig.Execution.SubscriptionFetchTimeout,
88+
PropagateWhoRequestedFields: opts.RouterEngineConfig.Execution.PropagateWhoRequestedFields,
8889
}
8990

9091
if opts.ApolloCompatibilityFlags.ValueCompletion.Enabled {

router/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ require (
3131
github.com/tidwall/gjson v1.18.0
3232
github.com/tidwall/sjson v1.2.5
3333
github.com/twmb/franz-go v1.16.1
34-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.220
34+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.221.0.20250822094201-72b8aff43390
3535
// Do not upgrade, it renames attributes we rely on
3636
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
3737
go.opentelemetry.io/contrib/propagators/b3 v1.23.0

router/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
317317
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
318318
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk=
319319
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
320-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.220 h1:+imPYcv+XExZ+ofX5jCxtaA7upeys7uWA7RsTZiTTWE=
321-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.220/go.mod h1:DnYY1alnsgzkanSwbFiFIdXKOuf8dHQWQ2P4BzTc6aI=
320+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.221.0.20250822094201-72b8aff43390 h1:nY23hSJDj56SshzrV2Xhv5JMLJo2TTcG/yijCh/xGUI=
321+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.221.0.20250822094201-72b8aff43390/go.mod h1:DnYY1alnsgzkanSwbFiFIdXKOuf8dHQWQ2P4BzTc6aI=
322322
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
323323
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
324324
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=

router/pkg/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ type EngineExecutionConfiguration struct {
394394
ResolverMaxRecyclableParserSize int `envDefault:"32768" env:"ENGINE_RESOLVER_MAX_RECYCLABLE_PARSER_SIZE" yaml:"resolver_max_recyclable_parser_size,omitempty"`
395395
EnableSubgraphFetchOperationName bool `envDefault:"false" env:"ENGINE_ENABLE_SUBGRAPH_FETCH_OPERATION_NAME" yaml:"enable_subgraph_fetch_operation_name"`
396396
DisableVariablesRemapping bool `envDefault:"false" env:"ENGINE_DISABLE_VARIABLES_REMAPPING" yaml:"disable_variables_remapping"`
397+
PropagateWhoRequestedFields bool `envDefault:"false" env:"ENGINE_PROPAGATE_WHO_REQUESTED_FIELDS" yaml:"propagate_who_requested_fields"`
397398
SubscriptionFetchTimeout time.Duration `envDefault:"30s" env:"ENGINE_SUBSCRIPTION_FETCH_TIMEOUT" yaml:"subscription_fetch_timeout,omitempty"`
398399
}
399400

router/pkg/config/config.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2780,6 +2780,11 @@
27802780
"default": false,
27812781
"description": "Disables variables renaming during normalization. This option could have a negative impact on planner cache hits."
27822782
},
2783+
"propagate_who_requested_fields": {
2784+
"type": "boolean",
2785+
"default": false,
2786+
"description": "Enables sending to upstream subgraphs (only) the \"whoRequestedFields\" extension that explains why each field was requested. This flag does not expose the data to clients."
2787+
},
27832788
"subscription_fetch_timeout": {
27842789
"type": "string",
27852790
"format": "go-duration",

router/pkg/config/testdata/config_defaults.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@
370370
"ResolverMaxRecyclableParserSize": 32768,
371371
"EnableSubgraphFetchOperationName": false,
372372
"DisableVariablesRemapping": false,
373+
"PropagateWhoRequestedFields": false,
373374
"SubscriptionFetchTimeout": 30000000000
374375
},
375376
"WebSocket": {

router/pkg/config/testdata/config_full.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@
730730
"ResolverMaxRecyclableParserSize": 4096,
731731
"EnableSubgraphFetchOperationName": true,
732732
"DisableVariablesRemapping": false,
733+
"PropagateWhoRequestedFields": false,
733734
"SubscriptionFetchTimeout": 30000000000
734735
},
735736
"WebSocket": {

0 commit comments

Comments
 (0)