Skip to content

Commit

Permalink
fix(federation/buildSubgraphSchema): do not create an invalid _Entity…
Browse files Browse the repository at this point in the history
… union if there is no entity defined (#5885)
  • Loading branch information
ardatan committed Feb 8, 2024
1 parent bebeb6c commit 2d76909
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/brown-guests-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-tools/federation': patch
---

Avoid creating invalid schema when there is no entity
97 changes: 55 additions & 42 deletions packages/federation/src/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {
visit,
} from 'graphql';
import { ValueOrPromise } from 'value-or-promise';
import { Resolvers } from '@apollo/client';
import { mergeResolvers, mergeTypeDefs } from '@graphql-tools/merge';
import { IExecutableSchemaDefinition, makeExecutableSchema } from '@graphql-tools/schema';
import { printSchemaWithDirectives } from '@graphql-tools/utils';
import { printSchemaWithDirectives, TypeSource } from '@graphql-tools/utils';

export const SubgraphBaseSDL = /* GraphQL */ `
scalar _Any
Expand All @@ -25,7 +26,6 @@ export const SubgraphBaseSDL = /* GraphQL */ `
}
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
}
Expand Down Expand Up @@ -90,50 +90,63 @@ export function buildSubgraphSchema<TContext = any>(
},
});

const entityTypeDefinition = `union _Entity ${entityTypeNames.length > 0 ? '=' : ''} ${entityTypeNames.join(' | ')}`;
const givenResolvers: any = mergeResolvers(opts.resolvers);
const subgraphResolvers = {
_Entity: {
__resolveType: (obj: { __typename: string }) => obj.__typename,
},
Query: {
_entities: (
_root: never,
args: { representations: any[] },
context: TContext,
info: GraphQLResolveInfo,
) =>
ValueOrPromise.all(
args.representations.map(representation =>
new ValueOrPromise(() =>
givenResolvers[representation.__typename]?.__resolveReference?.(
representation,
context,
info,
),
).then(resolvedEntity => {
if (!resolvedEntity) {
return representation;
}
if (!resolvedEntity.__typename) {
resolvedEntity.__typename = representation.__typename;
}
return resolvedEntity;
}),
),
).resolve(),
_service: () => ({}),
},
_Service: {
sdl: (_root: never, _args: never, _context: TContext, info: GraphQLResolveInfo) =>
printSchemaWithDirectives(info.schema),
},
};

const allTypeDefs: TypeSource[] = [typeDefs];
const allResolvers: Resolvers[] = [sdlResolvers, givenResolvers];

if (entityTypeNames.length > 0) {
allTypeDefs.push(`union _Entity = ${entityTypeNames.join(' | ')}`);
allTypeDefs.push(`extend type Query {
_entities(representations: [_Any!]!): [_Entity]!
}`);
allResolvers.push({
_Entity: {
__resolveType: entityTypeResolver,
},
Query: {
_entities: (_root: any, args: { representations: any[] }, context: TContext, info: any) =>
ValueOrPromise.all(
args.representations.map(representation =>
new ValueOrPromise(() =>
givenResolvers[representation.__typename]?.__resolveReference?.(
representation,
context,
info,
),
).then(resolvedEntity => {
if (!resolvedEntity) {
return representation;
}
if (!resolvedEntity.__typename) {
resolvedEntity.__typename = representation.__typename;
}
return resolvedEntity;
}),
),
).resolve(),
},
});
}
return makeExecutableSchema({
assumeValid: true,
assumeValidSDL: true,
...opts,
typeDefs: [entityTypeDefinition, typeDefs],
resolvers: [subgraphResolvers, givenResolvers],
typeDefs: allTypeDefs,
resolvers: allResolvers,
});
}

function entityTypeResolver(obj: { __typename: string }) {
return obj.__typename;
}

const sdlResolvers = {
Query: {
_service: () => ({}),
},
_Service: {
sdl: (_root: never, _args: never, _context: any, info: GraphQLResolveInfo) =>
printSchemaWithDirectives(info.schema),
},
};
11 changes: 4 additions & 7 deletions packages/federation/test/__snapshots__/subgraph.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ directive @composeDirective(name: String!) repeatable on SCHEMA
directive @extends on OBJECT | INTERFACE
union _Entity
scalar _Any
scalar _FieldSet
Expand All @@ -45,7 +43,6 @@ type _Service {
}
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
users: [User!]!
}
Expand Down Expand Up @@ -83,8 +80,6 @@ directive @composeDirective(name: String!) repeatable on SCHEMA
directive @extends on OBJECT | INTERFACE
union _Entity = User
scalar _Any
scalar _FieldSet
Expand All @@ -101,13 +96,15 @@ type _Service {
}
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
users: [User!]!
_entities(representations: [_Any!]!): [_Entity]!
}
type User @key(fields: "id") @extends {
id: ID!
name: String!
}"
}
union _Entity = User"
`;
5 changes: 4 additions & 1 deletion packages/federation/test/subgraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ it('allows a subgraph without any keys', () => {
}
`,
});
expect(printSchemaWithDirectives(subgraphSchema).trim()).toMatchSnapshot();
const schema = printSchemaWithDirectives(subgraphSchema).trim();
expect(schema).toMatchSnapshot();
expect(schema).not.toContain(`_Entity`);
expect(schema).not.toContain(`_entities(representations: [_Any!]!): [_Entity]!`);
});

0 comments on commit 2d76909

Please sign in to comment.