Skip to content

feat: support user-defined extension forwarding from subgraphs#2836

Open
Noroth wants to merge 9 commits intomainfrom
ludwig/eng-9502-router-subgraph-extension-forwarding-support
Open

feat: support user-defined extension forwarding from subgraphs#2836
Noroth wants to merge 9 commits intomainfrom
ludwig/eng-9502-router-subgraph-extension-forwarding-support

Conversation

@Noroth
Copy link
Copy Markdown
Contributor

@Noroth Noroth commented May 7, 2026

Summary by CodeRabbit

  • New Features

    • Added subgraph extension propagation support, enabling forwarding of extensions from subgraph responses to client responses.
    • Configurable extension field allowlist to control which extension fields are propagated.
    • Selectable algorithms (first_write / last_write) to resolve conflicts when multiple subgraphs return the same extension field.
  • Documentation

    • Added comprehensive documentation for subgraph extension propagation configuration and behavior.
    • Updated navigation structure to organize subgraph data propagation topics.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/docs-website.
  • I have read the Contributors Guide.

Open Source AI Manifesto

This project follows the principles of the Open Source AI Manifesto. Please ensure your contribution aligns with its principles.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@github-actions github-actions Bot added the router label May 7, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR implements subgraph extension forwarding for the GraphQL router. It adds configuration types, schema validation, executor wiring to propagate subgraph response extensions to clients, comprehensive integration tests covering forwarding behavior and conflict resolution algorithms, documentation, dependency updates, and removes an unrelated Kafka websocket test.

Changes

Subgraph Extension Propagation Implementation

Layer / File(s) Summary
Configuration Types
router/pkg/config/config.go
Introduces SubgraphExtensionPropagationAlgorithm enum with first_write and last_write values; adds SubgraphExtensionPropagationConfiguration struct with Enabled, AllowedExtensionFields, and Algorithm fields.
Configuration Schema & Defaults
router/pkg/config/config.schema.json, router/pkg/config/testdata/config_defaults.json, router/pkg/config/testdata/config_full.json, router/pkg/config/fixtures/full.yaml
JSON schema, test defaults, full config, and fixture YAML all updated with subgraph extension propagation configuration; defaults disable forwarding with first_write algorithm.
Executor Wiring
router/core/executor.go
ExecutorConfigurationBuilder.Build maps SubgraphExtensionPropagation.Enabled to resolver AllowCustomExtensionProperties, converts AllowedExtensionFields to AllowedSubgraphExtensions set, and maps Algorithm to ExtensionForwardingAlgorithm.
Router Configuration and Options
router/core/router_config.go, router/core/router.go, router/core/factoryresolver.go, router/core/graph_server.go, router/core/supervisor_instance.go
Router core infrastructure updated: Config struct extended with subgraphExtensionPropagation field; new WithSubgraphExtensionPropagation router option added; RouterEngineConfiguration struct includes SubgraphExtensionPropagation field; SupervisorInstance wires option during router creation; graph server initialization passes propagation config.
Test Infrastructure
router-tests/testenv/testenv.go
Test environment Config adds optional ModifySubgraphExtensionPropagation callback for mutating propagation settings; GraphQLResponse struct extended with Extensions json.RawMessage field for unmarshaling forwarded extensions in assertions.
Integration Tests
router-tests/protocol/extension_forwarding_test.go
Comprehensive test suite covering single-subgraph forwarding with allowlist, multi-subgraph merging, first_write and last_write conflict resolution algorithms, disabled forwarding behavior, allowed/disallowed field filtering, complex query merging, partial subgraph responses, per-request call isolation; includes requireEmptyExtensions helper for assertions.
Documentation
docs-website/router/configuration.mdx, docs-website/router/subgraph-data-propagation/subgraph-extension-propagation.mdx, docs-website/docs.json, docs-website/router/subscriptions-migration.mdx
New documentation page and section under "Subgraph Data Propagation" describing configuration, allowed-field semantics, conflict resolution algorithms, examples, and per-request isolation; navigation updated with subgraph data propagation group and redirect for error propagation path; subscriptions migration guide link updated.
Dependency Updates
router/go.mod, router-tests/go.mod
github.com/wundergraph/graphql-go-tools/v2 bumped from v2.1.0 to v2.1.1-0.20260507092259-51aaf9398da0 in both modules; commented replace directives adjusted for formatting.
Removed Test
router-tests/events/kafka_events_test.go
Deleted test case validating websocket subscriptions with EnableNetPoll = false.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: support user-defined extension forwarding from subgraphs' directly and clearly describes the main feature being added: extension forwarding capability from subgraphs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-e7c4a43bd9f6881b89157d200f43da9a18baa3fc

@codecov
Copy link
Copy Markdown

codecov Bot commented May 7, 2026

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 65.72%. Comparing base (42b21fc) to head (9125715).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
router/core/supervisor_instance.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2836       +/-   ##
===========================================
+ Coverage   46.23%   65.72%   +19.49%     
===========================================
  Files        1080      254      -826     
  Lines      144704    26539   -118165     
  Branches     9247        0     -9247     
===========================================
- Hits        66897    17442    -49455     
+ Misses      76077     7683    -68394     
+ Partials     1730     1414      -316     
Files with missing lines Coverage Δ
router/core/executor.go 87.40% <100.00%> (+0.73%) ⬆️
router/core/factoryresolver.go 80.23% <ø> (ø)
router/core/graph_server.go 84.62% <100.00%> (-0.45%) ⬇️
router/core/router.go 69.72% <100.00%> (-0.36%) ⬇️
router/core/router_config.go 93.75% <ø> (ø)
router/pkg/config/config.go 80.51% <ø> (ø)
router/core/supervisor_instance.go 0.00% <0.00%> (ø)

... and 838 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
router-tests/protocol/extension_forwarding_test.go (2)

38-55: ⚡ Quick win

Extract repeated extension-injection middleware into a shared helper.

The same recorder/unmarshal/marshal/write block is duplicated across many subtests. A file-level helper will reduce noise and make future test adjustments safer.

♻️ Refactor sketch
+func injectExtensions(t *testing.T, raw string) func(http.Handler) http.Handler {
+	t.Helper()
+	return func(handler http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			recorder := httptest.NewRecorder()
+			handler.ServeHTTP(recorder, r)
+
+			var response graphqlResponseWithExtensions
+			require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
+			response.Extensions = json.RawMessage(raw)
+
+			responseBytes, err := json.Marshal(response)
+			require.NoError(t, err)
+			w.WriteHeader(recorder.Code)
+			_, _ = w.Write(responseBytes)
+		})
+	}
+}

Then use:

- Middleware: func(handler http.Handler) http.Handler { ... },
+ Middleware: injectExtensions(t, `{"myExtension":"myValue"}`),

Also applies to: 86-103, 106-123, 154-171, 174-191, 222-239, 260-279, 353-370, 401-418, 456-477

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router-tests/protocol/extension_forwarding_test.go` around lines 38 - 55, The
test repeats identical middleware logic (creating an httptest.NewRecorder,
calling handler.ServeHTTP, unmarshalling into graphqlResponseWithExtensions,
injecting a fixed Extensions JSON, re-marshalling and writing the response)
across many subtests; extract that into a file-level helper function, e.g.
createInjectExtensionsMiddleware() (or injectExtensionsMiddleware) that returns
an http.Handler middleware performing the
recorder/unmarshal/modify/marshal/write sequence, and replace each inline
Middleware: func(...) block with a call to this helper so all tests reuse the
same implementation.

64-69: ⚡ Quick win

Assert GraphQL errors explicitly in response checks.

These assertions can pass with partial data while errors is present. Add require.Empty(t, resp.Errors) (or a small helper) after decode in each case.

✅ Minimal improvement
 var resp testenv.GraphQLResponse
 require.NoError(t, json.NewDecoder(strings.NewReader(res.Body)).Decode(&resp))
+require.Empty(t, resp.Errors)

 require.Equal(t, `{"employee":{"id":1}}`, string(resp.Data))

Also applies to: 133-137, 200-205, 248-253, 309-335, 379-384, 427-432, 492-497

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router-tests/protocol/extension_forwarding_test.go` around lines 64 - 69, The
response checks currently only assert Data and Extensions but may ignore GraphQL
errors; after decoding into resp (type testenv.GraphQLResponse) and after the
existing require.NoError(t, json.NewDecoder(...).Decode(&resp)) add an explicit
assertion require.Empty(t, resp.Errors) (or call the shared helper) before
asserting resp.Data/resp.Extensions; do this in the shown block (resp variable
decode) and mirror the same change in the other test blocks referenced (around
the resp checks at the other listed ranges).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@router-tests/go.mod`:
- Around line 33-35: Update the pinned OpenTelemetry SDK versions in this module
by changing any require or replace entries that reference
go.opentelemetry.io/otel/sdk (and its submodule
go.opentelemetry.io/otel/sdk/metric) from v1.39.0 or older (including the
replace at v1.28.0) to v1.40.0 or later; locate the require line that lists
"go.opentelemetry.io/otel/sdk" and the replace directives referencing
"go.opentelemetry.io/otel/sdk" (and "go.opentelemetry.io/otel/sdk/metric") and
bump their versions to at least v1.40.0 so both the direct dependency and any
replacements are aligned to a patched release.

In `@router/go.mod`:
- Line 34: Update all go.opentelemetry.io/otel/sdk pins in the router module to
v1.40.0 or later: change the require lines for "go.opentelemetry.io/otel/sdk"
and "go.opentelemetry.io/otel/sdk/metric" from v1.39.0 to v1.40.0+ and update
any replace directives that currently pin "go.opentelemetry.io/otel/sdk" (and
its metric submodule) from v1.28.0 to v1.40.0+ so the module meets the security
floor; after editing the go.mod entries run go mod tidy to update go.sum.

---

Nitpick comments:
In `@router-tests/protocol/extension_forwarding_test.go`:
- Around line 38-55: The test repeats identical middleware logic (creating an
httptest.NewRecorder, calling handler.ServeHTTP, unmarshalling into
graphqlResponseWithExtensions, injecting a fixed Extensions JSON, re-marshalling
and writing the response) across many subtests; extract that into a file-level
helper function, e.g. createInjectExtensionsMiddleware() (or
injectExtensionsMiddleware) that returns an http.Handler middleware performing
the recorder/unmarshal/modify/marshal/write sequence, and replace each inline
Middleware: func(...) block with a call to this helper so all tests reuse the
same implementation.
- Around line 64-69: The response checks currently only assert Data and
Extensions but may ignore GraphQL errors; after decoding into resp (type
testenv.GraphQLResponse) and after the existing require.NoError(t,
json.NewDecoder(...).Decode(&resp)) add an explicit assertion require.Empty(t,
resp.Errors) (or call the shared helper) before asserting
resp.Data/resp.Extensions; do this in the shown block (resp variable decode) and
mirror the same change in the other test blocks referenced (around the resp
checks at the other listed ranges).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d69064fa-9739-44a7-8d25-125043b743f5

📥 Commits

Reviewing files that changed from the base of the PR and between 4912550 and c906ebc.

⛔ Files ignored due to path filters (2)
  • router-tests/go.sum is excluded by !**/*.sum
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • router-tests/go.mod
  • router-tests/protocol/extension_forwarding_test.go
  • router-tests/testenv/testenv.go
  • router/core/executor.go
  • router/go.mod
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/fixtures/full.yaml
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json

Comment thread router-tests/go.mod
Comment thread router/go.mod
@Noroth Noroth requested review from a team as code owners May 7, 2026 10:00
@Noroth Noroth requested a review from Aenimus May 7, 2026 10:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-website/router/configuration.mdx`:
- Line 1972: The line starting "Forwards `extensions` returned by subgraphs to
the client response..." is a long multi-clause sentence; split it into two
short, declarative sentences: one stating that the router forwards subgraph
`extensions` to the client response, and a second stating that an allow list
restricts which root extension fields are propagated and that an algorithm
selects a winner when multiple subgraphs return the same root field. Ensure
punctuation and tone match reference docs style.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b1ff110-b472-4a92-bec7-815626c56e39

📥 Commits

Reviewing files that changed from the base of the PR and between c906ebc and d8b460e.

📒 Files selected for processing (2)
  • docs-website/router/configuration.mdx
  • router-tests/events/kafka_events_test.go
💤 Files with no reviewable changes (1)
  • router-tests/events/kafka_events_test.go

Comment thread docs-website/router/configuration.mdx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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 (1)
router-tests/testenv/testenv.go (1)

1416-1418: ⚡ Quick win

Default the extension propagation algorithm in testenv to match runtime defaults.

cfg.SubgraphExtensionPropagation starts as a zero-value in configureRouter, so partial callback mutations can leave Algorithm empty and produce behavior that differs from config-loaded runtime defaults (first_write).

♻️ Proposed fix
 	if testConfig.ModifySubgraphExtensionPropagation != nil {
 		testConfig.ModifySubgraphExtensionPropagation(&cfg.SubgraphExtensionPropagation)
 	}
+	if cfg.SubgraphExtensionPropagation.Algorithm == "" {
+		cfg.SubgraphExtensionPropagation.Algorithm = config.SubgraphExtensionPropagationAlgorithmFirstWrite
+	}

Also applies to: 1499-1499

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router-tests/testenv/testenv.go` around lines 1416 - 1418, The testenv
currently leaves cfg.SubgraphExtensionPropagation as a zero-value so callbacks
via testConfig.ModifySubgraphExtensionPropagation can produce an empty Algorithm
that differs from runtime defaults; update the setup in configureRouter/testenv
so cfg.SubgraphExtensionPropagation.Algorithm is defaulted to "first_write" (the
runtime default) before invoking testConfig.ModifySubgraphExtensionPropagation
(or ensure the callback only mutates non-zero fields), referencing the symbols
cfg.SubgraphExtensionPropagation and
testConfig.ModifySubgraphExtensionPropagation to locate where to set the
default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@router/pkg/config/config.schema.json`:
- Around line 3807-3813: The current schema makes an empty or missing
allowed_extension_fields behave as "allow all", which is unsafe; change the
schema so empty/missing allowed_extension_fields defaults to forwarding none and
add a new boolean allow_all_extension_fields flag (default false) that must be
explicitly set true to enable full passthrough; update validation/consumer logic
that reads allowed_extension_fields and any enabled setting (e.g., the code that
checks enabled) to treat an absent/empty allowed_extension_fields as deny-all
unless allow_all_extension_fields === true, and ensure schema descriptions for
allowed_extension_fields and the new allow_all_extension_fields clearly document
the safer default and the explicit opt-in requirement.

---

Nitpick comments:
In `@router-tests/testenv/testenv.go`:
- Around line 1416-1418: The testenv currently leaves
cfg.SubgraphExtensionPropagation as a zero-value so callbacks via
testConfig.ModifySubgraphExtensionPropagation can produce an empty Algorithm
that differs from runtime defaults; update the setup in configureRouter/testenv
so cfg.SubgraphExtensionPropagation.Algorithm is defaulted to "first_write" (the
runtime default) before invoking testConfig.ModifySubgraphExtensionPropagation
(or ensure the callback only mutates non-zero fields), referencing the symbols
cfg.SubgraphExtensionPropagation and
testConfig.ModifySubgraphExtensionPropagation to locate where to set the
default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c7c9b72-d5ef-40cc-b659-473dca5ff26c

📥 Commits

Reviewing files that changed from the base of the PR and between 7c3eb12 and 9125715.

📒 Files selected for processing (18)
  • docs-website/docs.json
  • docs-website/router/configuration.mdx
  • docs-website/router/subgraph-data-propagation/subgraph-error-propagation.mdx
  • docs-website/router/subgraph-data-propagation/subgraph-extension-propagation.mdx
  • docs-website/router/subscriptions-migration.mdx
  • router-tests/protocol/extension_forwarding_test.go
  • router-tests/testenv/testenv.go
  • router/core/executor.go
  • router/core/factoryresolver.go
  • router/core/graph_server.go
  • router/core/router.go
  • router/core/router_config.go
  • router/core/supervisor_instance.go
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/fixtures/full.yaml
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json
✅ Files skipped from review due to trivial changes (3)
  • router/core/router_config.go
  • docs-website/router/subscriptions-migration.mdx
  • docs-website/router/subgraph-data-propagation/subgraph-extension-propagation.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
  • router/pkg/config/fixtures/full.yaml
  • docs-website/docs.json

Comment thread router/pkg/config/config.schema.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant