-
Notifications
You must be signed in to change notification settings - Fork 152
feat: add support of field selection reasons extensions #1282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: add support of field selection reasons extensions #1282
Conversation
This option of Resolver enables router to send to subgraphs an extension that includes reasons why fields where included in the request. The name of fields in the extension are open for discussion.
WalkthroughPropagates per-field "fetch reasons" through planner and resolve layers, indexes fetch-reason fields in data-source metadata, computes inverse field-ref dependants, surfaces per-fetch reasons on concrete fetch types and public APIs, and optionally injects Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
v2/pkg/engine/plan/visitor.go (1)
1398-1461
: Bug: user-requested fields can be dropped when requestedBy exists but resolves to no subgraphsIf fieldRefRequestedBy[fieldRef] exists but yields no BySubgraphs (e.g., nil ObjectFetchConfiguration or unmapped planners), the function currently doesn’t append the record—even when ByUser is true. That loses attribution for genuinely user-requested fields.
Fix: append when requested.ByUser OR there are any BySubgraphs.
Also, avoid shadowing the fetchID parameter inside the inner loop and defensively handle missing TypeName.
for _, fieldRef := range fields { fieldName := v.Operation.FieldNameString(fieldRef) if fieldName == "__typename" { continue } requested := resolve.RequestedField{ - TypeName: v.fieldEnclosingTypeNames[fieldRef], + TypeName: v.fieldEnclosingTypeNames[fieldRef], FieldName: fieldName, } requested.ByUser = !v.skipField(fieldRef) requestedByRefs, ok := v.fieldRefRequestedBy[fieldRef] if !ok { if requested.ByUser { result = append(result, requested) } continue } requested.BySubgraphs = make([]string, 0, len(requestedByRefs)) for _, reqByRef := range requestedByRefs { - depFieldPlanners, ok := v.fieldPlanners[reqByRef] + depFieldPlanners, ok := v.fieldPlanners[reqByRef] if !ok { continue } // find the subgraph's name by iterating over planners that are responsible for reqByRef - for _, fetchID := range depFieldPlanners { - ofc := v.planners[fetchID].ObjectFetchConfiguration() + for _, plannerID := range depFieldPlanners { + ofc := v.planners[plannerID].ObjectFetchConfiguration() if ofc == nil { continue } requested.BySubgraphs = append(requested.BySubgraphs, ofc.sourceName) - depKind, ok := v.fieldDependencyKind[fieldDependencyKey{field: reqByRef, dependsOn: fieldRef}] + depKind, ok := v.fieldDependencyKind[fieldDependencyKey{field: reqByRef, dependsOn: fieldRef}] if !ok { continue } switch depKind { case fieldDependencyKindKey: requested.ReasonIsKey = true case fieldDependencyKindRequires: requested.ReasonIsRequires = true } } } - if len(requested.BySubgraphs) > 0 { + if requested.ByUser || len(requested.BySubgraphs) > 0 { slices.Sort(requested.BySubgraphs) requested.BySubgraphs = slices.Compact(requested.BySubgraphs) result = append(result, requested) } }Optional: if TypeName might be missing (shouldn’t, but defensive), consider skipping such entries or defaulting with a clear sentinel; otherwise downstream JSON may contain empty typeName.
🧹 Nitpick comments (15)
execution/engine/config_factory_federation.go (1)
309-314
: Stale commented code references undefined symbol; clarify intent or remove.The commented-out
FetchURLRules
line referencesrouterEngineConfig.Headers
which isn't in scope here (likely meantrouterConfig.EngineConfig.Headers
). If you plan to re-enable this soon, fix the symbol and keep a precise TODO; otherwise, delete the block to avoid confusion.Apply this minimal cleanup to keep the comment accurate until the code is enabled:
- // dataSourceRules := FetchURLRules(&routerEngineConfig.Headers, routerConfig.BySubgraphs, subscriptionUrl) + // TODO(eng-7769): enable once header rules are wired. Likely: + // dataSourceRules := FetchURLRules(&routerConfig.EngineConfig.Headers, routerConfig.BySubgraphs, subscriptionUrl)v2/pkg/engine/plan/configuration.go (1)
30-35
: Go doc comments should start with the field name.To keep golint/godoc happy and improve clarity, start doc comments with the exported field names and tighten phrasing.
Apply:
- // Tests can suppress generating resolve.FieldInfo in the planner. - DisableIncludeInfo bool + // DisableIncludeInfo controls whether the planner generates resolve.FieldInfo (useful in tests). + DisableIncludeInfo bool - // Test can suppress generating field dependencies structures in the planner. - DisableIncludeFieldDependencies bool + // DisableIncludeFieldDependencies controls whether the planner generates field dependency structures (useful in tests). + DisableIncludeFieldDependencies boolv2/pkg/engine/postprocess/create_concrete_single_fetch_types.go (1)
77-112
: Propagating FieldsRequestedBy to BatchEntityFetch looks correct; confirm immutability.You pass through
fetch.FieldsRequestedBy
as-is. If the source slice is treated as immutable after planning (expected), this is fine. If anything mutates it later, consider cloning to avoid aliasing.Would you like a follow-up patch that defensively clones this slice during post-processing?
v2/pkg/engine/resolve/loader.go (2)
1565-1581
: Skip propagation for introspection and keep extensions collision-safe.Two small improvements:
- Don’t attach
fieldsRequestedBy
to introspection requests; it’s noise and could confuse tooling.- You already assume
body.extensions
is an object;jsonparser.Set
will error if it’s incompatible. Current error handling is fine, but tightening the condition keeps input shape predictable.Here’s a focused tweak to guard introspection without changing behavior elsewhere.
Apply:
- if l.propagateFieldsRequestedBy { + if l.propagateFieldsRequestedBy && !IsIntrospectionDataSource(res.ds.ID) { fieldsRequestedBy := fetchItem.Fetch.RequestedFields() if fieldsRequestedBy != nil { // We expect that body.extensions is an object var encoded []byte encoded, res.err = json.Marshal(fieldsRequestedBy) if res.err != nil { res.err = errors.WithStack(res.err) return } input, res.err = jsonparser.Set(input, encoded, "body", "extensions", "fieldsRequestedBy") if res.err != nil { res.err = errors.WithStack(res.err) return } } }
1565-1581
: Consider namespacing the extension key to prevent collisions.If subgraphs or clients use a similarly named field, collisions are possible. Consider a vendor-prefixed key like
wgFieldsRequestedBy
or a namespaced objectextensions.wg.fieldsRequestedBy
.v2/pkg/engine/plan/planner.go (1)
231-239
: Make inverseMap deterministic to avoid flaky tests.Map iteration order is random; sorting the resulting slices gives stable outputs for debugging/tests.
Apply:
func inverseMap(m map[int][]int) map[int][]int { inverse := make(map[int][]int) for k, v := range m { for _, v2 := range v { inverse[v2] = append(inverse[v2], k) } } - return inverse + // Normalize ordering for deterministic plans/tests + for key := range inverse { + sort.Ints(inverse[key]) + } + return inverse }And add the import:
// at top of file import ( "fmt" "strings" "sort" ... )execution/engine/execution_engine_test.go (4)
218-219
: Add a helper option for symmetry and reuse (nit).You added propagateFieldsRequestedBy to the internal test options. To keep the test harness consistent with withValueCompletion() and reduce inline closures, add a small helper:
func withPropagateFieldsRequestedBy() executionTestOptions { return func(options *_executionTestOptions) { options.propagateFieldsRequestedBy = true } }
827-876
: Stability of JSON body assertion for fieldsRequestedBy.The test asserts a byte-for-byte body string including the extensions.fieldsRequestedBy array. If the upstream computation ever changes the order in that slice, this becomes flaky. Consider making the round-tripper support JSON assertions (e.g., decode and compare specific fields ignoring array element order) or build the expected body via json.Marshal on a struct to guarantee stable key ordering.
Apply this diff to reuse a helper option (from my other comment) and avoid the inline closure:
- func(eto *_executionTestOptions) { - eto.propagateFieldsRequestedBy = true - }, + withPropagateFieldsRequestedBy(),
4347-4470
: Reduce duplication in fieldsRequestedBy and centralize expected body construction.Two separate entries for the same coordinate (User.id) carry different reasons (requestedBySubgraphs+reasonIsKey vs requestedByUser). Consider deduplicating per (typeName, fieldName) and merging reasons into a single object before emitting. This will shrink payload size and simplify consumption by subgraphs.
Additionally, the long raw JSON strings are brittle. A small local builder that marshals:
- query string
- optional variables
- extensions.fieldsRequestedBy (slice of structs)
would improve maintainability and cut quoting mistakes. Because tests elsewhere already assert exact JSON bodies, using structs (not maps) will keep key order deterministic.
Example local type to drive json.Marshal deterministically:
type expectedBody struct { Query string `json:"query"` Variables any `json:"variables,omitempty"` Extensions struct { FieldsRequestedBy []struct { TypeName string `json:"typeName"` FieldName string `json:"fieldName"` RequestedByUser bool `json:"requestedByUser,omitempty"` RequestedBySubgraphs []string `json:"requestedBySubgraphs,omitempty"` ReasonIsKey bool `json:"reasonIsKey,omitempty"` } `json:"fieldsRequestedBy,omitempty"` } `json:"extensions,omitempty"` }
4537-4539
: Use the helper option instead of an inline closure (nit).Same suggestion as above for consistency.
- func(eto *_executionTestOptions) { - eto.propagateFieldsRequestedBy = true - }, + withPropagateFieldsRequestedBy(),v2/pkg/engine/plan/visitor.go (2)
49-49
: New inverse map is fine, but document/guarantee producer semanticsAdding fieldRefRequestedBy (inverse of fieldRefDependsOnFieldRefs) is appropriate. Please ensure its population (likely in planner.go) is always done before configureFetch runs, and consider adding a brief doc on whether refs included here are only direct dependents or also transitive. This affects expectations for buildFieldsRequestedBy.
636-646
: Helper extraction improves clarity; consider naming and fast-pathskipField helper is clear and correctly preserves previous semantics. Minor: if skipFieldsRefs can grow, consider a small set/map for O(1) checks, or keep as-is if the list is tiny.
- for _, skipRef := range v.skipFieldsRefs { - if skipRef == ref { return true } - } + // If this grows, switch to map[int]struct{} for O(1). + for _, skipRef := range v.skipFieldsRefs { + if skipRef == ref { return true } + }v2/pkg/engine/resolve/fetch.go (2)
416-427
: Tighten docstring grammar and naming consistencyMinor grammar nits and align wording with the JSON tags (“requestedBySubgraphs”, “requestedByUser”).
-// RequestedField contains an answer about who requested a specific TypeName, FieldName combination. -// It can be requested by set Subgraphs and by a User. +// RequestedField explains who requested a specific (typeName, fieldName) combination. +// A field can be requested by the user and/or by one or more subgraphs, with optional reasons.
350-354
: Surface looks good; be explicit about equality semanticsFetchConfiguration now carries FieldsRequestedBy. Note that Equals() does not compare this field (nor CoordinateDependencies). If tests or tooling need to assert exact propagation of requested fields, rely on explicit checks rather than Equals(). Consider adding a comment near Equals() to make this intentional.
func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool { + // Note: FieldsRequestedBy and CoordinateDependencies are intentionally excluded from equality. + // They carry diagnostic/metadata that doesn't affect fetch identity.v2/pkg/engine/resolve/resolve.go (1)
168-171
: Option name and docs LGTM; clarify exposure scopePropagateFieldsRequestedBy is clear. Consider clarifying in the comment that this only propagates to upstream subgraphs (extensions) and is not exposed to clients unless separately surfaced, to avoid confusion about information leakage.
-// PropagateFieldsRequestedBy enables sending to upstream subgraphs the "fieldsRequestedBy" -// extension that contains information why each field was requested. +// PropagateFieldsRequestedBy enables sending to upstream subgraphs (only) the "fieldsRequestedBy" +// extension that explains why each field was requested. This flag does not expose the data to clients.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
execution/engine/config_factory_federation.go
(1 hunks)execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
(4 hunks)v2/pkg/engine/plan/configuration.go
(1 hunks)v2/pkg/engine/plan/planner.go
(2 hunks)v2/pkg/engine/plan/visitor.go
(5 hunks)v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
(2 hunks)v2/pkg/engine/resolve/fetch.go
(11 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
v2/pkg/engine/resolve/loader.go (1)
v2/pkg/engine/resolve/fetch.go (1)
Fetch
(20-26)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(419-426)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(419-426)
v2/pkg/engine/resolve/resolve.go (1)
v2/pkg/engine/resolve/resolvable.go (1)
ResolvableOptions
(62-67)
execution/engine/execution_engine_test.go (4)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (1)
DataSourceMetadata
(34-56)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-354)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (17)
v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go (1)
124-147
: Same propagation for EntityFetch; mirror the immutability consideration.Same note as above; the propagation is correct. Just ensure upstream treats
FieldsRequestedBy
as read-only post plan.v2/pkg/engine/resolve/loader.go (1)
152-155
: New Loader flag is well-scoped.Adding
propagateFieldsRequestedBy
behind a boolean is the right safety valve. Ensure it’s plumbed fromResolverOptions.PropagateFieldsRequestedBy
innewTools
so tests can toggle it.v2/pkg/engine/plan/planner.go (1)
156-157
: Wiring inverse dependencies is a good call; watch for nondeterministic order.Deriving
fieldRefRequestedBy
fromfieldRefDependsOn
is correct. Note that map iteration in Go is randomized; if ordering matters downstream (tests, deterministic plans), ensure consumers sort.execution/engine/execution_engine_test.go (2)
4505-4541
: Federation scenario with fieldsRequestedBy propagation — good coverage.This mirrors the non-extension path, verifies both subgraphs see the extension, and exercises key-based entity fetch reasoning. Nicely scoped and readable.
266-267
: All good: PropagateFieldsRequestedBy gating confirmedA ripgrep search shows that:
PropagateFieldsRequestedBy
is only added toResolverOptions
inresolve.go
and correctly passed through to the engine.- In
loader.go
, the"fieldsRequestedBy"
extension is set on the request only within theif l.propagateFieldsRequestedBy { … }
block, with no occurrences elsewhere.No further action needed.
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (4)
3183-3201
: LGTM: requires for country/city are accurately attributed.Marking Address.country and Address.city as requires from address.service, while preserving Account.address as user-requested, matches the dependency chain. No issues spotted.
3295-3313
: LGTM: downstream requires on line3/zip are precise.Both fields are correctly attributed to account.service via requires, and the enclosing Account.address remains user-requested. This aligns with the later fullAddress resolution.
3420-3431
: LGTM: user-requested fullAddress attribution is clear.Capturing Address.fullAddress and Account.address as ByUser in the final fetch is appropriate and keeps the provenance readable.
3097-3135
: No helper needed: BySubgraphs is already deterministically sortedThe test’s expected slice literal (
[]string{"account.service", "address-enricher.service", "address.service"}
) already matches the alphabetical order enforced in the production code, so there’s no risk of flakiness here.• In
v2/pkg/engine/plan/visitor.go
(lines 1453–1454), the code callsslices.Sort(requested.BySubgraphs) requested.BySubgraphs = slices.Compact(requested.BySubgraphs)which guarantees that
BySubgraphs
is sorted and deduplicated before being returned.You can safely leave the test as-is.
v2/pkg/engine/plan/visitor.go (2)
58-66
: Good: tracking maps for “who planned what” and enclosing typesplannerFields, fieldPlanners, and fieldEnclosingTypeNames are the right primitives for later attribution. Nice that they’re initialized in EnterDocument.
1335-1338
: Plumbing LGTM; relies on promoted fields via embeddingAssigning CoordinateDependencies and FieldsRequestedBy on singleFetch works via field promotion (FetchConfiguration is embedded). Gated by DisableIncludeFieldDependencies, which is consistent with where the underlying maps are populated.
v2/pkg/engine/resolve/fetch.go (5)
25-26
: Interface extension is coherentAdding RequestedFields() to Fetch cleanly exposes the new metadata without leaking struct details.
108-111
: SingleFetch delegation is correctReturning FetchConfiguration.FieldsRequestedBy via promotion is appropriate and keeps SingleFetch lean.
180-181
: BatchEntityFetch wiring is consistentStoring FieldsRequestedBy directly on BatchEntityFetch and exposing via RequestedFields() matches the post-processing pattern used elsewhere.
Please confirm create_concrete_single_fetch_types.go copies FetchConfiguration.FieldsRequestedBy from SingleFetch to BatchEntityFetch during post-processing.
Also applies to: 191-194
232-234
: EntityFetch wiring is consistentCoordinateDependencies and FieldsRequestedBy are exposed consistently.
Double-check post-processing populates EntityFetch.FieldsRequestedBy and preserves CoordinateDependencies.
Also applies to: 244-247
283-286
: ParallelListItemFetch delegation is correctDelegating to the underlying SingleFetch configuration makes sense here.
v2/pkg/engine/resolve/resolve.go (1)
253-254
: Good: loader gets the new flagPassing propagateFieldsRequestedBy into Loader completes the plumbing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
v2/pkg/engine/plan/visitor.go (2)
1399-1461
: Tighten semantics: exclude “self” and stabilize output order for FieldsRequestedByTwo suggestions:
- Exclude the current fetch’s planner/subgraph from BySubgraphs to avoid self-referencing noise.
- Sort the resulting slice deterministically (by TypeName, FieldName) for stable tests/traces.
Apply this diff:
func (v *Visitor) buildFieldsRequestedBy(fetchID int) []resolve.RequestedField { fields, ok := v.plannerFields[fetchID] if !ok { return nil } result := make([]resolve.RequestedField, 0, len(fields)) + // Identify current subgraph for this fetch (to omit self) + currentSubgraphName := "" + if ofc := v.planners[fetchID].ObjectFetchConfiguration(); ofc != nil { + currentSubgraphName = ofc.sourceName + } + for _, fieldRef := range fields { fieldName := v.Operation.FieldNameString(fieldRef) if fieldName == "__typename" { continue } requested := resolve.RequestedField{ TypeName: v.fieldEnclosingTypeNames[fieldRef], FieldName: fieldName, } requested.ByUser = !v.skipField(fieldRef) requestedByRefs, ok := v.fieldRefAllowsFieldRefs[fieldRef] if !ok { if requested.ByUser { result = append(result, requested) } continue } requested.BySubgraphs = make([]string, 0, len(requestedByRefs)) for _, reqByRef := range requestedByRefs { depFieldPlanners, ok := v.fieldPlanners[reqByRef] if !ok { continue } // find the subgraph's name by iterating over planners that are responsible for reqByRef for _, plannerID := range depFieldPlanners { ofc := v.planners[plannerID].ObjectFetchConfiguration() - if ofc == nil { + if ofc == nil || ofc.sourceName == currentSubgraphName || plannerID == fetchID { continue } requested.BySubgraphs = append(requested.BySubgraphs, ofc.sourceName) depKind, ok := v.fieldDependencyKind[fieldDependencyKey{field: reqByRef, dependsOn: fieldRef}] if !ok { continue } switch depKind { case fieldDependencyKindKey: requested.ReasonIsKey = true case fieldDependencyKindRequires: requested.ReasonIsRequires = true } } } if requested.ByUser || len(requested.BySubgraphs) > 0 { slices.Sort(requested.BySubgraphs) requested.BySubgraphs = slices.Compact(requested.BySubgraphs) result = append(result, requested) } } + // Stable ordering across runs + slices.SortFunc(result, func(a, b resolve.RequestedField) int { + if c := strings.Compare(a.TypeName, b.TypeName); c != 0 { + return c + } + return strings.Compare(a.FieldName, b.FieldName) + }) return result }
636-646
: skipField is fine; optimize only if neededLooping a small slice is OK. If skipFieldsRefs grows, switch to a map[int]struct{} as your TODO suggests.
v2/pkg/engine/resolve/fetch.go (2)
416-425
: RequestedField payload shape is sensible; minor naming thoughtSchema looks good and compact. If you ever need per-subgraph reasons, you’ll outgrow booleans; for now, this is pragmatic.
Optional future-proofing: consider nesting reasons to avoid widening the struct later:
- reasons: { key: bool, requires: bool }
No change required for this PR.
350-354
: Document metadata exclusions in FetchConfiguration.EqualsThe current implementation of
FetchConfiguration.Equals
(lines 356–364) deliberately omits bothFieldsRequestedBy
andOperationName
. These two fields carry metadata (the set of user/subgraph–requested fields and an optional operation name for tracing) and do not influence the structural equivalence of fetch plans.Please add an explicit note above the
Equals
method to clarify this behavior:func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool { + // Intentionally ignore metadata fields: + // • FieldsRequestedBy: for tracking which fields were requested + // • OperationName: for propagating the operation name upstream + // These do not affect structural equality of the fetch plan. if fc.Input != other.Input { return false } // … if fc.SetTemplateOutputToNullOnVariableNull != other.SetTemplateOutputToNullOnVariableNull { return false } return true }This documentation will help future maintainers understand why these fields are excluded, and ensure any caching or testing logic based on
Equals
remains accurate.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
v2/pkg/engine/plan/configuration.go
(1 hunks)v2/pkg/engine/plan/planner.go
(3 hunks)v2/pkg/engine/plan/visitor.go
(7 hunks)v2/pkg/engine/resolve/fetch.go
(11 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- v2/pkg/engine/plan/configuration.go
🚧 Files skipped from review as they are similar to previous changes (3)
- v2/pkg/engine/resolve/loader.go
- v2/pkg/engine/plan/planner.go
- v2/pkg/engine/resolve/resolve.go
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (8)
v2/pkg/engine/plan/visitor.go (2)
58-66
: Clear inline documentation for planner field mapsGood clarifications for plannerFields/fieldPlanners/fieldEnclosingTypeNames; improves maintainability.
49-49
: No action required: inverse mapping is always initialized unconditionally in Plan()I’ve verified that in
planner.go
thePlan
method begins by assigning:p.planningVisitor.fieldRefDependsOnFieldRefs = selectionsConfig.fieldRefDependsOn p.planningVisitor.fieldDependencyKind = selectionsConfig.fieldDependencyKind p.planningVisitor.fieldRefAllowsFieldRefs = inverseMap(selectionsConfig.fieldRefDependsOn)This assignment at lines 155–157 is unguarded—there are no early
return
statements preceding it—so on every call toPlan
,fieldRefAllowsFieldRefs
is populated.v2/pkg/engine/resolve/fetch.go (6)
25-26
: Good: Fetch API gains RequestedFields() for a uniform surfaceThis will let loader/transport code query per-fetch field provenance without type switches.
171-181
: BatchEntityFetch: per-fetch storage for requested fieldsLooks consistent with EntityFetch and ParallelListItemFetch. No issues.
191-194
: BatchEntityFetch.RequestedFields implementation is straightforwardLGTM.
224-234
: EntityFetch adds FieldsRequestedBy and exposes itAlignment with BatchEntityFetch is good.
283-286
: ParallelListItemFetch delegates RequestedFields to underlying SingleFetchMakes sense for non-batching paths.
108-111
: SingleFetch.RequestedFields is correctly delegated and populatedThe
RequestedFields
method onSingleFetch
simply returnsFetchConfiguration.FieldsRequestedBy
. In the planner (v2/pkg/engine/plan/visitor.go:1338
), you can see:
singleFetch.FieldsRequestedBy = v.buildFieldsRequestedBy(internal.fetchID)
This confirms that the planner populates
FetchConfiguration.FieldsRequestedBy
before execution. No further changes are needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
v2/pkg/engine/plan/visitor.go (1)
1336-1339
: Confirm field ownership on SingleFetch vs embedded FetchConfigurationYou now assign SingleFetch.CoordinateDependencies and SingleFetch.WhoRequestedFields directly. Earlier versions placed these on the embedded FetchConfiguration; there was a compile error noted in a prior review when assigning on the wrong struct.
Please confirm SingleFetch defines these fields. If not, switch to:
- singleFetch.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) - singleFetch.WhoRequestedFields = v.buildRequestedFields(internal.fetchID) + singleFetch.FetchConfiguration.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) + singleFetch.FetchConfiguration.WhoRequestedFields = v.buildRequestedFields(internal.fetchID)
🧹 Nitpick comments (9)
execution/engine/execution_engine_test.go (2)
827-876
: Potential GET-vs-body ambiguity: prefer POST when sending extensionsThis test configures the subgraph fetch with Method: "GET" but asserts a non-empty JSON body including extensions.whoRequestedFields. Typical GraphQL over HTTP conventions avoid bodies on GET; some clients drop them. Unless the datasource automatically switches to POST when extensions are present, this may be brittle across transports.
- If the datasource auto-upgrades GET→POST when a body is needed, keep as-is but confirm in loader/datasource tests.
- Otherwise, set Method: "POST" for clarity and portability.
Suggested change:
- Fetch: &graphql_datasource.FetchConfiguration{ - URL: "https://example.com/", - Method: "GET", - }, + Fetch: &graphql_datasource.FetchConfiguration{ + URL: "https://example.com/", + Method: "POST", + },
4347-4471
: Stability of whoRequestedFields ordering across runsexpectedBody1/2 compare raw JSON strings. buildRequestedFields iterates v.plannerFields[fetchID] insertion order; while likely stable, it’s not explicitly ordered. You already sort and compact requested.BySubgraphs; consider also sorting the top-level whoRequestedFields by (typeName, fieldName) for deterministic test behavior and future-proofing.
I’ve proposed a concrete diff on the builder in v2/pkg/engine/plan/visitor.go to sort the result slice.
v2/pkg/engine/plan/visitor.go (2)
636-647
: skipField is O(n); acceptable, but simple to harden if the list growsGiven this runs once per visited field, the linear scan over skipFieldsRefs is likely fine. If skip lists can grow large in big plans, switch to a map[int]struct{} for O(1) lookups.
- // If this grows, switch to map[int]struct{} for O(1). - for _, skipRef := range v.skipFieldsRefs { - if skipRef == ref { - return true - } - } + // If this grows, consider using a map[int]struct{} for O(1) checks. + for _, skipRef := range v.skipFieldsRefs { + if skipRef == ref { + return true + } + }
1399-1461
: Deterministic whoRequestedFields ordering for test stabilityThe construction is correct: you exclude __typename, mark requestedByUser via skipField, derive requestedBySubgraphs and reason flags from planners/dependency kinds, then sort/compact subgraphs. To avoid any flakiness in tests that compare raw request bodies, also sort the result slice.
Apply this diff to sort the top-level list by (TypeName, FieldName):
func (v *Visitor) buildRequestedFields(fetchID int) []resolve.RequestedField { @@ - for _, fieldRef := range fields { + for _, fieldRef := range fields { fieldName := v.Operation.FieldNameString(fieldRef) if fieldName == "__typename" { continue } @@ - if requested.ByUser || len(requested.BySubgraphs) > 0 { + if requested.ByUser || len(requested.BySubgraphs) > 0 { slices.Sort(requested.BySubgraphs) requested.BySubgraphs = slices.Compact(requested.BySubgraphs) result = append(result, requested) } } - - return result + // Ensure deterministic order of whoRequestedFields + slices.SortFunc(result, func(a, b resolve.RequestedField) int { + if a.TypeName == b.TypeName { + if a.FieldName == b.FieldName { + return 0 + } + if a.FieldName < b.FieldName { + return -1 + } + return 1 + } + if a.TypeName < b.TypeName { + return -1 + } + return 1 + }) + return result }v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go (1)
124-129
: Same propagation for EntityFetchMirrors the batch path; looks good. If other concrete fetch types can include the extension in the future (e.g., ParallelListItemFetch envelopes), consider adding propagation there too for completeness, though not required for entity paths today.
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (4)
3097-3135
: Good addition: captures why parent fields and keys are pulled into the first fetch; ensure order is deterministicThe entries correctly mark Address.line1/line2 as requires from account.service, aggregate Address.id across all dependent subgraphs with ReasonIsKey, and flag the user-driven path (Query.user → User.account → Account.address). One nit: if BySubgraphs is built from a set/map in the planner, its order could be non-deterministic and make this assertion flaky. Consider either sorting BySubgraphs when producing WhoRequestedFields or sorting it in the test before assertion to avoid order-related flakes.
3183-3201
: Should representation keys appear in WhoRequestedFields for this fetch?This fetch relies on Address.id in $representations, but the WhoRequestedFields here only lists Address.country and Address.city (ReasonIsRequires). If WhoRequestedFields is meant to describe all fields “included in the request” (query + variables) for a given fetch, consider adding Address.id with ReasonIsKey as well; if it’s meant to capture only the newly selected fields (not representation keys), a brief comment in the code/tests clarifying the intended scope would help prevent confusion.
3295-3313
: Consistent requires attribution; clarify key usage if intendedSimilar to the previous block: Address.line3 and Address.zip are correctly attributed to account.service (ReasonIsRequires) for computing Address.fullAddress. If the intent is to also account for keys used in $representations per fetch, consider including Address.id with ReasonIsKey here, or confirm that keys are only tracked on the fetch where they’re actually selected (as in the first fetch).
3420-3431
: User-driven final field looks right; optional enhancementAddress.fullAddress and the pathway Account.address are accurately marked as user requested. Optional: to aid future debugging, you could also assert the absence of requires/key reasons here (i.e., that only ByUser is set), to make the contract explicit.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
(4 hunks)v2/pkg/engine/plan/visitor.go
(7 hunks)v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
(2 hunks)v2/pkg/engine/resolve/fetch.go
(11 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- v2/pkg/engine/resolve/loader.go
- v2/pkg/engine/resolve/resolve.go
- v2/pkg/engine/resolve/fetch.go
🧰 Additional context used
🧬 Code graph analysis (3)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
execution/engine/execution_engine_test.go (4)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (1)
DataSourceMetadata
(34-56)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-354)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (6)
execution/engine/execution_engine_test.go (3)
218-219
: Good addition: toggle for who-requested-fields propagationIntroducing the propagateWhoRequestedFields test flag on _executionTestOptions keeps the test harness clean and avoids leaking resolver concerns into every test case. No issues.
266-267
: ResolverOptions wiring looks correctPropagateWhoRequestedFields is plumbed through ResolverOptions alongside existing flags. This ensures tests can flip the behavior per run without global impact.
4501-4502
: Great coverage: with/without propagation path on federated planRunning the same scenario with and without the extension, and validating per-subgraph payloads (including reasonIsKey and requestedBySubgraphs) exercises the end-to-end plumbing well.
Also applies to: 4505-4540
v2/pkg/engine/plan/visitor.go (2)
1357-1397
: Minor: consistent coordinate assemblyGood call building coordinates with FieldName from the operation and TypeName from fieldEnclosingTypeNames, mirroring the same approach for dependency origins. This keeps coordinates stable across aliasing.
49-66
: Verify selection dependency maps are populated correctlyI see that
fieldRefAllowsFieldRefs
is indeed initialized inv2/pkg/engine/plan/planner.go:157
viap.planningVisitor.fieldRefAllowsFieldRefs = inverseMap(selectionsConfig.fieldRefDependsOn)and subsequently used in
v2/pkg/engine/plan/visitor.go:1417
when building requested fields.However, please confirm that:
selectionsConfig.fieldRefDependsOn
is populated with allfieldRef→[]dependentFieldRefs
entries before the inverseMap call.- The inverse mapping occurs prior to any invocations of
buildRequestedFields
, so thatBySubgraphs
and reason flags will be set correctly.No code change needed if the above is already true; otherwise, ensure dependency analysis runs early enough.
v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go (1)
77-82
: PropagateWhoRequestedFields for BatchEntityFetch Verified
- BatchEntityFetch (v2/pkg/engine/resolve/fetch.go) defines a FieldsRequestedBy slice and its RequestedFields() method returns this slice.
- Loader’s executeSourceLoad (v2/pkg/engine/resolve/loader.go) checks propagateWhoRequestedFields and calls fetchItem.Fetch.RequestedFields(), encoding and injecting the “whoRequestedFields” extension for both EntityFetch and BatchEntityFetch.
No further changes are needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
execution/engine/execution_engine_test.go (5)
214-219
: Add a short doc on the new test knob for claritypropagateWhoRequestedFields is self-explanatory, but a one-line comment on its intent (propagates field provenance into subgraph request extensions) would help future readers scanning options.
type _executionTestOptions struct { resolvableOptions resolve.ResolvableOptions apolloRouterCompatibilitySubrequestHTTPError bool - propagateWhoRequestedFields bool + // when true, expected test round-trips must include extensions.whoRequestedFields in upstream requests + propagateWhoRequestedFields bool }
827-876
: Good coverage for basic propagation; guard against brittle string comparisonsNice, this validates that enabling propagation produces the expected extensions payload and correctly switches to POST. However, matching the request body as a raw JSON string is fragile (key order and array ordering can change with innocuous refactors). Consider comparing bodies semantically in the test RoundTripper (parse JSON and deep-equal) or generate expected bodies via json.Marshal of typed structs to keep ordering deterministic.
If you want, I can provide a small change to createTestRoundTripper to accept an expected JSON value and compare canonically. As a local improvement in this file, you can also marshal a struct for expectedBody to avoid hand-crafted JSON strings.
4347-4471
: Helper is useful; build expected JSON programmatically to reduce brittlenessThe makeDataSource(t, expectRequestedBy) abstraction is solid and keeps the two federation subgraphs readable. The only weak spot is the large hand-written expectedBody JSON strings. Building them via structs and json.Marshal will:
- eliminate quoting/escaping errors,
- lock key ordering to struct field order,
- make diffs smaller if the payload shape evolves.
You can keep the test harness unchanged since the output will still be a compact JSON string.
Apply this focused refactor to use a builder for the two expected bodies:
- var expectedBody1 string - var expectedBody2 string - if !expectRequestedBy { - expectedBody1 = `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}"}` - expectedBody2 = `{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename title}}}","variables":{"representations":[{"__typename":"User","id":"1"},{"__typename":"User","id":"3"}]}}` - } else { - expectedBody1 = `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {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":"Admin","fieldName":"some","requestedByUser":true},{"typeName":"Query","fieldName":"accounts","requestedByUser":true}]}}` - expectedBody2 = `{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename title}}}","variables":{"representations":[{"__typename":"User","id":"1"},{"__typename":"User","id":"3"}]},"extensions":{"whoRequestedFields":[{"typeName":"User","fieldName":"title","requestedByUser":true},{"typeName":"User","fieldName":"some","requestedByUser":true}]}}` - } + expectedBody1, expectedBody2 := buildFederationExpectedBodies(expectRequestedBy)Add this helper somewhere in this test file (outside the function):
// support code for the diff above type gqlReq struct { Query string `json:"query"` Variables map[string]any `json:"variables,omitempty"` Extensions map[string]any `json:"extensions,omitempty"` } func mustMarshal(v any) string { b, err := json.Marshal(v) if err != nil { panic(err) } return string(b) } func buildFederationExpectedBodies(withReasons bool) (string, string) { // first subgraph body body1 := gqlReq{ Query: `{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}`, } if withReasons { body1.Extensions = map[string]any{ "whoRequestedFields": []any{ map[string]any{"typeName": "User", "fieldName": "id", "requestedBySubgraphs": []string{"id-2"}, "reasonIsKey": true}, map[string]any{"typeName": "User", "fieldName": "some", "requestedByUser": true}, map[string]any{"typeName": "User", "fieldName": "id", "requestedByUser": true}, map[string]any{"typeName": "Admin", "fieldName": "some", "requestedByUser": true}, map[string]any{"typeName": "Query", "fieldName": "accounts", "requestedByUser": true}, }, } } // second subgraph body body2 := gqlReq{ Query: `query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename title}}}`, Variables: map[string]any{ "representations": []any{ map[string]any{"__typename": "User", "id": "1"}, map[string]any{"__typename": "User", "id": "3"}, }, }, } if withReasons { body2.Extensions = map[string]any{ "whoRequestedFields": []any{ map[string]any{"typeName": "User", "fieldName": "title", "requestedByUser": true}, map[string]any{"typeName": "User", "fieldName": "some", "requestedByUser": true}, }, } } return mustMarshal(body1), mustMarshal(body2) }
4505-4540
: End-to-end federation path with propagation enabledThis complements the non-propagating run and validates the extension plumbing across both subgraphs. Consider one more variant where Method remains POST but propagation is disabled (to assert that no extensions key leaks when the flag is false).
I can add a follow-up test variant if you’d like.
827-876
: Reduce risk from ordering of whoRequestedFields itemsDepending on how the planner assembles WhoRequestedFields, the slice ordering could change with future refactors. If the order is not guaranteed, tests may become flaky. Either:
- sort the whoRequestedFields slice before marshalling upstream bodies, or
- adapt the RoundTripper to compare the whoRequestedFields array as a set (order-insensitive).
If ordering is guaranteed by construction, please add a brief comment stating that invariant so future changes don’t unintentionally break it.
Would you like me to add a deterministic sort (e.g., by typeName, then fieldName, then flags) at the injection point?
Also applies to: 4347-4471
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
execution/engine/execution_engine_test.go
(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
execution/engine/execution_engine_test.go (6)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (1)
DataSourceMetadata
(34-56)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-354)v2/pkg/engine/plan/federation_metadata.go (2)
FederationMetaData
(10-16)FederationFieldConfigurations
(113-113)v2/pkg/engine/resolve/context.go (1)
Request
(148-151)
🔇 Additional comments (4)
execution/engine/execution_engine_test.go (4)
262-267
: Plumbing the option through ResolverOptions looks correctPassing PropagateWhoRequestedFields from test options into resolve.ResolverOptions is consistent with the new API surface. No issues spotted.
858-861
: POST is required to carry extensionsGood call to switch the fetch method to POST for this case; a GET would drop the body and the extension. This keeps behavior explicit in the test.
4411-4418
: Federation config is set on SchemaConfigurationGood to see federation explicitly enabled with the corresponding ServiceSDL; matches the entity fetches used in this test.
262-267
: All good:PropagateWhoRequestedFields
is wired and the extension key is consistent
- The
ResolverOptions
struct in v2/pkg/engine/resolve/resolve.go includesPropagateWhoRequestedFields
and it’s passed through to the loader via
propagateWhoRequestedFields: options.PropagateWhoRequestedFields
.- In v2/pkg/engine/resolve/loader.go the JSON extension
"whoRequestedFields"
is injected (at line 1575) using that flag.- Tests in execution/engine/execution_engine_test.go and execution_engine_helpers_test.go uniformly use
"whoRequestedFields"
in their expected bodies.- The
createTestRoundTripper
implementations in both pkg/graphql and execution/engine compare the raw request body string, so formatting changes would break existing tests.No further action required here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
v2/pkg/engine/resolve/fetch.go (1)
20-26
: Critical: ImplementRequestedFields()
on all built-in Fetch types and verify downstream consumersI ran a quick grep for types that implement
FetchKind()
—the heuristic for Fetch implementers—and confirmed that none currently defineRequestedFields()
, so the interface change breaks internal code immediately:
- SingleFetch (v2/pkg/engine/resolve/fetch.go:163)
- BatchEntityFetch (v2/pkg/engine/resolve/fetch.go:217)
- EntityFetch (v2/pkg/engine/resolve/fetch.go:262)
- ParallelListItemFetch (v2/pkg/engine/resolve/fetch.go:287)
Please add a
RequestedFields() []RequestedField
method to each of these types (for example, returningnil
or the appropriate field list) so they satisfy the updated interface. For example:func (f *SingleFetch) RequestedFields() []RequestedField { return nil }Once internal types compile, also audit any out-of-tree implementations of
Fetch
in downstream repos. If you find external clients relying on the old interface, consider:
- Providing a default/no-op adapter that implements
RequestedFields()
- Introducing a wrapper interface to preserve backward compatibility
♻️ Duplicate comments (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
3296-3313
: Same consideration as the previous fetch about representation keys and determinism.The comments on Lines 3183–3201 apply here as well (potential inclusion of Address.id as ReasonIsKey and sorting of BySubgraphs for stability).
v2/pkg/engine/plan/visitor.go (1)
1337-1339
: Fix assignments to embedded FetchConfiguration to avoid compile error.SingleFetch does not (currently) declare CoordinateDependencies or FieldsRequestedBy at the top level; they live on the embedded FetchConfiguration. Assigning to the wrong receiver won’t compile.
Apply this diff:
- singleFetch.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) - singleFetch.FieldsRequestedBy = v.buildRequestedFields(internal.fetchID) + singleFetch.FetchConfiguration.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) + singleFetch.FetchConfiguration.FieldsRequestedBy = v.buildRequestedFields(internal.fetchID)To double-check the struct shape in your current branch, run:
#!/bin/bash # Inspect where FieldsRequestedBy is declared rg -nP 'type\s+SingleFetch\b[\s\S]*?\{' -n v2/pkg/engine/resolve/fetch.go -A30 rg -nP 'type\s+FetchConfiguration\b[\s\S]*?\{' -n v2/pkg/engine/resolve/fetch.go -A60 | sed -n '1,120p'
🧹 Nitpick comments (11)
v2/pkg/engine/resolve/fetch.go (4)
171-181
: Inconsistent storage location for FieldsRequestedBy across fetch typesBatchEntityFetch stores FieldsRequestedBy on the fetch itself, while SingleFetch keeps it in FetchConfiguration. Consider standardizing to a single location (preferably on FetchConfiguration for consistency) or add a short comment explaining the rationale to avoid confusion for contributors.
Apply a clarifying comment locally if you keep the current layout:
type BatchEntityFetch struct { FetchDependencies - + // FieldsRequestedBy contains the per-fetch field request provenance. + // Note: Unlike SingleFetch, this is stored on the fetch itself rather than FetchConfiguration. Input BatchInput DataSource DataSource PostProcessing PostProcessingConfiguration DataSourceIdentifier []byte Trace *DataSourceLoadTrace Info *FetchInfo CoordinateDependencies []FetchDependency FieldsRequestedBy []RequestedField }
226-234
: Mirror comment from BatchEntityFetch: storage consistencyEntityFetch mirrors BatchEntityFetch by storing FieldsRequestedBy on the struct itself and not in a FetchConfiguration. If you keep this asymmetry with SingleFetch/ParallelListItemFetch, please document it to prevent accidental future regressions.
350-354
: Equality semantics: FieldsRequestedBy and OperationName are not comparedFetchConfiguration.Equals does not compare FieldsRequestedBy or OperationName. If plan equality is used for deduplication or caching, differing values here won’t affect equality. That could be fine if these are diagnostics-only, but please confirm.
Option A (document intent): add an explicit comment in Equals:
func (fc *FetchConfiguration) Equals(other *FetchConfiguration) bool { @@ - // Note: we do not compare CoordinateDependencies, as they contain more detailed + // Note: we do not compare CoordinateDependencies, as they contain more detailed // dependencies information that is already present in the FetchDependencies on the fetch itself + // Note: we intentionally do not compare FieldsRequestedBy and OperationName, + // as they are used for diagnostics/propagation only and should not affect plan equality.Option B (enforce equality): include these fields in the comparison if they must influence plan identity.
416-425
: Shape and naming of RequestedField; consider extensibility and consistent casing
- The by/reason information is encoded as booleans on the whole coordinate. If a coordinate is requested by multiple subgraphs for different reasons, we lose per-subgraph reason fidelity. Consider a structure like per-subgraph entries or a reasons []string enum (["key","requires"]) for future-proofing.
- JSON key casing: struct fields use lowerCamelCase (requestedBySubgraphs/requestedByUser), while the request extension key added in loader.go is "FieldsRequestedBy" (PascalCase). Recommend aligning on lowerCamelCase for consistency with other extension fields (e.g., statusCode).
If you decide to compact the reasons:
type RequestedField struct { TypeName string `json:"typeName"` FieldName string `json:"fieldName"` - BySubgraphs []string `json:"requestedBySubgraphs,omitempty"` - ByUser bool `json:"requestedByUser,omitempty"` - ReasonIsKey bool `json:"reasonIsKey,omitempty"` - ReasonIsRequires bool `json:"reasonIsRequires,omitempty"` + BySubgraphs []string `json:"requestedBySubgraphs,omitempty"` + ByUser bool `json:"requestedByUser,omitempty"` + Reasons []string `json:"reasons,omitempty"` // e.g. ["key","requires"] }At minimum, ensure BySubgraphs is de-duplicated and stable-sorted before marshalling to keep payloads deterministic.
v2/pkg/engine/resolve/loader.go (2)
1565-1581
: Only inject when non-empty and align extension key casing; keep extensions object robust
- Skip injection when the slice is empty to reduce payload churn.
- Prefer "fieldsRequestedBy" (lowerCamelCase) to be consistent with other extension keys (e.g., statusCode).
- The jsonparser.Set call is correct and will create missing objects, but will error if extensions is not an object. Your error handling is good.
Apply this diff:
- fieldsRequestedBy := fetchItem.Fetch.RequestedFields() - if fieldsRequestedBy != nil { + fieldsRequestedBy := fetchItem.Fetch.RequestedFields() + if len(fieldsRequestedBy) > 0 { var encoded []byte encoded, res.err = json.Marshal(fieldsRequestedBy) if res.err != nil { res.err = errors.WithStack(res.err) return } - // We expect that body.extensions is an object - input, res.err = jsonparser.Set(input, encoded, "body", "extensions", "FieldsRequestedBy") + // Ensure body.extensions exists (jsonparser will create intermediate objects) and set the field. + input, res.err = jsonparser.Set(input, encoded, "body", "extensions", "fieldsRequestedBy") if res.err != nil { res.err = errors.WithStack(res.err) return } }If you anticipate frequent reuse of the same slice per fetch, consider precomputing and caching the marshalled bytes on the fetch to avoid per-request marshalling.
1565-1581
: Add coverage: verify propagation behavior across fetch kinds and introspection exclusionPlease add tests that assert:
- Nothing is injected for introspection data sources.
- fieldsRequestedBy is present only when the option is enabled.
- Injection works for SingleFetch, EntityFetch, BatchEntityFetch, and ParallelListItemFetch.
- Empty slices are not injected once the guard is added.
I can draft table-driven tests around executeSourceLoad/loadSingleFetch with fake DataSource stubs to validate the input body payloads. Let me know if you want me to push a test scaffolding.
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
3183-3201
: Should representation keys also appear in FieldsRequestedBy, or is this selection-set only?This fetch sends Address.id in variables.representations, but id isn’t included in FieldsRequestedBy. If the extension is meant to explain “why fields were included in the request” to the receiving subgraph, keys passed via variables may also be relevant (e.g., mark id with ReasonIsKey). If the intent is “selection set only,” consider documenting that and keeping keys out for noise reduction.
Would you like me to add a test/helper asserting the chosen semantics (selection-set-only vs selection+variables) so future changes don’t regress this?
execution/engine/execution_engine_test.go (2)
827-876
: Solid happy-path coverage for per-fetch extensions; one naming consideration.The round-tripper assertion verifies the extension payload and HTTP method switch to POST when extensions are present. Minor: consider aligning the extension key casing with existing keys like "valueCompletion" (camelCase) for consistency, e.g., "fieldsRequestedBy". If you decide to change it, we can update the tests and loader in one pass.
If you opt in, I can provide a scoped diff for the tests once the extension key name is finalized.
4505-4541
: Great federated coverage; suggest adding one more case for '@requires'.This test exercises keys and cross-subgraph dependency reporting. Consider adding a similar case with @requires to prove ReasonIsRequires toggles correctly and that requestedBySubgraphs attribution matches the providing subgraph.
Happy to draft a minimal schema+plan snippet for @requires if helpful.
v2/pkg/engine/plan/visitor.go (1)
1399-1461
: Requested-field computation is coherent and stable; minor nit on allocations.Logic cleanly:
- filters out __typename,
- flags user vs dependency,
- collects subgraph names from planners,
- marks key/requires,
- sorts and compacts.
Nit: FieldNameString and repeated slice appends can allocate in tight loops on large selections. Not urgent, but consider pre-sizing BySubgraphs using the max of fieldPlanners[reqByRef] lengths, or caching FieldNameString results if this shows up in profiles.
v2/pkg/engine/resolve/resolve.go (1)
168-171
: Clarify transport requirement in the option doc.Since extensions are carried in the GraphQL request body, this effectively requires HTTP POST to subgraphs. Add a note to avoid surprises when a datasource is configured for GET.
Apply this doc tweak:
- // PropagateFieldsRequestedBy enables sending to upstream subgraphs (only) the "FieldsRequestedBy" - // extension that explains why each field was requested. This flag does not expose the data to clients. + // PropagateFieldsRequestedBy enables sending to upstream subgraphs (only) the "FieldsRequestedBy" + // extension that explains why each field was requested. This flag does not expose the data to clients. + // Note: This requires GraphQL over HTTP POST (JSON body). GET datasources will not include this extension.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
(4 hunks)v2/pkg/engine/plan/visitor.go
(7 hunks)v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
(2 hunks)v2/pkg/engine/resolve/fetch.go
(11 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
🧰 Additional context used
🧬 Code graph analysis (6)
v2/pkg/engine/resolve/loader.go (1)
v2/pkg/engine/resolve/fetch.go (1)
Fetch
(20-26)
execution/engine/execution_engine_test.go (4)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (1)
DataSourceMetadata
(34-56)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-354)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
v2/pkg/engine/resolve/resolve.go (1)
v2/pkg/engine/resolve/resolvable.go (1)
ResolvableOptions
(62-67)
v2/pkg/engine/resolve/fetch.go (2)
v2/pkg/engine/plan/datasource_configuration.go (1)
DataSource
(256-269)v2/pkg/engine/resolve/datasource.go (1)
DataSource
(12-15)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (11)
v2/pkg/engine/resolve/fetch.go (5)
108-110
: LGTM: SingleFetch.RequestedFields exposes per-fetch metadataSimple passthrough to FetchConfiguration.FieldsRequestedBy. Looks good.
126-142
: Docs-only tweaks in PostProcessingConfigurationNon-functional comment changes; nothing to flag.
191-194
: LGTM: BatchEntityFetch.RequestedFields passthroughStraightforward accessor.
244-247
: LGTM: EntityFetch.RequestedFields passthroughAccessor reads from the struct field as expected.
283-286
: LGTM: ParallelListItemFetch.RequestedFields delegates to underlying SingleFetchCorrectly exposes the same metadata per list item fetch.
v2/pkg/engine/resolve/loader.go (1)
154-155
: ✅ propagateFieldsRequestedBy correctly wired (default remains false)All looks wired up as intended:
- In v2/pkg/engine/resolve/resolve.go (line 253),
options.PropagateFieldsRequestedBy
is passed through into the loader’spropagateFieldsRequestedBy
field.- The
Loader
struct in v2/pkg/engine/resolve/loader.go definespropagateFieldsRequestedBy bool
, so the flag has a proper home.- Because Go’s zero value for
bool
isfalse
and there’s no override, existing users won’t see any payload changes by default.No changes needed here.
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (2)
3420-3431
: LGTM: user-requested fields are captured cleanly at the final fetch.Capturing Address.fullAddress and Account.address as ByUser=true aligns with the intent of the extension. No issues spotted.
3097-3135
: BySubgraphs ordering is already deterministic in buildRequestedFieldsI checked v2/pkg/engine/plan/visitor.go around lines 1453–1455, and you’ll see we already call slices.Sort(requested.BySubgraphs) followed by slices.Compact(requested.BySubgraphs) before returning the slice. That guarantees the same lexicographical order of BySubgraphs every run, so this test won’t flake. No changes needed here.
execution/engine/execution_engine_test.go (2)
218-219
: Plumbs the new test knob cleanly.Adding propagateFieldsRequestedBy to the local options struct is straightforward and keeps the feature flag scoped to tests. LGTM.
266-267
: ResolverOptions wiring looks correct.PropagateFieldsRequestedBy is passed through to the engine; no other behavior is altered. LGTM.
v2/pkg/engine/resolve/resolve.go (1)
237-256
: Loader wiring LGTM; validate GET-to-POST behavior when needed.Flag propagation to Loader is correct. One operational consideration: if a datasource is configured with Method: GET and the flag is enabled, do we silently omit the extension or upgrade to POST? Either behavior is fine, but it should be explicit.
If the current behavior is “omit”, consider logging once per fetch when the flag is set but the transport can’t carry extensions. If you prefer “upgrade”, we can add a guarded switch in the GraphQL loader path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
v2/pkg/engine/plan/visitor.go (1)
49-66
: The grep output shows no assignments or initializations forplannerFields
,fieldPlanners
, orfieldEnclosingTypeNames
anywhere inv2/pkg/engine/plan
. OnlyfieldRefAllowsFieldRefs
is populated viainverseMap
in the planner. The other new maps remain uninitialized and will be nil at runtime.Please add initialization and population logic for these maps in your planner setup—similar to how you handle
fieldRefAllowsFieldRefs
—to ensure they’re safe to read:• In
planner.go
, after constructingplanningVisitor
, initialize:
plannerFields = make(map[int][]int)
fieldPlanners = make(map[int][]int)
fieldEnclosingTypeNames = make(map[int]string)
• Populate each map from
selectionsConfig
(or other source) before invokingplanningWalker.ResetVisitors()
.Once these are wired up, your visitor’s reads for these maps will never encounter nil/map lookups.
♻️ Duplicate comments (1)
execution/engine/execution_engine_test.go (1)
4336-4473
: Nested federation test: expectations and metadata align with planner semantics.
- expectedBody1’s FieldsRequestedBy covers: User.id (byUser and bySubgraphs with reasonIsKey) and Admin.some (byUser). Matches ProtectedFieldNames and dependency chain.
- expectedBody2 intentionally has no extensions (no protected fields on subgraph 2). This fixes the earlier “some” leak.
LGTM.
🧹 Nitpick comments (10)
v2/pkg/engine/plan/type_field.go (3)
4-7
: Document field semantics and intent (esp. “Protected”).Add brief GoDoc-style comments to avoid ambiguity and help downstream users.
type TypeField struct { - TypeName string - FieldNames []string - ExternalFieldNames []string - ProtectedFieldNames []string + TypeName string // GraphQL type name + FieldNames []string // Fields owned/resolved by this subgraph + ExternalFieldNames []string // Fields that are externally resolved (federated) + ProtectedFieldNames []string // Fields included due to auth/policy requirements }If you also want a type-level comment (outside the changed lines):
// TypeField describes fields associated with a GraphQL type, including // externally resolved and authorization-protected fields.
4-7
: Enforce uniqueness and deterministic ordering of name slices.Downstream set-membership and diff-based tests benefit from canonicalized data. Consider normalizing when constructing instances.
// Helper: returns sorted unique copy. func uniqueSorted(in []string) []string { if len(in) == 0 { return nil } seen := make(map[string]struct{}, len(in)) out := make([]string, 0, len(in)) for _, s := range in { if _, ok := seen[s]; ok { continue } seen[s] = struct{}{} out = append(out, s) } slices.Sort(out) // Go 1.21+ return out } func NewTypeField(typeName string, fields, external, protected []string) TypeField { return TypeField{ TypeName: typeName, FieldNames: uniqueSorted(fields), ExternalFieldNames: uniqueSorted(external), ProtectedFieldNames: uniqueSorted(protected), } }
4-7
: If serialized, prefer non-nil slices for stable JSON shape.If TypeField is marshaled into subgraph request extensions, decide between null vs [] and keep it consistent (initialize to empty slices if needed).
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (3)
1086-1089
: ProtectedFieldNames on Address: confirm intent and keep consistent across subgraphsYou protect id, line1, line2 here. Later for Address in address.service you only protect line3, zip. If asymmetry is intentional, add a short comment; otherwise, align both (e.g., protect id in both or neither).
1268-1271
: ProtectedFieldNames asymmetry with users subgraphHere id is not protected, unlike users.service. Consider adding id for consistency, or document why it's different.
Apply if you want to align:
- ProtectedFieldNames: []string{"line3", "zip"}, + ProtectedFieldNames: []string{"id", "line3", "zip"},
3263-3276
: Requires reasons captured well; consider expanding coverageZip and line3 are correctly marked ReasonIsRequires by account.service. Optionally add a case where ByUser=true is exercised, and/or a fetch showing requires from a sibling (e.g., address-enricher) to validate multi-hop attribution.
execution/engine/execution_engine_test.go (1)
827-879
: Good positive test for extension payload on simple hero.
- Body asserts the extensions.FieldsRequestedBy structure and key naming.
- ProtectedFieldNames on Query.hero and Character.name align with filter logic.
Consider also asserting absence when flag is off for this same scenario (mirrors nested test). Optional.
v2/pkg/engine/plan/datasource_configuration.go (1)
99-146
: Micro-optimization: pre-size child maps.You pre-sized root maps using slice lengths; do the same for child to reduce rehashing.
- if _, ok := d.childNodesIndex[typeName]; !ok { - d.childNodesIndex[typeName] = fieldsIndex{ - fields: make(map[string]struct{}), - externalFields: make(map[string]struct{}), - protectedFields: make(map[string]struct{}), - } - } + if _, ok := d.childNodesIndex[typeName]; !ok { + d.childNodesIndex[typeName] = fieldsIndex{ + fields: make(map[string]struct{}, len(d.ChildNodes[i].FieldNames)), + externalFields: make(map[string]struct{}, len(d.ChildNodes[i].ExternalFieldNames)), + protectedFields: make(map[string]struct{}, len(d.ChildNodes[i].ProtectedFieldNames)), + } + }v2/pkg/engine/plan/visitor.go (2)
636-646
: Potential hot path: convert to set if it grows.You already noted switching to map if skipFieldsRefs expands. Keep an eye on size in large ops.
1399-1466
: Requested-fields builder works; consider deduping by coordinate.Today, identical coordinates can appear multiple times (e.g., byUser and bySubgraphs for User.id), inflating payloads. Optional: merge per (typeName, fieldName) to a single entry with combined flags and sources for simpler consumption and smaller bodies.
Example minimal refactor inside this block:
- result := make([]resolve.RequestedField, 0, len(fields)) + merged := make(map[string]*resolve.RequestedField, len(fields)) ... - requested := resolve.RequestedField{ TypeName: typeName, FieldName: fieldName } + key := typeName + "\x00" + fieldName + rf, ok := merged[key] + if !ok { + rf = &resolve.RequestedField{ TypeName: typeName, FieldName: fieldName } + merged[key] = rf + } + requested := *rf ... - if requested.ByUser || len(requested.BySubgraphs) > 0 { - slices.Sort(requested.BySubgraphs) - requested.BySubgraphs = slices.Compact(requested.BySubgraphs) - result = append(result, requested) - } + if requested.ByUser || len(requested.BySubgraphs) > 0 { + slices.Sort(requested.BySubgraphs) + requested.BySubgraphs = slices.Compact(requested.BySubgraphs) + *rf = requested + } ... - return result + // flatten in stable order + out := make([]resolve.RequestedField, 0, len(merged)) + for _, v := range merged { + out = append(out, *v) + } + slices.SortFunc(out, func(a, b resolve.RequestedField) int { + if a.TypeName == b.TypeName { + if a.FieldName == b.FieldName { return 0 } + if a.FieldName < b.FieldName { return -1 } + return 1 + } + if a.TypeName < b.TypeName { return -1 } + return 1 + }) + return outNote: would require test updates if order changes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (8)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
(4 hunks)v2/pkg/engine/plan/datasource_configuration.go
(5 hunks)v2/pkg/engine/plan/federation_metadata.go
(1 hunks)v2/pkg/engine/plan/type_field.go
(1 hunks)v2/pkg/engine/plan/visitor.go
(7 hunks)v2/pkg/engine/resolve/fetch.go
(11 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- v2/pkg/engine/resolve/loader.go
- v2/pkg/engine/resolve/fetch.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-09T09:34:51.269Z
Learnt from: SkArchon
PR: wundergraph/graphql-go-tools#1218
File: v2/pkg/engine/resolve/loader.go:58-60
Timestamp: 2025-07-09T09:34:51.269Z
Learning: In Go's bytes package, the String() method on *bytes.Buffer handles nil receivers gracefully by returning an empty string rather than panicking, making additional nil checks unnecessary when calling String() on potentially nil *bytes.Buffer instances.
Applied to files:
v2/pkg/engine/plan/federation_metadata.go
🧬 Code graph analysis (3)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
v2/pkg/engine/resolve/fetch.go (1)
RequestedField
(418-425)
execution/engine/execution_engine_test.go (4)
v2/pkg/engine/plan/datasource_configuration.go (2)
DataSource
(293-306)DataSourceMetadata
(34-56)v2/pkg/engine/plan/type_field.go (1)
TypeField
(3-8)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-354)
🔇 Additional comments (13)
v2/pkg/engine/plan/federation_metadata.go (1)
108-111
: Confirm value-based fmt.Stringer usage for FederationFieldConfigurationI ran targeted searches but found no direct uses of
fmt.Print*
on value instances or explicit calls toString()
/Format()
onFederationFieldConfiguration
. However, absence of matches isn’t proof of safety. Please manually verify that no code paths pass a value (non-pointer) tofmt
verbs expectingfmt.Stringer
, as the pointer-receiver change alters:
- Value-based
fmt.Sprintf("%s", fValue)
will no longer callString()
, producing Go’s default struct formatting instead of JSON.- A nil-pointer
f
now marshals to"null"
rather than{}
; confirm this semantic shift is acceptable.If you need backward-compatible behavior, consider:
- Adding an explicit nil guard in
String()
:func (f *FederationFieldConfiguration) String() string { + if f == nil { + return "null" + } b, _ := json.Marshal(f) return string(b) }- Implementing a
Format
on the value receiver to preserve%s
/%v
behavior:func (f FederationFieldConfiguration) Format(s fmt.State, verb rune) { _, _ = fmt.Fprint(s, (&f).String()) }Please review all formatting and logging sites to ensure no regressions occur.
v2/pkg/engine/plan/type_field.go (1)
4-7
: No lingering usages of removed TypeFields helpersI’ve verified that there are no remaining calls to HasNode, HasExternalNode, or HasNodeWithTypename across the codebase. The only matches for
TypeFields.*.
are in type signatures and function definitions (not method invocations on the struct), so all helper call sites have been successfully removed.v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
3099-3122
: No action needed—BySubgraphs is already sorted and deduplicated
The implementation ofbuildRequestedFields
inv2/pkg/engine/plan/visitor.go
callsslices.Sort(requested.BySubgraphs)
followed byslices.Compact(requested.BySubgraphs)
before returning its results (lines 1459–1461). This ensures a deterministic, lexicographically ordered, and deduplicated slice of subgraph names, matching the test’s expectations. You can ignore the suggestion to add sorting logic here.Likely an incorrect or invalid review comment.
execution/engine/execution_engine_test.go (3)
218-219
: Test option plumbing looks good.Adding propagateFieldsRequestedBy to _executionTestOptions is clear and default-safe.
262-267
: ResolverOptions flag wired correctly.PropagateFieldsRequestedBy is passed through to the engine; no regressions seen.
4503-4543
: Nice A/B coverage.
- “run” (flag off) vs “run with extension” (flag on) validates toggle behavior for nested fetches.
v2/pkg/engine/plan/datasource_configuration.go (4)
54-56
: Indexing by typeName improves lookup performance.The new root/child indexes with protectedFields support are appropriate for fast membership checks.
67-76
: NodesInfo expanded coherently.HasProtectedRootNode/HasProtectedChildNode API is consistent with existing Has* methods.
178-188
: Protected-root accessor is straightforward and safe on nil indexes.LGTM.
223-233
: Protected-child accessor mirrors root accessor correctly.LGTM.
v2/pkg/engine/plan/visitor.go (3)
342-345
: Capturing enclosing type names early is necessary for coordinates.This enables stable GraphCoordinate and extension entries.
211-223
: Recording planner-field associations on LeaveField is correct.Post-order ensures children precede parents, matching test ordering of FieldsRequestedBy.
1344-1397
: Dependency extraction to GraphCoordinates looks consistent.Uses fieldEnclosingTypeNames and dependencyKind to flag IsKey/IsRequires. LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
v2/pkg/engine/plan/visitor.go (1)
1336-1339
: Assignments to promoted fields are valid; previous compile error is resolvedUsing singleFetch.CoordinateDependencies and singleFetch.FieldsRequestedBy works via the embedded FetchConfiguration; no need to qualify further.
🧹 Nitpick comments (5)
v2/pkg/engine/plan/visitor.go (2)
636-646
: Consider O(1) membership for skipFieldsRefsLinear scan is fine for small slices; if this grows, switch to a map[int]struct{} or build a lazy index once per document.
func (v *Visitor) skipField(ref int) bool { - // If this grows, switch to map[int]struct{} for O(1). - for _, skipRef := range v.skipFieldsRefs { - if skipRef == ref { - return true - } - } - return false + // If this grows, switch to map[int]struct{} for O(1). + for _, skipRef := range v.skipFieldsRefs { + if skipRef == ref { + return true + } + } + return false }
1399-1489
: Prevent duplicate BySubgraphs entries when merging duplicatesIf the same (type, field) appears multiple times in plannerFields, the merge path appends BySubgraphs without re-deduping, leading to duplicates.
Apply this minimal fix inside the merge branch:
- if i, ok := index[key]; ok { + if i, ok := index[key]; ok { // True should overwrite false. result[i].ByUser = result[i].ByUser || byUser if len(bySubgraphs) > 0 { - result[i].BySubgraphs = append(result[i].BySubgraphs, bySubgraphs...) + result[i].BySubgraphs = append(result[i].BySubgraphs, bySubgraphs...) + slices.Sort(result[i].BySubgraphs) + result[i].BySubgraphs = slices.Compact(result[i].BySubgraphs) result[i].ReasonIsKey = result[i].ReasonIsKey || isKey result[i].ReasonIsRequires = result[i].ReasonIsRequires || isRequires } continue }v2/pkg/engine/resolve/fetch.go (3)
350-351
: Document semantics of FieldsRequestedBy on FetchConfigurationAdd a short doc comment: protected fields only; used for upstream extension propagation; no execution impact.
type FetchConfiguration struct { @@ - FieldsRequestedBy []RequestedField + // FieldsRequestedBy contains provenance for protected fields (schema @protected). + // Used for optional propagation to subgraphs via request extensions; does not affect execution. + FieldsRequestedBy []RequestedField
356-386
: Clarify equality: explicitly ignore FieldsRequestedByEquals already ignores CoordinateDependencies; call out FieldsRequestedBy too to avoid confusion.
// Note: we do not compare CoordinateDependencies, as they contain more detailed // dependencies information that is already present in the FetchDependencies on the fetch itself + // Note: we also intentionally do not compare FieldsRequestedBy, as it is diagnostic/telemetry-only + // and does not affect fetch execution semantics.
416-425
: Align JSON field names with existing GraphCoordinate conventionsUse typeName/fieldName for consistency and to avoid conflation with GraphQL’s __typename.
type RequestedField struct { - TypeName string `json:"__typename"` - FieldName string `json:"field"` + TypeName string `json:"typeName"` + FieldName string `json:"fieldName"` BySubgraphs []string `json:"bySubgraphs,omitempty"` ByUser bool `json:"byUser,omitempty"` ReasonIsKey bool `json:"reasonIsKey,omitempty"` ReasonIsRequires bool `json:"reasonIsRequires,omitempty"` }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/plan/visitor.go
(7 hunks)v2/pkg/engine/resolve/fetch.go
(11 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- v2/pkg/engine/resolve/loader.go
- execution/engine/execution_engine_test.go
🔇 Additional comments (9)
v2/pkg/engine/plan/visitor.go (2)
49-49
: Good addition: inverse dependency map capturedStoring the inverse mapping (fieldRefAllowsFieldRefs) on the visitor simplifies reasoning when assembling “requested by” provenance.
58-66
: Planner/field tracking maps look correctThe three maps (plannerFields, fieldPlanners, fieldEnclosingTypeNames) are initialized in EnterDocument and populated in AllowVisitor/EnterField — solid groundwork for per-fetch attribution.
v2/pkg/engine/resolve/fetch.go (7)
104-111
: OK: methods use promoted fields from embedded FetchConfigurationReturning s.CoordinateDependencies / s.FieldsRequestedBy via embedding keeps the API simple.
171-181
: BatchEntityFetch wiring looks consistentNew storage and accessor for FieldsRequestedBy aligns with SingleFetch semantics.
191-194
: OK: BatchEntityFetch.RequestedFields implemented
224-234
: EntityFetch carries FieldsRequestedBy and CoordinateDependenciesMatches BatchEntityFetch; consistent across variants.
244-247
: OK: EntityFetch.RequestedFields implemented
283-285
: OK: ParallelListItemFetch delegates to underlying SingleFetch
25-26
: Ensure all Fetch implementations define RequestedFields()The
Fetch
interface inv2/pkg/engine/resolve/fetch.go
was extended with:RequestedFields() []RequestedFieldBecause Go requires that every type implementing an interface must define all its methods, please manually verify that each concrete type providing a
FetchKind()
method also implementsRequestedFields()
. A quick way to locate them is:rg -nP 'func\s*\(\*?[A-Za-z_][A-Za-z0-9_]*\)\s*FetchKind\(' -C1 v2/pkg/engine/resolve
Then, for every listed type
T
, ensure there is:func (t *T) RequestedFields() []RequestedField { // TODO: return the list of fields this fetch operation needs }This check couldn’t be fully automated with the previous script—absence of results isn’t evidence that everything is covered. Please confirm by eyeballing or enhancing the search pattern to match any variations in receiver names or return-type qualifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
v2/pkg/engine/resolve/fetch.go (1)
420-429
: Unify JSON field names with GraphCoordinate; avoid “__typename”.To prevent confusion with GraphQL’s introspection field and to keep consistency with GraphCoordinate {typeName, fieldName}, prefer typeName/fieldName over __typename/field. This is an externalized API—best to settle it now.
type RequestedField struct { - TypeName string `json:"__typename"` - FieldName string `json:"field"` + TypeName string `json:"typeName"` + FieldName string `json:"fieldName"` BySubgraphs []string `json:"bySubgraphs,omitempty"` ByUser bool `json:"byUser,omitempty"` ReasonIsKey bool `json:"reasonIsKey,omitempty"` ReasonIsRequires bool `json:"reasonIsRequires,omitempty"` }Follow-ups:
- Update loader serialization and tests expecting the old keys.
- If different reasons apply per subgraph, consider future-proofing with a per-subgraph detail structure; otherwise clarify that reasons are aggregated across the fetch.
🧹 Nitpick comments (5)
v2/pkg/engine/resolve/fetch.go (5)
20-26
: Document semantics of the new interface method.Add a brief contract so callers know it’s per-fetch, protected-only, and read-only.
type Fetch interface { FetchKind() FetchKind Dependencies() *FetchDependencies DataSourceInfo() DataSourceInfo DependenciesCoordinates() []FetchDependency + // RequestedFields returns provenance for protected fields used by this fetch. + // The returned slice must be treated as read-only and may be nil. RequestedFields() []RequestedField }
108-110
: Return a cloned slice to avoid external mutation.Expose a defensive copy so callers can’t mutate internal state.
func (s *SingleFetch) RequestedFields() []RequestedField { - return s.FieldsRequestedBy + return slices.Clone(s.FieldsRequestedBy) }
191-194
: Defensive copy for requested fields.Mirror SingleFetch behavior here.
func (b *BatchEntityFetch) RequestedFields() []RequestedField { - return b.FieldsRequestedBy + return slices.Clone(b.FieldsRequestedBy) }
244-247
: Defensive copy for requested fields.Keep the slice immutable to callers.
func (e *EntityFetch) RequestedFields() []RequestedField { - return e.FieldsRequestedBy + return slices.Clone(e.FieldsRequestedBy) }
283-285
: Defensive copy for requested fields.Mirror other getters to prevent accidental mutation.
func (p *ParallelListItemFetch) RequestedFields() []RequestedField { - return p.Fetch.FieldsRequestedBy + return slices.Clone(p.Fetch.FieldsRequestedBy) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
v2/pkg/engine/resolve/fetch.go
(12 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (7)
v2/pkg/engine/resolve/fetch.go (7)
104-106
: LGTM: use promoted field for deps.Switching to s.CoordinateDependencies removes unnecessary coupling to FetchConfiguration.
127-142
: Docs improvement LGTM.The expanded post-processing comments are clearer.
170-181
: Per-fetch storage on BatchEntityFetch: ensure stable, deduped content.Looks good. Upstream builder should dedupe and sort FieldsRequestedBy (by typeName, fieldName) for deterministic payloads and tests.
Would you like me to add a sorting step in the planner where the slice is built?
224-234
: EntityFetch wiring LGTM.Field reordering plus per-fetch provenance storage is consistent with other fetch types.
279-282
: LGTM: use promoted deps from inner fetch.Keeps ParallelListItemFetch thin and consistent.
350-356
: Good placement and equality semantics.Storing FieldsRequestedBy on FetchConfiguration and excluding it from Equals keeps plan equality stable while enabling propagation. Consider explicitly documenting that a nil slice should suppress the extension entirely in loader.
368-373
: Equality exclusions LGTM.Not comparing derived metadata (CoordinateDependencies, FieldsRequestedBy) is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
execution/engine/execution_engine_test.go (1)
4357-4358
: Should second subgraph also receive fetch_reasons?expectedBody2 omits extensions. If the intention is to propagate only for types/fields marked via FetchReasonFields, this is fine. Consider adding a variant where id-2 has FetchReasonFields including title and assert the extension is present to cover the multi-subgraph case.
🧹 Nitpick comments (11)
v2/pkg/engine/plan/datasource_configuration.go (4)
79-82
: Optional: compress three sets into one bitmask map to reduce memoryIf memory becomes a concern on large schemas, replace three maps with one map[string]uint8 and use bit flags (field/external/fetchReason). Keeps lookups O(1) while cutting overhead.
129-134
: Nit: preallocate child maps like root mapsMinor perf consistency: pre-size child maps using the known lengths (mirrors root indexing).
Apply:
- d.childNodesIndex[typeName] = fieldsIndex{ - fields: make(map[string]struct{}), - externalFields: make(map[string]struct{}), - fetchReasonFields: make(map[string]struct{}), - } + d.childNodesIndex[typeName] = fieldsIndex{ + fields: make(map[string]struct{}, len(d.ChildNodes[i].FieldNames)), + externalFields: make(map[string]struct{}, len(d.ChildNodes[i].ExternalFieldNames)), + fetchReasonFields: make(map[string]struct{}, len(d.ChildNodes[i].FetchReasonFields)), + }
178-189
: DRY the Has methods via a small helper*Several Has* methods duplicate nil/typeName checks. Consider a private helper to reduce repetition and future drift.
Add outside the changed ranges:
func hasField(idx map[string]fieldsIndex, typeName, fieldName string, sel func(fieldsIndex) map[string]struct{}) bool { if idx == nil { return false } fi, ok := idx[typeName] if !ok { return false } _, ok = sel(fi)[fieldName] return ok } // Then: func (d *DataSourceMetadata) HasFetchReasonRootNode(typeName, fieldName string) bool { return hasField(d.rootNodesIndex, typeName, fieldName, func(fi fieldsIndex) map[string]struct{} { return fi.fetchReasonFields }) }
223-234
: Apply the same helper for child fetch-reason checksReuse the helper for symmetry with root checks.
execution/engine/execution_engine_test.go (3)
218-219
: Unify option naming: use “PropagateFetchReasons” consistently.Tests use propagateFieldsRequestedBy while ResolverOptions uses PropagateFetchReasons. Rename the test toggle for clarity and grep-ability.
- propagateFieldsRequestedBy bool + propagateFetchReasons bool @@ - PropagateFetchReasons: opts.propagateFieldsRequestedBy, + PropagateFetchReasons: opts.propagateFetchReasons,Also update the call sites:
- eto.propagateFieldsRequestedBy = true + eto.propagateFetchReasons = trueAlso applies to: 266-267
827-875
: Good coverage of request extension payload for simple fetch.POST + extensions.fetch_reasons with expected keys and values looks correct. Consider adding a negative test that uses POST with propagation disabled to assert the extension is omitted (guards against regressions).
214-221
: Optional: table-driven flag wiring.executionTestOptions is growing. Consider table-driving flags in tests to reduce boilerplate.
v2/pkg/engine/plan/visitor.go (2)
636-646
: skipField is O(n); switch to a set if this grows.If skipFieldsRefs becomes large, convert to map[int]struct{} for O(1) lookups.
- for _, skipRef := range v.skipFieldsRefs { - if skipRef == ref { return true } - } + if v.skipFieldsSet != nil { + _, ok := v.skipFieldsSet[ref] + return ok + }
1399-1490
: Deterministic ordering for fetch_reasons to avoid flaky tests.Order of plannerFields can vary; sort reasons by (TypeName, FieldName) before returning.
func (v *Visitor) buildFetchReasons(fetchID int) []resolve.FetchReason { @@ - return reasons + // Ensure stable order for deterministic request payloads + slices.SortFunc(reasons, func(a, b resolve.FetchReason) int { + if c := strings.Compare(a.TypeName, b.TypeName); c != 0 { + return c + } + return strings.Compare(a.FieldName, b.FieldName) + }) + return reasons }v2/pkg/engine/resolve/fetch.go (2)
421-431
: Align JSON field names across payloads.FetchReason uses typename/field while GraphCoordinate uses typeName/fieldName. Prefer a single convention (camelCase) for client ergonomics.
-type FetchReason struct { - TypeName string `json:"typename"` - FieldName string `json:"field"` - FromSubgraphs []string `json:"from_subgraphs,omitempty"` - FromUser bool `json:"from_user,omitempty"` - IsKey bool `json:"is_key,omitempty"` - IsRequires bool `json:"is_requires,omitempty"` -} +type FetchReason struct { + TypeName string `json:"typeName"` + FieldName string `json:"fieldName"` + FromSubgraphs []string `json:"fromSubgraphs,omitempty"` + FromUser bool `json:"fromUser,omitempty"` + IsKey bool `json:"isKey,omitempty"` + IsRequires bool `json:"isRequires,omitempty"` +}Note: requires updating tests’ expected bodies accordingly.
350-354
: Docstring: clarify scope (“protected fields”).Consider clarifying that FieldFetchReasons are produced only for fields marked in TypeField.FetchReasonFields (proxy for “protected” fields) to align repo docs with behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
(4 hunks)v2/pkg/engine/plan/datasource_configuration.go
(5 hunks)v2/pkg/engine/plan/type_field.go
(1 hunks)v2/pkg/engine/plan/visitor.go
(7 hunks)v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
(2 hunks)v2/pkg/engine/resolve/fetch.go
(12 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- v2/pkg/engine/resolve/loader.go
- v2/pkg/engine/plan/type_field.go
- v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
- v2/pkg/engine/resolve/resolve.go
🧰 Additional context used
🧬 Code graph analysis (3)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (1)
v2/pkg/engine/resolve/fetch.go (1)
FetchReason
(423-430)
execution/engine/execution_engine_test.go (5)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (2)
DataSource
(293-306)DataSourceMetadata
(34-56)v2/pkg/engine/plan/type_field.go (1)
TypeField
(3-8)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-357)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
FetchReason
(423-430)
🔇 Additional comments (15)
v2/pkg/engine/plan/datasource_configuration.go (4)
54-56
: LGTM: type-scoped indexes for fast lookupsPer-type maps for root/child node indexes are clear and efficient.
108-115
: LGTM: root index preallocationGood use of capacities for fields/external/fetchReason maps to minimize rehashing.
116-124
: LGTM: root index populationStraightforward, safe writes; duplicates naturally deduped by map set.
136-144
: LGTM: child index populationConsistent with root population; read-only after init so safe for concurrent reads.
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go (2)
1086-1089
: LGTM: opt-in list for reason-capturing looks precise.The
FetchReasonFields
forAddress
(id
,line1
,line2
) is a sensible minimal set. Please ensure we have (or add) a negative test asserting that fields not listed here never appear inFieldFetchReasons
, to prevent future regressions.
1268-1271
: LGTM: targeted reasons onaddress.service
.Limiting
FetchReasonFields
toline3
andzip
onAddress
keeps the signal focused. Same request as above: consider a check that otherAddress
fields are not emitted into fetch reasons for this DS.execution/engine/execution_engine_test.go (3)
839-839
: Confirm key casing for extension fields.You serialize reasons with keys typename and field. Elsewhere (GraphCoordinate) you use typeName and fieldName. Decide on a single convention across all payloads to avoid client confusion.
Would you like to standardize on camelCase (typeName, fieldName) and update tests accordingly?
4507-4542
: Nice end-to-end validation of nested propagation.run with extension toggles propagation and reuses the shared data source builder. Looks good.
4349-4472
: ```shell
#!/bin/bashSearch for configureFetch invocations to verify it's called after fieldRefAllowsFieldRefs is set
rg -nC3 'configureFetch' -n v2/pkg/engine
</blockquote></details> <details> <summary>v2/pkg/engine/plan/visitor.go (3)</summary><blockquote> `207-223`: **Planner-to-field bookkeeping looks correct.** Storing plannerFields and fieldPlanners on LeaveField prevents duplication and keeps fetch-local ordering. --- `1336-1339`: **Correct placement of dependencies and reasons on SingleFetch.** Assigning CoordinateDependencies and FieldFetchReasons here is the right layer. --- `49-66`: **Inverse and direct dependency maps are populated correctly** In NewPlanner (`planner.go`), `fieldRefAllowsFieldRefs` is initialized via `inverseMap(selectionsConfig.fieldRefDependsOn)`; in `Visitor.LeaveField` (`visitor.go`), `plannerFields`, `fieldPlanners` and `fieldEnclosingTypeNames` are appended for each field. </blockquote></details> <details> <summary>v2/pkg/engine/resolve/fetch.go (3)</summary><blockquote> `20-26`: **Interface extension is coherent.** Adding FetchReasons() to Fetch and implementing on all fetch kinds is consistent with the propagation feature. --- `100-111`: **Accessors correctly read promoted/own fields.** Returning CoordinateDependencies and FieldFetchReasons from the right structs looks good. Also applies to: 187-194, 240-247, 279-285 --- `350-357`: **Treating FieldFetchReasons as derived metadata is correct.** Excluding FieldFetchReasons (and CoordinateDependencies) from Equals prevents false cache misses. Also applies to: 369-374 </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
Outdated
Show resolved
Hide resolved
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
Outdated
Show resolved
Hide resolved
@devsergiy, Things were renamed using the agreed terminology. Open for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
v2/pkg/engine/resolve/resolve.go (2)
152-154
: Fix doc comment mismatch with field nameComment mentions AllowedCustomSubgraphErrorFields but the field is AllowedSubgraphErrorFields.
- // AllowedCustomSubgraphErrorFields defines which fields are allowed in the subgraph error when in passthrough mode + // AllowedSubgraphErrorFields defines which fields are allowed in the subgraph error when in passthrough mode
164-167
: Align fetch-reasons option naming between Resolver and Loader
- Rename Resolver option PropagateFetchReasons → PropagateFieldFetchReasons to match loader flag propagateFieldFetchReasons.
- Clarify comment: PropagateFieldFetchReasons enables sending the “fetch_reasons” extension under response.body.extensions to upstream GraphQL subgraphs; never exposed to clients.
Apply:
--- a/v2/pkg/engine/resolve/resolve.go @@ -164,7 +164,7 @@ type Options struct { - // PropagateFetchReasons enables sending to upstream subgraphs (only) the "fetch_reasons" - // extension that explains why each field was requested. This flag does not expose the data to clients. - PropagateFetchReasons bool + // PropagateFieldFetchReasons enables sending the "fetch_reasons" extension + // under response.body.extensions to upstream GraphQL subgraphs; never exposed to clients. + PropagateFieldFetchReasons bool--- a/v2/pkg/engine/resolve/resolve.go @@ -249 +249 - propagateFieldFetchReasons: options.PropagateFetchReasons, + propagateFieldFetchReasons: options.PropagateFieldFetchReasons,Also update all references (including execution_engine_test.go).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
v2/pkg/engine/resolve/resolve.go
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/resolve/resolve.go (1)
v2/pkg/engine/resolve/resolvable.go (1)
ResolvableOptions
(62-67)
🔇 Additional comments (1)
v2/pkg/engine/resolve/resolve.go (1)
169-169
: Doc: New() comment tweak looks goodNo issues.
v2/pkg/engine/plan/visitor.go
Outdated
@@ -46,6 +47,7 @@ type Visitor struct { | |||
skipFieldsRefs []int | |||
fieldRefDependsOnFieldRefs map[int][]int | |||
fieldDependencyKind map[fieldDependencyKey]fieldDependencyKind | |||
fieldRefAllowsFieldRefs map[int][]int // inverse of fieldRefDependsOnFieldRefs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe fieldRefDependencyForFieldRefs
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. It is a tricky stuff. I did not want it to be similar to the other field "fieldRefDependsOnFieldRefs"...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about fieldRefDependants
? Dependant is the inverse of "depends on".
@@ -54,13 +56,13 @@ type Visitor struct { | |||
indirectInterfaceFields map[int]indirectInterfaceField | |||
pathCache map[astvisitor.VisitorKind]map[int]string | |||
|
|||
// plannerFields tells which fields are planned on which planners | |||
// map plannerID -> []fieldRef | |||
// plannerFields maps plannerID to fieldRefs planned on this planner. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to keep an explicit explanation of what the key and the value are in a map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even better will be explanation like this
map[plannerID][]fieldRef
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It (key and value explanation) is included into this phrase "plannerFields maps plannerID to fieldRefs".
In general, "T maps A to B" is equivalent to T map[A]B
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
v2/pkg/engine/plan/visitor.go (1)
1338-1341
: Assign via embedded struct for clarity (no-op behavior-wise).Being explicit avoids confusion if SingleFetch gains same-named fields later.
- singleFetch.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) - singleFetch.FieldFetchReasons = v.buildFetchReasons(internal.fetchID) + singleFetch.FetchConfiguration.CoordinateDependencies = v.resolveFetchDependencies(internal.fetchID) + singleFetch.FetchConfiguration.FieldFetchReasons = v.buildFetchReasons(internal.fetchID)
🧹 Nitpick comments (3)
execution/engine/execution_engine_test.go (2)
218-218
: Rename test option to match ResolverOptions and feature naming.Use “fetchReasons” consistently (not “fieldsRequestedBy”).
- propagateFieldsRequestedBy bool + propagateFetchReasons bool- PropagateFetchReasons: opts.propagateFieldsRequestedBy, + PropagateFetchReasons: opts.propagateFetchReasons,- eto.propagateFieldsRequestedBy = true + eto.propagateFetchReasons = true- eto.propagateFieldsRequestedBy = true + eto.propagateFetchReasons = trueAlso applies to: 267-267, 876-876, 4540-4540
4349-4472
: Add a requires-chain coverage to FetchReasons.Current tests validate IsKey. Add a case where a field is included due to @requires to assert is_requires=true in the propagated extension.
Also applies to: 4507-4543
v2/pkg/engine/plan/visitor.go (1)
637-640
: Tighten the comment for skipField.Minor doc polish.
-// skipField returns true if the field was added by the query planner as a dependency. -// For another field and should not be included in the response. -// If it returns false, the user requests the field. +// skipField returns true if the field was added by the planner as a dependency +// for another field and should not be included in the response. +// If it returns false, the field was requested by the client.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
(4 hunks)v2/pkg/engine/plan/visitor.go
(8 hunks)v2/pkg/engine/resolve/fetch.go
(12 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_federation_test.go
🧰 Additional context used
🧬 Code graph analysis (2)
execution/engine/execution_engine_test.go (5)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (2)
DataSource
(296-309)DataSourceMetadata
(34-56)v2/pkg/engine/plan/type_field.go (1)
TypeField
(3-8)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-357)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
FetchReason
(423-430)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (3)
v2/pkg/engine/resolve/fetch.go (3)
104-110
: FetchReasons API wiring looks correct.All fetch kinds expose FetchReasons through concrete implementations. Good separation and promotion usage.
Also applies to: 279-285, 191-197, 244-247
350-354
: Exclude FieldFetchReasons from equality — aligns with intent.Treating reasons as derived metadata (non-semantic) is appropriate for plan caching.
Also applies to: 359-374
421-430
: JSON contract for FetchReason is concise and matches tests.Naming (typename, field, by_subgraphs, etc.) is consistent with expected payloads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
v2/pkg/engine/plan/planner.go (1)
232-244
: Minor: pre-size the map; optionally dedup inputsPre-size to reduce rehashing; dedup only if upstream can emit duplicates.
Apply this diff:
-func inverseMap(m map[int][]int) map[int][]int { - inverse := make(map[int][]int) +func inverseMap(m map[int][]int) map[int][]int { + inverse := make(map[int][]int, len(m)) for k, v := range m { for _, v2 := range v { inverse[v2] = append(inverse[v2], k) } }v2/pkg/engine/plan/visitor.go (3)
637-648
: Fix comment grammar; keep TODO conciseSmall readability nit; logic can stay linear for now.
Apply this diff:
-// skipField returns true if the field was added by the query planner as a dependency. -// For another field and should not be included in the response. -// If it returns false, the user requests the field. +// skipField returns true if the field was added by the query planner as a dependency +// for another field and should not be included in the response. +// If it returns false, the field was requested by the client. func (v *Visitor) skipField(ref int) bool { - // TODO: If this grows, switch to map[int]struct{} for O(1). + // TODO: If this grows, switch to map[int]struct{} for O(1). for _, skipRef := range v.skipFieldsRefs { if skipRef == ref { return true } } return false }
1358-1396
: Avoid parameter shadowing for readabilityInner loop variable fetchID shadows function param; use a distinct name.
Apply this diff:
- for _, fetchID := range depFieldPlanners { // planner index == fetchID - ofc := v.planners[fetchID].ObjectFetchConfiguration() + for _, depPlannerID := range depFieldPlanners { // planner index == fetchID + ofc := v.planners[depPlannerID].ObjectFetchConfiguration() if ofc == nil { continue } origin := resolve.FetchDependencyOrigin{ - FetchID: fetchID, + FetchID: depPlannerID, Subgraph: ofc.sourceName, Coordinate: resolve.GraphCoordinate{ FieldName: v.Operation.FieldNameString(depFieldRef), TypeName: v.fieldEnclosingTypeNames[depFieldRef], }, }
1485-1489
: Optional: move per-reason dedup outside the loopSorting/compacting on every merge can be O(k log k) repeatedly. Do a single pass after the loop.
Apply this diff:
- if reasons[i].BySubgraphs != nil { - slices.Sort(reasons[i].BySubgraphs) - reasons[i].BySubgraphs = slices.Compact(reasons[i].BySubgraphs) - } + // Defer sort/compact of BySubgraphs to a final pass.Then, below the loop and before sorting reasons:
- slices.SortFunc(reasons, func(a, b resolve.FetchReason) int { + for i := range reasons { + if reasons[i].BySubgraphs != nil { + slices.Sort(reasons[i].BySubgraphs) + reasons[i].BySubgraphs = slices.Compact(reasons[i].BySubgraphs) + } + } + slices.SortFunc(reasons, func(a, b resolve.FetchReason) int { return cmp.Or( cmp.Compare(a.TypeName, b.TypeName), cmp.Compare(a.FieldName, b.FieldName), ) })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
v2/pkg/engine/plan/planner.go
(3 hunks)v2/pkg/engine/plan/visitor.go
(8 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-29T09:35:47.950Z
Learnt from: ysmolski
PR: wundergraph/graphql-go-tools#1282
File: v2/pkg/engine/plan/visitor.go:5-5
Timestamp: 2025-08-29T09:35:47.950Z
Learning: The wundergraph/graphql-go-tools project does not support Go versions < 1.23, so compatibility concerns for features available in Go 1.21+ (like cmp.Or) should not be raised.
Applied to files:
v2/pkg/engine/plan/visitor.go
🧬 Code graph analysis (2)
v2/pkg/engine/plan/visitor.go (1)
v2/pkg/engine/resolve/fetch.go (1)
FetchReason
(423-430)
v2/pkg/engine/resolve/resolve.go (1)
v2/pkg/engine/resolve/resolvable.go (1)
ResolvableOptions
(62-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (10)
v2/pkg/engine/plan/planner.go (2)
5-5
: LGTM: deterministic inverse ordering dependencyImporting sort for normalization is appropriate.
157-158
: Wire inverse dependencies into planning visitorGood placement and timing; ensures downstream consumers (e.g., buildFetchReasons) have the inverse relation available before Walk.
v2/pkg/engine/resolve/resolve.go (2)
170-170
: Doc tweak LGTMConstructor comment reads well and is accurate.
250-251
: No changes needed
Feature flag plumbing and gating verified:propagateFieldFetchReasons
is driven byoptions.PropagateFetchReasons
inResolver
→Loader
, and fetch reasons are only added whenDataSourceMetadata.RequiresFetchReason
returns true for@openfed__requireFetchReason
fields.v2/pkg/engine/plan/visitor.go (6)
5-5
: LGTM: use cmp for deterministic sort keysGo 1.23+ is supported per project learnings; cmp.Or usage is fine.
50-50
: LGTM: explicit inverse mapping on visitorClear naming and purpose; pairs with planner.inverseMap().
59-67
: LGTM: tracking planners⇄fields and enclosing type namesThe maps enable later dependency/reason computation with minimal coupling.
343-345
: LGTM: record enclosing type early (before skip)
Captures type for both user and planner-added fields; required by dependencies/reasons.
1338-1341
: SingleFetch CoordinateDependencies and FieldFetchReasons are defined and consumed; no further action needed.
1401-1499
: DS.RequiresFetchReason and fetch_reasons extension verified
Implementation covers both root and child nodes (via hasFetchReasonRootNode/ChildNode) and resolve/loader.go injects thefetch_reasons
extension. No changes required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
v2/pkg/engine/resolve/resolve.go (1)
164-165
: Resolved: extension key uses “fetch_reasons” (plural). LGTM.
🧹 Nitpick comments (2)
v2/pkg/engine/resolve/resolve.go (2)
152-154
: Fix doc/field name mismatch.Comment says AllowedCustomSubgraphErrorFields but the field is AllowedSubgraphErrorFields. Align to avoid confusion in public API docs.
- // AllowedCustomSubgraphErrorFields defines which fields are allowed in the subgraph error when in passthrough mode + // AllowedSubgraphErrorFields defines which fields are allowed in the subgraph error when in passthrough mode
164-167
: Clarify directive gating in the option’s doc.Explicitly note that only fields annotated with @openfed__requireFetchReason are included.
- // PropagateFetchReasons enables adding the "fetch_reasons" extension to - // upstream subgraph requests. This extension explains why each field was requested. - // This flag does not expose the data to clients. + // PropagateFetchReasons enables adding the "fetch_reasons" request extension to + // upstream subgraph requests. The extension explains why each field was requested. + // The router only includes reasons for fields annotated with @openfed__requireFetchReason. + // This flag does not expose the data to clients.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
v2/pkg/engine/resolve/resolve.go
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/resolve/resolve.go (1)
v2/pkg/engine/resolve/resolvable.go (1)
ResolvableOptions
(62-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (2)
v2/pkg/engine/resolve/resolve.go (2)
170-170
: Doc comment tweak: New() description looks good.
250-251
: Verified Loader propagation flag wiringLoader struct includes the propagateFieldFetchReasons flag, injection into body.extensions.fetch_reasons is correctly gated by that flag (and skips introspection), and no client responses expose fetch_reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
v2/pkg/engine/resolve/loader.go (1)
1611-1611
: Re: “will it work if extensions are not present yet?” — yesjsonparser.Set creates intermediate objects; this path will work when extensions is absent.
execution/engine/execution_engine_test.go (1)
4355-4357
: Confirming fix: removed extraneous "some" from second fetch expectationsThis resolves the earlier discrepancy where a field not present in the second fetch was listed in the extension.
🧹 Nitpick comments (7)
v2/pkg/engine/resolve/loader.go (3)
178-180
: Field name consistency with optionsConsider renaming enableRequireFetchReasons to propagateFetchReasons to align with ResolverOptions.EnableRequireFetchReasons and the test nomenclature. This reduces cognitive load.
Apply within this file:
- enableRequireFetchReasons bool + propagateFetchReasons boolAnd update references accordingly:
- if l.enableRequireFetchReasons && !IsIntrospectionDataSource(res.ds.ID) { + if l.propagateFetchReasons && !IsIntrospectionDataSource(res.ds.ID) {
1601-1617
: Gated fetch_reasons injection looks correct; ensure deterministic ordering to avoid test flakesThe guard and path are right, and jsonparser.Set will create body.extensions if missing. One potential source of flakiness: the order of fetchReasons. If generation isn’t already stable, sort by (typename, field) before marshaling or adjust producer-side ordering to guarantee stability.
Would you like me to add a small sort at the producer (where FetchReasons are built) to guarantee order?
1601-1617
: Do not silently overwrite an existing extensions.fetch_reasonsIf upstream middleware already set extensions.fetch_reasons via l.ctx.Extensions, this will overwrite it. If coexistence is desired, merge/append instead or skip when present.
execution/engine/execution_engine_test.go (4)
218-219
: Option naming: align with feature namepropagateFieldsRequestedBy → propagateFetchReasons for consistency with extensions.fetch_reasons and ResolverOptions.EnableRequireFetchReasons.
- propagateFieldsRequestedBy bool + propagateFetchReasons bool
263-268
: Correct wiring of the flag into ResolverOptionsEnableRequireFetchReasons uses the test option as intended. After the suggested rename, adjust the mapping.
- EnableRequireFetchReasons: opts.propagateFieldsRequestedBy, + EnableRequireFetchReasons: opts.propagateFetchReasons,
827-879
: Great positive path coverage for fetch_reasonsTest validates injection and expected shape. If FetchReasons ordering isn’t guaranteed upstream, this exact string assertion could become brittle. Consider normalizing JSON in the round-tripper for body equality or sorting fetchReasons before encode.
4349-4472
: makeDataSource: expectations and metadata look coherent; minor robustness nits
- expectedBody1 matches the annotated FetchReasonFields on the first subgraph. Good.
- expectedBody2 intentionally omits fetch_reasons; matches metadata (no FetchReasonFields). Good.
- To reduce brittleness, prefer comparing JSON semantically (object/array equality) in the test transport rather than raw string order.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- v2/pkg/engine/resolve/resolve.go
🧰 Additional context used
🧬 Code graph analysis (2)
v2/pkg/engine/resolve/loader.go (1)
v2/pkg/engine/resolve/fetch.go (1)
Fetch
(20-26)
execution/engine/execution_engine_test.go (5)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (2)
DataSource
(296-309)DataSourceMetadata
(34-56)v2/pkg/engine/plan/type_field.go (1)
TypeField
(3-8)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-357)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (1)
execution/engine/execution_engine_test.go (1)
4507-4542
: Both “with” and “without” extension scenarios covered — niceDual runs verify gating behavior via EnableRequireFetchReasons and expected request bodies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
v2/pkg/engine/resolve/loader.go (1)
1611-1611
: Re: “Will it work if extensions are not present yet?”—yesThis path creates/mutates extensions as needed; tests confirm it. Nothing to change.
🧹 Nitpick comments (3)
v2/pkg/engine/resolve/loader.go (2)
33-37
: Hoist extension key to a package constant to avoid driftUse a single constant for the "fetch_reasons" key to keep parity across files/tests and prevent typos.
@@ const ( IntrospectionSchemaTypeDataSourceID = "introspection__schema&__type" IntrospectionTypeFieldsDataSourceID = "introspection__type__fields" IntrospectionTypeEnumValuesDataSourceID = "introspection__type__enumValues" ) +// request extension keys +const extensionKeyFetchReasons = "fetch_reasons" @@ - input, res.err = jsonparser.Set(input, encoded, "body", "extensions", "fetch_reasons") + input, res.err = jsonparser.Set(input, encoded, "body", "extensions", extensionKeyFetchReasons)Also applies to: 1601-1617
1594-1600
: Double-check non-object extensions inputIf ctx.Extensions is present but not an object (e.g., "null" or a scalar), jsonparser.Set on body.extensions.fetch_reasons may behave unexpectedly. Consider normalizing extensions to {} when non-object.
Would you like a small helper to coerce body.extensions to an object before adding keys?
Also applies to: 1601-1617
execution/engine/execution_engine_test.go (1)
827-827
: Tighten test nameConsider renaming to: “execute simple hero operation with fetch reason propagation”.
- t.Run("execute simple hero operation with propagating to subgraphs reason for fields being requested", runWithoutError( + t.Run("execute simple hero operation with fetch reason propagation", runWithoutError(
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
execution/engine/execution_engine_test.go
(5 hunks)v2/pkg/engine/resolve/loader.go
(2 hunks)v2/pkg/engine/resolve/resolve.go
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
v2/pkg/engine/resolve/resolve.go (1)
v2/pkg/engine/resolve/resolvable.go (1)
ResolvableOptions
(62-67)
v2/pkg/engine/resolve/loader.go (1)
v2/pkg/engine/resolve/fetch.go (1)
Fetch
(20-26)
execution/engine/execution_engine_test.go (5)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (2)
DataSource
(296-309)DataSourceMetadata
(34-56)v2/pkg/engine/plan/type_field.go (1)
TypeField
(3-8)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-357)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (6)
v2/pkg/engine/resolve/loader.go (1)
1601-1617
: fetch_reasons injection logic looks correct and safely gated
- Gated by option and introspection IDs.
- Only sets when reasons are non-empty.
- Proper error propagation via res.err.
v2/pkg/engine/resolve/resolve.go (1)
164-168
: Option + wiring LGTMPropagateFetchReasons is documented clearly and correctly wired through newTools into Loader.
Also applies to: 250-251
execution/engine/execution_engine_test.go (4)
214-219
: Test harness flagging/wiring is cleanNew propagateFetchReasons option is plumbed into ResolverOptions correctly.
Also applies to: 262-267
827-879
: Solid happy-path coverage for fetch_reasons propagation
- Uses POST to carry extensions.
- Verifies exact payload including reasons for Query.hero and Character.name.
4355-4357
: Nested fetch scenario expectations look consistentexpectedBody1/2 match the planned selections, and the second subgraph body omits fetch_reasons appropriately.
Also applies to: 4426-4431
4349-4472
: Deterministic ordering confirmed in buildFetchReasons: the finalreasons
slice is sorted viaslices.SortFunc
on(TypeName, FieldName)
, and eachBySubgraphs
list is sorted and compacted, guaranteeing stable JSON output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
execution/engine/execution_engine_test.go (1)
4357-4357
: Previous issue about extraneous "some" in expectedBody2 is resolved.expectedBody2 no longer lists unrelated fields; thanks for fixing.
🧹 Nitpick comments (5)
execution/engine/execution_engine_test.go (5)
214-219
: Add a dedicated test option for fetch-reason propagation (LGTM, minor ergonomics).The new propagateFetchReasons toggle is fine. Consider adding a small helper like withValueCompletion() to keep call sites consistent.
Proposed helper (place near withValueCompletion):
func withPropagateFetchReasons() executionTestOptions { return func(o *_executionTestOptions) { o.propagateFetchReasons = true } }
827-874
: Positive test for extension payload is precise and scoped (POST + targeted FetchReasonFields).
- The expected body matches the POST method and only includes fields whitelisted via FetchReasonFields.
- Nice: both root (Query.hero) and child (Character.name) reasons are asserted.
Potential flake guard: comparing the outbound body structurally (JSON) rather than byte-for-byte would reduce brittleness if field order changes.
875-878
: Prefer a helper to set the option.Minor consistency win with withValueCompletion().
- func(eto *_executionTestOptions) { - eto.propagateFetchReasons = true - }, + withPropagateFetchReasons(),Add the helper:
func withPropagateFetchReasons() executionTestOptions { return func(o *_executionTestOptions) { o.propagateFetchReasons = true } }
4349-4472
: De-duplicate "id-2" and make expectedBody1 resilient to DS id drift.Hardcoding id-2 in the embedded JSON is easy to desync from the DS id below. Suggest deriving it once and formatting into the JSON.
- makeDataSource := func(t *testing.T, expectFetchReasons bool) []plan.DataSource { + makeDataSource := func(t *testing.T, expectFetchReasons bool) []plan.DataSource { + const secondID = "id-2" var expectedBody1 string var expectedBody2 string if !expectFetchReasons { expectedBody1 = `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}"}` } else { - expectedBody1 = `{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}","extensions":{"fetch_reasons":[{"typename":"Admin","field":"some","by_user":true},{"typename":"User","field":"id","by_subgraphs":["id-2"],"by_user":true,"is_key":true}]}}` + expectedBody1 = fmt.Sprintf(`{"query":"{accounts {__typename ... on User {some {__typename id}} ... on Admin {some {__typename id}}}}","extensions":{"fetch_reasons":[{"typename":"Admin","field":"some","by_user":true},{"typename":"User","field":"id","by_subgraphs":["%s"],"by_user":true,"is_key":true}]}}`, secondID) } expectedBody2 = `{"query":"query($representations: [_Any!]!){_entities(representations: $representations){... on User {__typename title}}}","variables":{"representations":[{"__typename":"User","id":"1"},{"__typename":"User","id":"3"}]}}` return []plan.DataSource{ mustGraphqlDataSourceConfiguration(t, "id-1", @@ - mustGraphqlDataSourceConfiguration(t, - "id-2", + mustGraphqlDataSourceConfiguration(t, + secondID,
4507-4542
: Extension-enabled run is correctly gated by test option and DS expectations.Nice separation via makeDataSource(t, true) and the option toggle.
Consider switching to withPropagateFetchReasons() for symmetry with other option helpers (see prior suggestion).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
execution/engine/execution_engine_test.go
(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
execution/engine/execution_engine_test.go (6)
v2/pkg/starwars/starwars.go (2)
FileSimpleHeroQuery
(13-13)Schema
(55-59)v2/pkg/engine/plan/datasource_configuration.go (2)
DataSource
(296-309)DataSourceMetadata
(34-56)v2/pkg/engine/plan/type_field.go (1)
TypeField
(3-8)v2/pkg/engine/datasource/graphql_datasource/configuration.go (4)
ConfigurationInput
(16-23)FetchConfiguration
(124-128)SchemaConfiguration
(135-139)FederationConfiguration
(130-133)v2/pkg/engine/resolve/fetch.go (2)
Fetch
(20-26)FetchConfiguration
(314-357)v2/pkg/engine/plan/configuration.go (1)
FieldConfiguration
(101-118)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
- GitHub Check: Build and test (go 1.23 / ubuntu-latest)
- GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (3)
execution/engine/execution_engine_test.go (3)
262-267
: Wiring PropagateFetchReasons into ResolverOptions looks correct.Good plumb-through; default remains false for all other tests.
Please confirm there are no unintended extensions added in tests that don’t enable the option (a quick grep for "fetch_reasons" in round-tripper expectations should only hit the two new cases).
844-857
: FetchReasonFields are minimal and accurate.Limiting to hero and name avoids leaking reasons on non-annotated fields.
4503-4504
: Baseline run without extension uses makeDataSource(..., false) — good control.
The PropagateFetchReasons option of Resolver enables router to send to subgraphs an extension that includes reasons why fields where included in the request.
This data is sent only for the fields marked with
@openfed__requireFetchReason
in the schema.Directive is implemented in the router repo.
Router part: wundergraph/cosmo#2160
Summary by CodeRabbit
New Features
Improvements
Tests
Documentation
Chores
Checklist