Skip to content

Commit

Permalink
fix: handle @resolveTo on interfaces correctly (#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan authored Dec 3, 2024
1 parent a5d4734 commit 76642d8
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-frogs-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-tools/stitch': patch
---

Fixes the bug when interfaces extended by \`additionalTypeDefs\`
5 changes: 5 additions & 0 deletions .changeset/nice-ducks-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/fusion-runtime': patch
---

Handle \`@resolveTo\` for interfaces correctly
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createTenv } from '@internal/e2e';
import { expect, it } from 'vitest';

const { gateway, service } = createTenv(__dirname);

it('works', async () => {
const { execute } = await gateway({
supergraph: {
with: 'mesh',
services: [await service('Test')],
},
pipeLogs: true,
});

const result = await execute({
query: /* GraphQL */ `
query {
node(id: "1") {
id
... on User {
name
}
self {
id
... on User {
name
}
}
}
}
`,
});

expect(result.errors).toBeFalsy();
expect(result.data.node).toEqual({
id: '1',
name: 'Alice',
self: {
id: '1',
name: 'Alice',
},
});
});
33 changes: 33 additions & 0 deletions e2e/interface-additional-resolvers/mesh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
defineConfig,
loadGraphQLHTTPSubgraph,
} from '@graphql-mesh/compose-cli';
import { Opts } from '@internal/testing';

const opts = Opts(process.argv);

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Test', {
endpoint: `http://localhost:${opts.getServicePort('Test')}/graphql`,
}),
},
],
additionalTypeDefs: /* GraphQL */ `
extend interface Node {
self: Node!
@resolveTo(
sourceName: "Test"
sourceTypeName: "Query"
sourceFieldName: "node"
sourceArgs: { id: "{root.id}" }
requiredSelectionSet: "{ id }"
)
}
extend type User implements Node {
self: Node!
}
`,
});
15 changes: 15 additions & 0 deletions e2e/interface-additional-resolvers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@e2e/interface-additional-resolvers",
"private": true,
"dependencies": {
"@graphql-mesh/compose-cli": "^1.2.0",
"@graphql-mesh/cross-helpers": "^0.4.8",
"@graphql-mesh/store": "^0.103.4",
"@graphql-mesh/types": "^0.103.4",
"@graphql-mesh/utils": "^0.103.4",
"@graphql-tools/utils": "^10.6.0",
"graphql": "^16.9.0",
"graphql-yoga": "^5.10.4",
"tslib": "^2.8.0"
}
}
40 changes: 40 additions & 0 deletions e2e/interface-additional-resolvers/services/Test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createServer } from 'node:http';
import { Opts } from '@internal/testing';
import { createSchema, createYoga } from 'graphql-yoga';

export const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
type Query {
node(id: ID!): Node
user(id: ID!): User
}
`,
resolvers: {
Node: {
__resolveType: (obj: { __typename: string }) => obj.__typename,
},
Query: {
node: () => ({ __typename: 'User', id: '1', name: 'Alice' }),
},
},
}),
maskedErrors: false,
});

const opts = Opts(process.argv);

createServer(yoga).listen(opts.getServicePort('Test'), () => {
console.log(
`🚀 Server ready at http://localhost:${opts.getServicePort('Test')}/graphql`,
);
});
40 changes: 35 additions & 5 deletions packages/fusion-runtime/src/federation/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
GraphQLDirective,
GraphQLSchema,
GraphQLString,
isInterfaceType,
isObjectType,
isOutputType,
Kind,
Expand Down Expand Up @@ -327,9 +328,42 @@ export function handleFederationSubschema({
}
return undefined;
},
[MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName) => {
[MapperKind.INTERFACE_FIELD]: (
fieldConfig,
fieldName,
typeName,
schema,
) => {
const fieldDirectives =
getDirectiveExtensions<FieldDirectives>(fieldConfig);
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
const type = schema.getType(typeName);
if (!isInterfaceType(type)) {
throw new Error(
`Type ${typeName} for field ${fieldName} is not an object type`,
);
}
const fieldMap = type.getFields();
const field = fieldMap[fieldName];
if (!field) {
throw new Error(`Field ${typeName}.${fieldName} not found`);
}
additionalTypeDefs.push({
kind: Kind.DOCUMENT,
definitions: [
{
kind: Kind.INTERFACE_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
fields: [astFromField(field, schema)],
},
],
});
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return null;
}
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find((directive) =>
compareSubgraphNames(directive.subgraph, subgraphName),
Expand All @@ -345,10 +379,6 @@ export function handleFederationSubschema({
}
return [realName, fieldConfig];
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return null;
}
return undefined;
},
[MapperKind.ENUM_VALUE]: (
Expand Down
2 changes: 2 additions & 0 deletions packages/fusion-runtime/src/federation/supergraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ export const handleFederationSupergraph: UnifiedGraphHandler = function ({
);
// @ts-expect-error - Typings are wrong
opts.resolvers = additionalResolvers;
// @ts-expect-error - Typings are wrong
opts.inheritResolversFromInterfaces = true;

if (onDelegationStageExecuteHooks?.length) {
for (const subschema of subschemas) {
Expand Down
16 changes: 14 additions & 2 deletions packages/stitch/src/typeCandidates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import {
import { wrapSchema } from '@graphql-tools/wrap';
import {
DocumentNode,
getNamedType,
GraphQLDirective,
GraphQLNamedType,
GraphQLObjectType,
GraphQLSchema,
isDirective,
isInterfaceType,
isIntrospectionType,
isNamedType,
isSpecifiedScalarType,
OperationTypeNode,
Expand Down Expand Up @@ -137,7 +138,7 @@ export function buildTypeCandidates<
const type = originalTypeMap[typeName] as GraphQLNamedType;
if (
isNamedType(type) &&
getNamedType(type).name.slice(0, 2) !== '__' &&
!isIntrospectionType(type) &&
!rootTypes.has(type as GraphQLObjectType)
) {
addTypeCandidate(typeCandidates, type.name, {
Expand All @@ -156,6 +157,17 @@ export function buildTypeCandidates<
throw new Error(`Expected to get named typed but got ${inspect(def)}`);
}
if (type != null) {
// There is a bug in interface types that causes them to not have _interfaces
// if they are not used in a schema. This is a workaround for that.
if (isInterfaceType(type)) {
try {
type.getInterfaces();
} catch {
Object.defineProperty(type, '_interfaces', {
value: [],
});
}
}
addTypeCandidate(typeCandidates, type.name, { type });
}
}
Expand Down
18 changes: 17 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,22 @@ __metadata:
languageName: unknown
linkType: soft

"@e2e/interface-additional-resolvers@workspace:e2e/interface-additional-resolvers":
version: 0.0.0-use.local
resolution: "@e2e/interface-additional-resolvers@workspace:e2e/interface-additional-resolvers"
dependencies:
"@graphql-mesh/compose-cli": "npm:^1.2.0"
"@graphql-mesh/cross-helpers": "npm:^0.4.8"
"@graphql-mesh/store": "npm:^0.103.4"
"@graphql-mesh/types": "npm:^0.103.4"
"@graphql-mesh/utils": "npm:^0.103.4"
"@graphql-tools/utils": "npm:^10.6.0"
graphql: "npm:^16.9.0"
graphql-yoga: "npm:^5.10.4"
tslib: "npm:^2.8.0"
languageName: unknown
linkType: soft

"@e2e/js-config@workspace:e2e/js-config":
version: 0.0.0-use.local
resolution: "@e2e/js-config@workspace:e2e/js-config"
Expand Down Expand Up @@ -10997,7 +11013,7 @@ __metadata:
languageName: node
linkType: hard

"graphql-yoga@npm:^5.10.3, graphql-yoga@npm:^5.7.0":
"graphql-yoga@npm:^5.10.3, graphql-yoga@npm:^5.10.4, graphql-yoga@npm:^5.7.0":
version: 5.10.4
resolution: "graphql-yoga@npm:5.10.4"
dependencies:
Expand Down

0 comments on commit 76642d8

Please sign in to comment.