Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ This repository contains a set of tests to evaluate and compare the compatibilit

<!-- gateways:start -->

| Gateway | Compatibility | Test Cases | Test Suites |
| :---------------------------------------------------------------------------------------------------------: | :-----------: | :----------: | :---------: |
| [Hive Gateway](https://the-guild.dev/graphql/hive/gateway) | 100.00% | 🟢 192 | 🟢 44 |
| [Hive Gateway (Rust QP)](https://the-guild.dev/graphql/hive/docs/gateway/other-features/rust-query-planner) | 100.00% | 🟢 192 | 🟢 44 |
| [Hive Router](https://github.com/graphql-hive/router) | 100.00% | 🟢 192 | 🟢 44 |
| [Apollo Router](https://www.apollographql.com/) | 97.40% | 🟢 187 ❌ 5 | 🟢 41 ❌ 3 |
| [Apollo Gateway](https://www.apollographql.com/) | 96.88% | 🟢 186 ❌ 6 | 🟢 40 ❌ 4 |
| [Cosmo Router](https://wundergraph.com) | 94.27% | 🟢 181 ❌ 11 | 🟢 37 ❌ 7 |
| [Grafbase Gateway](https://grafbase.com) | 91.67% | 🟢 176 ❌ 16 | 🟢 38 ❌ 6 |
| [Inigo Gateway](https://inigo.io) | 47.92% | 🟢 92100 | 🟢 12 ❌ 32 |
| Gateway | Compatibility | Test Cases | Test Suites |
| :---------------------------------------------------------------------------------------------------------: | :-----------: | :-----------: | :---------: |
| [Hive Gateway (Rust QP)](https://the-guild.dev/graphql/hive/docs/gateway/other-features/rust-query-planner) | 100.00% | 🟢 203 | 🟢 45 |
| [Hive Router](https://github.com/graphql-hive/router) | 100.00% | 🟢 203 | 🟢 45 |
| [Hive Gateway](https://the-guild.dev/graphql/hive/gateway) | 99.51% | 🟢 202 ❌ 1 | 🟢 44 ❌ 1 |
| [Apollo Router](https://www.apollographql.com/) | 97.54% | 🟢 198 ❌ 5 | 🟢 42 ❌ 3 |
| [Apollo Gateway](https://www.apollographql.com/) | 97.04% | 🟢 197 ❌ 6 | 🟢 41 ❌ 4 |
| [Cosmo Router](https://wundergraph.com) | 94.58% | 🟢 192 ❌ 11 | 🟢 38 ❌ 7 |
| [Grafbase Gateway](https://grafbase.com) | 92.12% | 🟢 187 ❌ 16 | 🟢 39 ❌ 6 |
| [Inigo Gateway](https://inigo.io) | 50.25% | 🟢 102101 | 🟢 12 ❌ 33 |

<!-- gateways:end -->

Expand Down
58 changes: 37 additions & 21 deletions REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@

## Summary

| Gateway | Compatibility | Test Cases | Test Suites |
| :---------------------------------------------------------------------------------------------------------: | :-----------: | :----------: | :---------: |
| [Hive Gateway](https://the-guild.dev/graphql/hive/gateway) | 100.00% | 🟢 192 | 🟢 44 |
| [Hive Gateway (Rust QP)](https://the-guild.dev/graphql/hive/docs/gateway/other-features/rust-query-planner) | 100.00% | 🟢 192 | 🟢 44 |
| [Hive Router](https://github.com/graphql-hive/router) | 100.00% | 🟢 192 | 🟢 44 |
| [Apollo Router](https://www.apollographql.com/) | 97.40% | 🟢 187 ❌ 5 | 🟢 41 ❌ 3 |
| [Apollo Gateway](https://www.apollographql.com/) | 96.88% | 🟢 186 ❌ 6 | 🟢 40 ❌ 4 |
| [Cosmo Router](https://wundergraph.com) | 94.27% | 🟢 181 ❌ 11 | 🟢 37 ❌ 7 |
| [Grafbase Gateway](https://grafbase.com) | 91.67% | 🟢 176 ❌ 16 | 🟢 38 ❌ 6 |
| [Inigo Gateway](https://inigo.io) | 47.92% | 🟢 92100 | 🟢 12 ❌ 32 |
| Gateway | Compatibility | Test Cases | Test Suites |
| :---------------------------------------------------------------------------------------------------------: | :-----------: | :-----------: | :---------: |
| [Hive Gateway (Rust QP)](https://the-guild.dev/graphql/hive/docs/gateway/other-features/rust-query-planner) | 100.00% | 🟢 203 | 🟢 45 |
| [Hive Router](https://github.com/graphql-hive/router) | 100.00% | 🟢 203 | 🟢 45 |
| [Hive Gateway](https://the-guild.dev/graphql/hive/gateway) | 99.51% | 🟢 202 ❌ 1 | 🟢 44 ❌ 1 |
| [Apollo Router](https://www.apollographql.com/) | 97.54% | 🟢 198 ❌ 5 | 🟢 42 ❌ 3 |
| [Apollo Gateway](https://www.apollographql.com/) | 97.04% | 🟢 197 ❌ 6 | 🟢 41 ❌ 4 |
| [Cosmo Router](https://wundergraph.com) | 94.58% | 🟢 192 ❌ 11 | 🟢 38 ❌ 7 |
| [Grafbase Gateway](https://grafbase.com) | 92.12% | 🟢 187 ❌ 16 | 🟢 39 ❌ 6 |
| [Inigo Gateway](https://inigo.io) | 50.25% | 🟢 102101 | 🟢 12 ❌ 33 |

## Detailed Results

Take a closer look at the results for each gateway.

You can look at the full list of tests [here](./src/test-suites/). Every test id corresponds to a directory in the `src/test-suites` folder.

<a id="hive-gateway"></a>
<a id="hive-gateway-router-runtime"></a>

### Hive Gateway
### Hive Gateway (Rust QP)

- [Repository](https://github.com/graphql-hive/gateway)
- [Website](https://the-guild.dev/graphql/hive/gateway)
- [Website](https://the-guild.dev/graphql/hive/docs/gateway/other-features/rust-query-planner)

<details>
<summary>Results</summary>
Expand Down Expand Up @@ -84,6 +84,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -118,12 +120,12 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
</details>

<a id="hive-gateway-router-runtime"></a>
<a id="hive-router"></a>

### Hive Gateway (Rust QP)
### Hive Router

- [Repository](https://github.com/graphql-hive/gateway)
- [Website](https://the-guild.dev/graphql/hive/docs/gateway/other-features/rust-query-planner)
- [Repository](https://github.com/graphql-hive/router)
- [Website](https://github.com/graphql-hive/router)

<details>
<summary>Results</summary>
Expand Down Expand Up @@ -183,6 +185,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -217,12 +221,12 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
</details>

<a id="hive-router"></a>
<a id="hive-gateway"></a>

### Hive Router
### Hive Gateway

- [Repository](https://github.com/graphql-hive/router)
- [Website](https://github.com/graphql-hive/router)
- [Repository](https://github.com/graphql-hive/gateway)
- [Website](https://the-guild.dev/graphql/hive/gateway)

<details>
<summary>Results</summary>
Expand Down Expand Up @@ -282,6 +286,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢❌🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -381,6 +387,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -480,6 +488,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -579,6 +589,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>❌❌</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>❌❌</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -678,6 +690,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>🟢🟢</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down Expand Up @@ -777,6 +791,8 @@ You can look at the full list of tests [here](./src/test-suites/). Every test id
<pre>❌❌</pre>
<a href="./src/test-suites/provides-on-union">provides-on-union</a>
<pre>❌❌</pre>
<a href="./src/test-suites/provides-only-requested-fields">provides-only-requested-fields</a>
<pre>🟢🟢🟢🟢❌🟢🟢🟢🟢🟢🟢</pre>
<a href="./src/test-suites/requires-circular">requires-circular</a>
<pre>❌❌</pre>
<a href="./src/test-suites/requires-interface">requires-interface</a>
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ async function getTestCases(router: ReturnType<typeof createRouter>) {
import("./test-suites/nested-provides/index.js"),
import("./test-suites/provides-on-interface/index.js"),
import("./test-suites/provides-on-union/index.js"),
import("./test-suites/provides-only-requested-fields/index.js"),
import("./test-suites/requires-requires/index.js"),
import("./test-suites/include-skip/index.js"),
import("./test-suites/circular-reference-interface/index.js"),
Expand Down
67 changes: 67 additions & 0 deletions src/test-suites/provides-only-requested-fields/a.subgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { shouldPunishForPoorPlans } from "../../env.js";
import { createSubgraph } from "../../subgraph.js";
import { entities } from "./data.js";

// "a" owns the Entity type. It is the only subgraph that can resolve `extra`,
// but `name` and `description` are also defined here so they have an owner.
//
// In a correct query plan the gateway only calls "a" when the client asks for
// `extra` (the field that "b" cannot @provide). It must NEVER ask "a" for
// `name` or `description` since "b" already declares it can resolve them via
// `@provides`. The throwing field resolvers below catch that mistake.
export default createSubgraph("a", {
typeDefs: /* GraphQL */ `
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@key", "@shareable"]
)

type Query {
_aPlaceholder: Boolean
}

type Entity @key(fields: "id") {
id: ID!
name: String! @shareable
description: String! @shareable
extra: String!
}
`,
resolvers: {
Entity: {
__resolveReference(key: { id: string }) {
const entity = entities.find((e) => e.id === key.id);

if (!entity) {
return null;
}

return {
id: entity.id,
name: entity.name,
description: entity.description,
extra: entity.extra,
};
},
name(parent: { name: string }) {
if (shouldPunishForPoorPlans()) {
throw new Error(
"You should be using the 'b' subgraph for `name` (it is provided via @provides on Query.entity / Query.entities).",
);
}

return parent.name;
},
description(parent: { description: string }) {
if (shouldPunishForPoorPlans()) {
throw new Error(
"You should be using the 'b' subgraph for `description` (it is provided via @provides on Query.entity / Query.entities).",
);
}

return parent.description;
},
},
},
});
89 changes: 89 additions & 0 deletions src/test-suites/provides-only-requested-fields/b.subgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { shouldPunishForPoorPlans } from "../../env.js";
import { createSubgraph } from "../../subgraph.js";
import { entities } from "./data.js";

// "b" is the @provides side. It exposes `Query.entity` / `Query.entities`
// with `@provides(fields: "name description")`, but BOTH `name` and
// `description` are `@external` here because "a" owns them.
//
// `@provides` is purely a HINT that this subgraph can short-circuit the
// resolution of those external fields when (and only when) the client
// actually asks for them. It does NOT mean the gateway should always pull
// every listed field from this subgraph regardless of the client selection.
//
// The custom `description` field resolver below throws in punish mode so we
// can prove (black-box) that the gateway is NOT over-forwarding fields the
// client never requested. None of the client queries in `test.ts` ask for
// `description`, so a well-behaved gateway will never trigger this resolver.
// A buggy gateway that injects every `@provides` field unconditionally will
// trigger it, the field bubbles up to a non-null parent, and the test fails
// with `entity: null`.
export default createSubgraph("b", {
typeDefs: /* GraphQL */ `
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@key", "@external", "@provides"]
)

type Query {
entity: Entity @provides(fields: "name description")
entities: [Entity!]! @provides(fields: "name description")
}

type Entity @key(fields: "id") {
id: ID!
name: String! @external
description: String! @external
}
`,
resolvers: {
Query: {
entity() {
const entity = entities[0];
return {
id: entity.id,
name: entity.name,
description: entity.description,
};
},
entities() {
return entities.map((entity) => ({
id: entity.id,
name: entity.name,
description: entity.description,
}));
},
},
Entity: {
__resolveReference(key: { id: string }) {
if (shouldPunishForPoorPlans()) {
throw new Error(
"You should not be entering 'b' through `__resolveReference` for this suite. The provided fields must be served by the @provides path on Query.entity / Query.entities.",
);
}

const entity = entities.find((e) => e.id === key.id);

if (!entity) {
return null;
}

return {
id: entity.id,
name: entity.name,
description: entity.description,
};
},
description(parent: { description: string }) {
if (shouldPunishForPoorPlans()) {
throw new Error(
"Over-fetch detected: the gateway asked 'b' for `description` even though no test query selects it. `@provides` is a hint, not a forced injection.",
);
}

return parent.description;
},
},
},
});
21 changes: 21 additions & 0 deletions src/test-suites/provides-only-requested-fields/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// The bug this suite covers (gateway over-forwarding @provides fields the
// client never asked for, plus re-delegating to the owner subgraph for
// @provides-covered fields) can only be observed through subgraph-level
// errors when the gateway asks for fields it shouldn't. Those throws are
// gated behind `PUNISH_FOR_POOR_PLANS=1` (see `src/env.ts`) so the default
// audit run remains a pure data-correctness check, while gateway authors
// can flip the env flag to lock down the spec-correct behavior.
export const entities = [
{
id: "e1",
name: "Entity One",
description: "Description One",
extra: "Extra One",
},
{
id: "e2",
name: "Entity Two",
description: "Description Two",
extra: "Extra Two",
},
];
6 changes: 6 additions & 0 deletions src/test-suites/provides-only-requested-fields/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { serve } from "../../supergraph.js";
import a from "./a.subgraph.js";
import b from "./b.subgraph.js";
import test from "./test.js";

export default serve("provides-only-requested-fields", [a, b], test);
Loading
Loading