From 8b0802fe6cd129653faac2b72ac7b30a194fd0b0 Mon Sep 17 00:00:00 2001 From: Mattia Lau Date: Thu, 29 Feb 2024 19:44:16 +0800 Subject: [PATCH] fix(apollo): federation interface resolver --- .../tests/code-first-federation/app.module.ts | 3 ++ .../human/character.entity.ts | 10 ++++++ .../human/human.entity.ts | 10 ++++++ .../human/human.module.ts | 7 +++++ .../human/human.resolver.ts | 19 ++++++++++++ .../code-first-federation.spec.ts.snap | 13 ++++++++ .../tests/e2e/code-first-federation.spec.ts | 31 +++++++++++++++++++ .../federation/graphql-federation.factory.ts | 12 +++++-- 8 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 packages/apollo/tests/code-first-federation/human/character.entity.ts create mode 100644 packages/apollo/tests/code-first-federation/human/human.entity.ts create mode 100644 packages/apollo/tests/code-first-federation/human/human.module.ts create mode 100644 packages/apollo/tests/code-first-federation/human/human.resolver.ts diff --git a/packages/apollo/tests/code-first-federation/app.module.ts b/packages/apollo/tests/code-first-federation/app.module.ts index 1e2268aeb..d6a65b523 100644 --- a/packages/apollo/tests/code-first-federation/app.module.ts +++ b/packages/apollo/tests/code-first-federation/app.module.ts @@ -7,13 +7,16 @@ import { PostModule } from './post/post.module'; import { RecipeModule } from './recipe/recipe.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; +import { HumanModule } from './human/human.module'; @Module({ imports: [ UserModule, PostModule, RecipeModule, + HumanModule, GraphQLModule.forRoot({ + inheritResolversFromInterfaces: true, driver: ApolloFederationDriver, includeStacktraceInErrorResponses: false, autoSchemaFile: true, diff --git a/packages/apollo/tests/code-first-federation/human/character.entity.ts b/packages/apollo/tests/code-first-federation/human/character.entity.ts new file mode 100644 index 000000000..1cca58367 --- /dev/null +++ b/packages/apollo/tests/code-first-federation/human/character.entity.ts @@ -0,0 +1,10 @@ +import { Field, ID, InterfaceType } from '@nestjs/graphql'; + +@InterfaceType() +export abstract class Character { + @Field((type) => ID) + id: string; + + @Field() + name: string; +} diff --git a/packages/apollo/tests/code-first-federation/human/human.entity.ts b/packages/apollo/tests/code-first-federation/human/human.entity.ts new file mode 100644 index 000000000..90128e85d --- /dev/null +++ b/packages/apollo/tests/code-first-federation/human/human.entity.ts @@ -0,0 +1,10 @@ +import { ObjectType } from '@nestjs/graphql'; +import { Character } from './character.entity'; + +@ObjectType({ + implements: () => [Character], +}) +export class Human implements Character { + id: string; + name: string; +} diff --git a/packages/apollo/tests/code-first-federation/human/human.module.ts b/packages/apollo/tests/code-first-federation/human/human.module.ts new file mode 100644 index 000000000..28a1ed091 --- /dev/null +++ b/packages/apollo/tests/code-first-federation/human/human.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { HumanResolver } from './human.resolver'; + +@Module({ + providers: [HumanResolver], +}) +export class HumanModule {} diff --git a/packages/apollo/tests/code-first-federation/human/human.resolver.ts b/packages/apollo/tests/code-first-federation/human/human.resolver.ts new file mode 100644 index 000000000..63f814ac9 --- /dev/null +++ b/packages/apollo/tests/code-first-federation/human/human.resolver.ts @@ -0,0 +1,19 @@ +import { Query, Resolver, ResolveField } from '@nestjs/graphql'; +import { Human } from './human.entity'; +import { Character } from './character.entity'; + +@Resolver(() => Character) +export class HumanResolver { + @Query(() => [Human]) + humans(): Human[] { + return [ + { id: '1', name: 'Bob' }, + { id: '2', name: 'Alice' }, + ]; + } + + @ResolveField(() => [Human]) + friends(): Human[] { + return [{ id: '3', name: 'Peter' }]; + } +} diff --git a/packages/apollo/tests/e2e/__snapshots__/code-first-federation.spec.ts.snap b/packages/apollo/tests/e2e/__snapshots__/code-first-federation.spec.ts.snap index 7e5342423..b8ee1f488 100644 --- a/packages/apollo/tests/e2e/__snapshots__/code-first-federation.spec.ts.snap +++ b/packages/apollo/tests/e2e/__snapshots__/code-first-federation.spec.ts.snap @@ -52,11 +52,24 @@ type Recipe implements IRecipe { description: String! } +type Human implements Character { + id: ID! + name: String! + friends: [Human!]! +} + +interface Character { + id: ID! + name: String! + friends: [Human!]! +} + type Query { findPost(id: Float!): Post! getPosts: [Post!]! search: [FederationSearchResultUnion!]! @deprecated(reason: "test") recipe: IRecipe! + humans: [Human!]! _entities(representations: [_Any!]!): [_Entity]! _service: _Service! } diff --git a/packages/apollo/tests/e2e/code-first-federation.spec.ts b/packages/apollo/tests/e2e/code-first-federation.spec.ts index d0c32b911..5ebe1725d 100644 --- a/packages/apollo/tests/e2e/code-first-federation.spec.ts +++ b/packages/apollo/tests/e2e/code-first-federation.spec.ts @@ -89,6 +89,37 @@ describe('Code-first - Federation', () => { }); }); + it('should return the human result with resolve interface', async () => { + const response = await apolloClient.executeOperation({ + query: gql` + { + humans { + id + name + friends { + id + name + } + } + } + `, + }); + expectSingleResult(response).toEqual({ + humans: [ + { + id: '1', + name: 'Bob', + friends: [{ id: '3', name: 'Peter' }], + }, + { + id: '2', + name: 'Alice', + friends: [{ id: '3', name: 'Peter' }], + }, + ], + }); + }); + afterEach(async () => { await app.close(); }); diff --git a/packages/graphql/lib/federation/graphql-federation.factory.ts b/packages/graphql/lib/federation/graphql-federation.factory.ts index 11705b167..efd72a388 100644 --- a/packages/graphql/lib/federation/graphql-federation.factory.ts +++ b/packages/graphql/lib/federation/graphql-federation.factory.ts @@ -139,9 +139,15 @@ export class GraphQLFederationFactory { typeDefs = typeDefsDecorator.decorate(typeDefs, federationOptions); } - let executableSchema: GraphQLSchema = buildFederatedSchema({ - typeDefs: gql(typeDefs), - resolvers: this.getResolvers(options.resolvers), + const resolvers = this.getResolvers(options.resolvers); + let executableSchema: GraphQLSchema = addResolversToSchema({ + schema: buildFederatedSchema({ + typeDefs: gql(typeDefs), + resolvers, + }), + resolvers, + resolverValidationOptions: options.resolverValidationOptions, + inheritResolversFromInterfaces: options.inheritResolversFromInterfaces, }); executableSchema = this.overrideOrExtendResolvers(