Skip to content

Commit 950fc03

Browse files
committed
Handle unresolved elements gracefully
1 parent 7d6f234 commit 950fc03

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-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: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ async function executeFetch<TContext>(
237237
const representationToEntity: number[] = [];
238238

239239
entities.forEach((entity, index) => {
240-
const representation = executeSelectionSet(entity, requires);
240+
const representation = executeSelectionSet(entity, requires, context);
241241
if (representation && representation[TypeNameMetaFieldDef.name]) {
242242
representations.push(representation);
243243
representationToEntity.push(index);
@@ -388,9 +388,10 @@ async function executeFetch<TContext>(
388388
* @param source Result of GraphQL execution.
389389
* @param selectionSet
390390
*/
391-
function executeSelectionSet(
391+
function executeSelectionSet<TContext>(
392392
source: Record<string, any> | null,
393393
selectionSet: SelectionSetNode,
394+
context: ExecutionContext<TContext>,
394395
): Record<string, any> {
395396
const result: Record<string, any> = Object.create(null);
396397

@@ -409,16 +410,19 @@ function executeSelectionSet(
409410
break;
410411
}
411412
if (typeof source[responseName] === 'undefined') {
412-
throw new Error(`Field "${responseName}" was not found in response.`);
413+
// collect error but continue to resolve
414+
context.errors.push(new GraphQLError(`Field "${responseName}" was not found in response.`));
415+
break;
413416
}
414417
if (Array.isArray(source[responseName])) {
415418
result[responseName] = source[responseName].map((value: any) =>
416-
selectionSet ? executeSelectionSet(value, selectionSet) : value,
419+
selectionSet ? executeSelectionSet(value, selectionSet, context) : value,
417420
);
418421
} else if (selectionSet) {
419422
result[responseName] = executeSelectionSet(
420423
source[responseName],
421424
selectionSet,
425+
context
422426
);
423427
} else {
424428
result[responseName] = source[responseName];
@@ -433,7 +437,7 @@ function executeSelectionSet(
433437
if (typename === selection.typeCondition.name.value) {
434438
deepMerge(
435439
result,
436-
executeSelectionSet(source, selection.selectionSet),
440+
executeSelectionSet(source, selection.selectionSet, context),
437441
);
438442
}
439443
break;

0 commit comments

Comments
 (0)