Skip to content

Commit 106c3a6

Browse files
committed
Handle unresolved elements gracefully
1 parent a2ff86f commit 106c3a6

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

packages/apollo-gateway/src/__tests__/__fixtures__/schemas/product.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const typeDefs = gql`
88
99
extend type Query {
1010
product(upc: String!): Product
11+
products(upcs: [String!]!): [Product]
1112
vehicle(id: String!): Vehicle
1213
topProducts(first: Int = 5): [Product]
1314
topCars(first: Int = 5): [Car]
@@ -150,7 +151,8 @@ const products = [
150151
{ __typename: 'Book', isbn: '0201633612', price: 49 },
151152
{ __typename: 'Book', isbn: '1234567890', price: 59 },
152153
{ __typename: 'Book', isbn: '404404404', price: 0 },
153-
{ __typename: 'Book', isbn: '0987654321', price: 29 },
154+
{ __typename: 'Book', isbn: '0987654321', price: 29, upc: '0987654321' },
155+
{ __typename: 'Book', isbn: '9999999999', price: 31, upc: '9999999999' },
154156
];
155157

156158
const vehicles = [
@@ -231,6 +233,10 @@ export const resolvers: GraphQLResolverMap<any> = {
231233
product(_, args) {
232234
return products.find(product => product.upc === args.upc);
233235
},
236+
products(_, args) {
237+
const upcs: Array<string> = args.upcs
238+
return upcs.map(upc => products.find(product => product.upc === upc));
239+
},
234240
vehicle(_, args) {
235241
return vehicles.find(vehicles => vehicles.id === args.id);
236242
},

packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,4 +672,50 @@ describe('executeQueryPlan', () => {
672672
}
673673
`);
674674
});
675+
676+
it('can execute queries with selections on unresolved @requires fields', async () => {
677+
// query a book not known to the book service
678+
const query = gql`
679+
query {
680+
products(upcs: ["9999999999", "0987654321"]) {
681+
upc
682+
name
683+
price
684+
}
685+
}
686+
`;
687+
688+
const operationContext = buildOperationContext(schema, query);
689+
const queryPlan = buildQueryPlan(operationContext);
690+
691+
const response = await executeQueryPlan(
692+
queryPlan,
693+
serviceMap,
694+
buildRequestContext(),
695+
operationContext,
696+
);
697+
698+
expect(response.errors?.map(e => e.message)).toEqual(expect.arrayContaining([
699+
"Cannot return null for non-nullable field Book.isbn.",
700+
"Field \"title\" was not found in response.",
701+
"Field \"year\" was not found in response.",
702+
]));
703+
704+
expect(response.data).toMatchInlineSnapshot(`
705+
Object {
706+
"products": Array [
707+
Object {
708+
"name": "undefined (undefined)",
709+
"price": "31",
710+
"upc": "9999999999",
711+
},
712+
Object {
713+
"name": "No Books Like This Book! (2019)",
714+
"price": "29",
715+
"upc": "0987654321",
716+
},
717+
],
718+
}
719+
`);
720+
});
675721
});

packages/apollo-gateway/src/executeQueryPlan.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async function executeFetch<TContext>(
242242
const representationToEntity: number[] = [];
243243

244244
entities.forEach((entity, index) => {
245-
const representation = executeSelectionSet(entity, requires);
245+
const representation = executeSelectionSet(entity, requires, context);
246246
if (representation && representation[TypeNameMetaFieldDef.name]) {
247247
representations.push(representation);
248248
representationToEntity.push(index);
@@ -381,9 +381,10 @@ async function executeFetch<TContext>(
381381
* @param source Result of GraphQL execution.
382382
* @param selectionSet
383383
*/
384-
function executeSelectionSet(
384+
function executeSelectionSet<TContext>(
385385
source: Record<string, any> | null,
386386
selectionSet: SelectionSetNode,
387+
context: ExecutionContext<TContext>,
387388
): Record<string, any> {
388389
const result: Record<string, any> = Object.create(null);
389390

@@ -402,16 +403,18 @@ function executeSelectionSet(
402403
break;
403404
}
404405
if (typeof source[responseName] === 'undefined') {
405-
throw new Error(`Field "${responseName}" was not found in response.`);
406+
context.errors.push(new GraphQLError(`Field "${responseName}" was not found in response.`));
407+
break;
406408
}
407409
if (Array.isArray(source[responseName])) {
408410
result[responseName] = source[responseName].map((value: any) =>
409-
selectionSet ? executeSelectionSet(value, selectionSet) : value,
411+
selectionSet ? executeSelectionSet(value, selectionSet, context) : value,
410412
);
411413
} else if (selectionSet) {
412414
result[responseName] = executeSelectionSet(
413415
source[responseName],
414416
selectionSet,
417+
context
415418
);
416419
} else {
417420
result[responseName] = source[responseName];
@@ -426,7 +429,7 @@ function executeSelectionSet(
426429
if (typename === selection.typeCondition.name.value) {
427430
deepMerge(
428431
result,
429-
executeSelectionSet(source, selection.selectionSet),
432+
executeSelectionSet(source, selection.selectionSet, context),
430433
);
431434
}
432435
break;

0 commit comments

Comments
 (0)