-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Related Issue
This is a distinct reproduction of the same []null / empty URL symptom seen in #7653, but with a different trigger: truly heterogeneous arrays where items are different subtypes and carry different nullable federated fields — rather than a single entity type with multiple @key variants.
Description
When Tyk Gateway resolves federated entities via _entities queries for a field on a type that appears in an array, the federation planner uses array index 0 as the anchor to determine the subgraph URL for the entire entity resolution batch.
If item[0] has null for the federated field being resolved (because item[0] is of a different subtype and that field does not apply to it), Tyk generates an empty URL and the fetch fails with:
Get "": unsupported protocol scheme ""
This manifests specifically with heterogeneous arrays — arrays where items have different subtypes, and only some items carry a particular federated field (others have null for that field).
How This Differs from #7653
Issue #7653 involves a single entity type (Product) extended via two different @key fields (id vs sku) in two separate subgraphs. All array items are the same type.
This issue involves an array of items where each item can be one of two completely different entity types (e.g. TypeAlpha or TypeBeta), and only items of the matching type carry a non-null value for a given federated field. The planner still exhibits the same index-0 URL anchor behavior, but it is triggered by a structurally different schema pattern.
Steps to Reproduce
Schema Setup
Subgraph A ("catalog") — owns the Item array; extends two entity types from Subgraph B:
extend type TypeAlpha @key(fields: "id") {
id: String @external
}
extend type TypeBeta @key(fields: "id") {
id: String @external
}
type Item {
itemType: ItemType! # enum: ALPHA | BETA
id: String!
alphaDetail: TypeAlpha # non-null only when itemType == ALPHA
betaDetail: TypeBeta # non-null only when itemType == BETA
}
enum ItemType {
ALPHA
BETA
}
type Query {
listItems: [Item!]!
}Subgraph B ("details") — owns both entity types:
type TypeAlpha @key(fields: "id") {
id: String
name: String
}
type TypeBeta @key(fields: "id") {
id: String
name: String
}Query
query {
listItems {
itemType
id
alphaDetail { name }
betaDetail { name }
}
}Data That Triggers the Bug
Subgraph A returns a mixed array where the first element is type ALPHA (so betaDetail is null at index 0):
[
{ "id": "alpha-001", "itemType": "ALPHA", "alphaDetail": { "id": "alpha-001" }, "betaDetail": null },
{ "id": "alpha-002", "itemType": "ALPHA", "alphaDetail": { "id": "alpha-002" }, "betaDetail": null },
{ "id": "beta-001", "itemType": "BETA", "alphaDetail": null, "betaDetail": { "id": "beta-001" } }
]Observed Result
alphaDetailresolves successfully — item[0] has a non-nullalphaDetail, Tyk correctly identifies the Subgraph B URL forTypeAlpha.betaDetailresolution fails — item[0] hasnullforbetaDetail, Tyk produces an empty URL string and errors.
Confirming the Index-0 Anchor
Reorder the array so a BETA item is first:
[
{ "id": "beta-001", "itemType": "BETA", "alphaDetail": null, "betaDetail": { "id": "beta-001" } },
{ "id": "alpha-001", "itemType": "ALPHA", "alphaDetail": { "id": "alpha-001" }, "betaDetail": null },
{ "id": "alpha-002", "itemType": "ALPHA", "alphaDetail": { "id": "alpha-002" }, "betaDetail": null }
]After reordering:
betaDetailnow resolves successfully.alphaDetailnow fails.
This confirms the planner anchors subgraph URL lookup to array index 0 regardless of which items actually carry the field.
Debug Logs
Enable debug logging (log_level: "debug"). The following pattern appears when the bug triggers:
# Successful — item[0] has non-null alphaDetail, URL correctly resolved
beforeFetchHook url="https://details-subgraph/query"
representations=[{"__typename":"TypeAlpha","id":"alpha-001"},{"__typename":"TypeAlpha","id":"alpha-002"}]
# → 200 OK ✓
# Failed — item[0] has null betaDetail, URL is empty string
beforeFetchHook url=""
representations=[]null
# → Get "": unsupported protocol scheme "" ✗
After reordering so item[0] is BETA:
# betaDetail now works
beforeFetchHook url="https://details-subgraph/query"
representations=[{"__typename":"TypeBeta","id":"beta-001"}]
# → 200 OK ✓
# alphaDetail now fails
beforeFetchHook url=""
representations=[]null
# → Get "": unsupported protocol scheme "" ✗
Expected Behavior
The federation planner should handle heterogeneous arrays where only a subset of items carry a particular federated field. It should either:
- Scan past null entries — find the first non-null value at any index to determine the subgraph URL, rather than always using index 0.
- Skip null representations — omit items with a null federated field from the
_entitiesbatch entirely, and only include items where the field is non-null.
Either approach aligns with the Apollo Federation spec, which requires entity resolution to be driven by __typename and key fields of each representation individually.
Actual Behavior
The planner uses index 0 to determine the subgraph URL for the entire batch. If item[0] has null for the field being resolved, Tyk generates an empty URL and issues a fetch to "", which fails immediately.
Environment
| Component | Version / Detail |
|---|---|
| Tyk Gateway | v5.11.0 |
| Federation spec | Apollo Federation v1 (@key, @external) |
| Subgraph count | 2 |
Workaround
Remove the GraphQL federation relationship for the affected field entirely. Resolve the details locally inside the owning subgraph by calling the details service directly via REST/gRPC. This bypasses the Tyk federation planner for that field but breaks the federation model and couples subgraphs at the network level.