-
Notifications
You must be signed in to change notification settings - Fork 575
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: apply fix for non-nullable fields
- Loading branch information
Tomas Kroupa
committed
Nov 5, 2024
1 parent
708d908
commit 4b38cd3
Showing
8 changed files
with
969 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@graphql-yoga/plugin-apollo-usage-report": patch | ||
--- | ||
|
||
### Fixed | ||
- get specific or the nearest possible trace node if something fails at `non-nullable` GraphQL query field |
205 changes: 205 additions & 0 deletions
205
packages/plugins/apollo-inline-trace/__tests__/apollo-inline-trace.yoga-gateway.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
import { versionInfo } from 'graphql'; | ||
import { createYoga, YogaServerInstance } from 'graphql-yoga'; | ||
import { useApolloInlineTrace } from '@graphql-yoga/plugin-apollo-inline-trace'; | ||
import { getStitchedSchemaFromLocalSchemas } from './fixtures/getStitchedSchemaFromLocalSchemas'; | ||
import { getSubgraph1Schema } from './fixtures/subgraph1'; | ||
import { getSubgraph2Schema } from './fixtures/subgraph2'; | ||
|
||
const describeIf = (condition: boolean) => (condition ? describe : describe.skip); | ||
|
||
describeIf(versionInfo.major >= 16)('Inline Trace - Yoga gateway', () => { | ||
let yoga: YogaServerInstance<Record<string, unknown>, Record<string, unknown>>; | ||
|
||
beforeAll(async () => { | ||
const gatewaySchema = await getStitchedSchemaFromLocalSchemas({ | ||
subgraph1: await getSubgraph1Schema(), | ||
subgraph2: await getSubgraph2Schema(), | ||
}); | ||
|
||
yoga = createYoga({ | ||
schema: gatewaySchema, | ||
plugins: [useApolloInlineTrace()], | ||
maskedErrors: false, | ||
}); | ||
}); | ||
|
||
it('nullableFail - federated query - should return result with expected data and errors', async () => { | ||
const query = /* GraphQL */ ` | ||
query { | ||
testNestedField { | ||
nullableFail { | ||
id | ||
sub1 | ||
} | ||
subgraph2 { | ||
id | ||
sub2 | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const expectedData = { | ||
testNestedField: { | ||
nullableFail: null, | ||
subgraph2: { | ||
email: 'user2@example.com', | ||
id: 'user2', | ||
sub2: true, | ||
}, | ||
}, | ||
}; | ||
|
||
const response = await yoga.fetch('http://yoga/graphql', { | ||
method: 'POST', | ||
body: JSON.stringify({ query }), | ||
headers: { | ||
'content-type': 'application/json', | ||
'apollo-federation-include-trace': 'ftv1', | ||
}, | ||
}); | ||
|
||
const result = await response.json(); | ||
|
||
expect(response.status).toBe(200); | ||
expect(result.errors).toMatchObject([ | ||
{ | ||
message: 'My original subgraph error!', | ||
path: ['testNestedField', 'nullableFail'], | ||
}, | ||
]); | ||
expect(result.data).toMatchObject(expectedData); | ||
expect(result.extensions.ftv1).toEqual(expect.any(String)); | ||
}); | ||
|
||
it('nullableFail - simple query - should return result with expected data and errors', async () => { | ||
const query = /* GraphQL */ ` | ||
query { | ||
testNestedField { | ||
nullableFail { | ||
id | ||
sub1 | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const expectedData = { | ||
testNestedField: { | ||
nullableFail: null, | ||
}, | ||
}; | ||
|
||
const response = await yoga.fetch('http://yoga/graphql', { | ||
method: 'POST', | ||
body: JSON.stringify({ query }), | ||
headers: { | ||
'content-type': 'application/json', | ||
'apollo-federation-include-trace': 'ftv1', | ||
}, | ||
}); | ||
|
||
const result = await response.json(); | ||
|
||
expect(response.status).toBe(200); | ||
expect(result.errors).toMatchObject([ | ||
{ | ||
message: 'My original subgraph error!', | ||
path: ['testNestedField', 'nullableFail'], | ||
}, | ||
]); | ||
expect(result.data).toMatchObject(expectedData); | ||
expect(result.extensions.ftv1).toEqual(expect.any(String)); | ||
}); | ||
|
||
it('nonNullableFail - federated query - should return result with expected data and errors', async () => { | ||
const query = /* GraphQL */ ` | ||
query { | ||
testNestedField { | ||
nonNullableFail { | ||
id | ||
sub1 | ||
} | ||
subgraph2 { | ||
id | ||
sub2 | ||
} | ||
} | ||
} | ||
`; | ||
|
||
/** | ||
* the whole query result is { testNestedField: null } even if subgraph2 query not fail, but it is probably ok according GraphQL documentation | ||
* "If the field which experienced an error was declared as Non-Null, the null result will bubble up to the next nullable field." | ||
* https://spec.graphql.org/draft/#sel-GAPHRPTCAACEzBg6S | ||
*/ | ||
const expectedData = { | ||
testNestedField: null, | ||
}; | ||
|
||
const response = await yoga.fetch('http://yoga/graphql', { | ||
method: 'POST', | ||
body: JSON.stringify({ query }), | ||
headers: { | ||
'content-type': 'application/json', | ||
'apollo-federation-include-trace': 'ftv1', | ||
}, | ||
}); | ||
|
||
const result = await response.json(); | ||
|
||
expect(response.status).toBe(200); | ||
expect(result.errors).toMatchObject([ | ||
{ | ||
message: 'Cannot return null for non-nullable field TestNestedField.nonNullableFail.', | ||
path: ['testNestedField', 'nonNullableFail'], | ||
}, | ||
]); | ||
expect(result.data).toMatchObject(expectedData); | ||
expect(result.extensions.ftv1).toEqual(expect.any(String)); | ||
}); | ||
|
||
it('nonNullableFail - simple query - should return result with expected data and errors', async () => { | ||
const query = /* GraphQL */ ` | ||
query { | ||
testNestedField { | ||
nonNullableFail { | ||
id | ||
sub1 | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const expectedData = { | ||
testNestedField: null, | ||
}; | ||
|
||
const response = await yoga.fetch('http://yoga/graphql', { | ||
method: 'POST', | ||
body: JSON.stringify({ query }), | ||
headers: { | ||
'content-type': 'application/json', | ||
'apollo-federation-include-trace': 'ftv1', | ||
}, | ||
}); | ||
|
||
const result = await response.json(); | ||
|
||
expect(response.status).toBe(200); | ||
expect(result.errors).toMatchObject([ | ||
{ | ||
message: 'My original subgraph error!', | ||
path: ['testNestedField', 'nonNullableFail'], | ||
}, | ||
]); | ||
expect(result.data).toMatchObject(expectedData); | ||
expect(result.extensions.ftv1).toEqual(expect.any(String)); | ||
}); | ||
}); |
36 changes: 36 additions & 0 deletions
36
packages/plugins/apollo-inline-trace/__tests__/fixtures/getStitchedSchemaFromLocalSchemas.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import { GraphQLSchema } from 'graphql'; | ||
import { IntrospectAndCompose, LocalGraphQLDataSource } from '@apollo/gateway'; | ||
import { createDefaultExecutor } from '@graphql-tools/delegate'; | ||
import { getStitchedSchemaFromSupergraphSdl } from '@graphql-tools/federation'; | ||
|
||
export async function getStitchedSchemaFromLocalSchemas( | ||
localSchemas: Record<string, GraphQLSchema>, | ||
): Promise<GraphQLSchema> { | ||
const introspectAndCompose = await new IntrospectAndCompose({ | ||
subgraphs: Object.keys(localSchemas).map(name => ({ name, url: `http://localhost/${name}` })), | ||
}).initialize({ | ||
healthCheck: async () => Promise.resolve(), | ||
update: () => undefined, | ||
getDataSource: ({ name }) => { | ||
const [_name, schema] = Object.entries(localSchemas).find(([key]) => key === name) ?? []; | ||
if (schema) { | ||
return new LocalGraphQLDataSource(schema); | ||
} | ||
throw new Error(`Unknown subgraph ${name}`); | ||
}, | ||
}); | ||
|
||
return getStitchedSchemaFromSupergraphSdl({ | ||
supergraphSdl: introspectAndCompose.supergraphSdl, | ||
onSubschemaConfig: cofig => { | ||
const [_name, schema] = | ||
Object.entries(localSchemas).find(([key]) => key === cofig.name.toLowerCase()) ?? []; | ||
if (schema) { | ||
cofig.executor = createDefaultExecutor(schema); | ||
} else { | ||
throw new Error(`Unknown subgraph ${cofig.name}`); | ||
} | ||
}, | ||
}); | ||
} |
52 changes: 52 additions & 0 deletions
52
packages/plugins/apollo-inline-trace/__tests__/fixtures/subgraph1.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import { GraphQLSchema, parse } from 'graphql'; | ||
import { createGraphQLError } from 'graphql-yoga'; | ||
import { buildSubgraphSchema } from '@apollo/subgraph'; | ||
|
||
const typeDefs = parse(/* GraphQL */ ` | ||
type Query { | ||
testNestedField: TestNestedField | ||
} | ||
type TestNestedField { | ||
subgraph1: TestUser1 | ||
nonNullableFail: TestUser1! | ||
nullableFail: TestUser1 | ||
} | ||
type TestUser1 { | ||
id: String! | ||
email: String! | ||
sub1: Boolean! | ||
} | ||
`); | ||
|
||
const resolvers = { | ||
Query: { | ||
testNestedField: () => ({ | ||
subgraph1: () => ({ | ||
id: 'user1', | ||
email: 'user1@example.com', | ||
sub1: true, | ||
}), | ||
nonNullableFail: () => { | ||
throw createGraphQLError('My original subgraph error!', { | ||
extensions: { | ||
code: 'BAD_REQUEST', | ||
}, | ||
}); | ||
}, | ||
nullableFail: () => { | ||
throw createGraphQLError('My original subgraph error!', { | ||
extensions: { | ||
code: 'BAD_REQUEST', | ||
}, | ||
}); | ||
}, | ||
}), | ||
}, | ||
}; | ||
|
||
export function getSubgraph1Schema(): GraphQLSchema { | ||
return buildSubgraphSchema({ typeDefs, resolvers }); | ||
} |
35 changes: 35 additions & 0 deletions
35
packages/plugins/apollo-inline-trace/__tests__/fixtures/subgraph2.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import { GraphQLSchema, parse } from 'graphql'; | ||
import { buildSubgraphSchema } from '@apollo/subgraph'; | ||
|
||
const typeDefs = parse(/* GraphQL */ ` | ||
type Query { | ||
testNestedField: TestNestedField | ||
} | ||
type TestNestedField { | ||
subgraph2: TestUser2 | ||
} | ||
type TestUser2 { | ||
id: String! | ||
email: String! | ||
sub2: Boolean! | ||
} | ||
`); | ||
|
||
const resolvers = { | ||
Query: { | ||
testNestedField: () => ({ | ||
subgraph2: () => ({ | ||
id: 'user2', | ||
email: 'user2@example.com', | ||
sub2: true, | ||
}), | ||
}), | ||
}, | ||
}; | ||
|
||
export function getSubgraph2Schema(): GraphQLSchema { | ||
return buildSubgraphSchema({ typeDefs, resolvers }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.