apollographql / apollographql/federation

ApolloServer returns null for whole request when one of `__resolveReference` returns `null`

Open
#752 7 comments 1 reaction 0 assignees View on GitHub
Dominant language
TypeScript
Stars
725
Forks
276
Avg merge
1h 47m
Merged PRs (30d)
1

Description

ApolloServer returns null for whole request when one of `__resolveReference` returns `null`.
That happens only when array item type is not optional.

```
assets: [Asset!]!
```

Code to reproduce:
```ts
import { buildFederatedSchema } from '@apollo/federation';
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer, gql, ServerInfo } from 'apollo-server';
import { ApolloServerTestClient, createTestClient } from 'apollo-server-testing';

describe('#handler', () => {
let gatewayServer: ApolloServer;
let landingPagesService: ServerInfo;
let assetsService: ServerInfo;
let testClient: ApolloServerTestClient;

beforeAll(async () => {
landingPagesService = await runLandingPages();
assetsService = await runAssets();

const gateway = new ApolloGateway({
serviceList: [
{ name: 'landingPages', url: `${landingPagesService.url}graphql` },
{ name: 'assets', url: `${assetsService.url}graphql` },
],
});

gatewayServer = new ApolloServer({
gateway,
engine: false,
subscriptions: false,
logger: console,
});

testClient = createTestClient(gatewayServer);
});

afterAll(() => {
landingPagesService.server.close();
assetsService.server.close();
});

it('should perform query', async () => {
// arrange
// run query against the server and snapshot the output
const body = await testClient.query({
query: '{ landingPage { assets { id name } } }',
});

expect(body.errors).not.toBeDefined();
expect(body.data).not.toBe(null);
expect(body.data).toMatchSnapshot();
}, 10000);
});

async function runAssets() {
const typeDefs = gql`
type Asset @key(fields: "id") {
id: String!
name: String!
}
`;

const assets = [
{
id: '1',
name: 'Asset0',
},
{
id: '2',
name: 'Asset1',
},
];

const resolvers = {
Asset: {
// eslint-disable-next-line @typescript-eslint/naming-convention
__resolveReference(object: any) {
return assets.find(asset => asset.id === object.id);
},
},
};

const server = new ApolloServer({
schema: buildFederatedSchema([
{
typeDefs,
resolvers,
},
]),
engine: false,
tracing: false,
});

const listener = await server.listen({ port: 40001 });

return listener;
}

async function runLandingPages() {
const typeDefs = gql`
extend type Query {
landingPage: LandingPage!
}

extend type Asset @key(fields: "id") {
id: String! @external
}

type LandingPage {
assets: [Asset!]!
}
`;

const resolvers = {
Query: {
landingPage() {
return {
assets: [
{ id: '1' },
{ id: '2' },
// uncomment to see incorrect behavior
// { id: '10' },
],
};
},
},
};

const server = new ApolloServer({
schema: buildFederatedSchema([
{
typeDefs,
resolvers,
},
]),
engine: false,
tracing: false,
});

const listener = await server.listen({ port: 40002 });

return listener;
}

```

### The expected behavior

Null items are filter out, and `errors` is filled.

### The actual behavior.

`data` is null.

versions:
- @apollo/gateway@0.28.1
- apollo-server@2.24.0
- apollo-server-testing@2.24.0

Contributor guide

Open the contributing guide

Research direction

Start by running the supplied TypeScript reproduction using ApolloGateway, buildFederatedSchema, and the __resolveReference entry point, including the commented-out missing asset. Trace how the gateway handles a null reference for the non-null assets: [Asset!]! field. Done means the response follows the reported expectation, with null items filtered, errors populated, and data remaining non-null.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, typescript
Domain
api, backend, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.