Skip to content

Commit 3e01db4

Browse files
committed
Add inheritResolversFromInterfaces option
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema` Later on we'll want to support interfaces implementing other interfaces graphql/graphql-spec#295
1 parent 33b367a commit 3e01db4

File tree

4 files changed

+150
-3
lines changed

4 files changed

+150
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* Fix typo in schema-directive.md deprecated example[PR #706](https://github.com/apollographql/graphql-tools/pull/706)
77
* Fix timezone bug in test for @date directive [PR #686](https://github.com/apollographql/graphql-tools/pull/686)
88
* Expose `defaultMergedResolver` from stitching [PR #685](https://github.com/apollographql/graphql-tools/pull/685)
9-
109
* Add `requireResolversForResolveType` to resolver validation options [PR #698](https://github.com/apollographql/graphql-tools/pull/698)
10+
* Add `inheritResolversFromInterfaces` to `makeExecutableSchema` and `addResolveFunctionsToSchema` [PR #720](https://github.com/apollographql/graphql-tools/pull/720)
1111

1212
### v2.23.0
1313

src/Interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export interface IExecutableSchemaDefinition<TContext = any> {
8585
directiveResolvers?: IDirectiveResolvers<any, TContext>;
8686
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
8787
parseOptions?: GraphQLParseOptions;
88+
inheritResolversFromInterfaces?: boolean;
8889
}
8990

9091
export type IFieldIteratorFn = (

src/schemaGenerator.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function _generateSchema(
6969
allowUndefinedInResolve: boolean,
7070
resolverValidationOptions: IResolverValidationOptions,
7171
parseOptions: GraphQLParseOptions,
72+
inheritResolversFromInterfaces: boolean
7273
) {
7374
if (typeof resolverValidationOptions !== 'object') {
7475
throw new SchemaError(
@@ -92,7 +93,7 @@ function _generateSchema(
9293

9394
const schema = buildSchemaFromTypeDefinitions(typeDefinitions, parseOptions);
9495

95-
addResolveFunctionsToSchema(schema, resolvers, resolverValidationOptions);
96+
addResolveFunctionsToSchema(schema, resolvers, resolverValidationOptions, inheritResolversFromInterfaces);
9697

9798
assertResolveFunctionsPresent(schema, resolverValidationOptions);
9899

@@ -117,6 +118,7 @@ function makeExecutableSchema<TContext = any>({
117118
directiveResolvers = null,
118119
schemaDirectives = null,
119120
parseOptions = {},
121+
inheritResolversFromInterfaces = false
120122
}: IExecutableSchemaDefinition<TContext>) {
121123
const jsSchema = _generateSchema(
122124
typeDefs,
@@ -125,6 +127,7 @@ function makeExecutableSchema<TContext = any>({
125127
allowUndefinedInResolve,
126128
resolverValidationOptions,
127129
parseOptions,
130+
inheritResolversFromInterfaces
128131
);
129132
if (typeof resolvers['__schema'] === 'function') {
130133
// TODO a bit of a hack now, better rewrite generateSchema to attach it there.
@@ -386,14 +389,19 @@ function getFieldsForType(type: GraphQLType): GraphQLFieldMap<any, any> {
386389

387390
function addResolveFunctionsToSchema(
388391
schema: GraphQLSchema,
389-
resolveFunctions: IResolvers,
392+
inputResolveFunctions: IResolvers,
390393
resolverValidationOptions: IResolverValidationOptions = {},
394+
inheritResolversFromInterfaces: boolean = false
391395
) {
392396
const {
393397
allowResolversNotInSchema = false,
394398
requireResolversForResolveType,
395399
} = resolverValidationOptions;
396400

401+
const resolveFunctions = inheritResolversFromInterfaces
402+
? extendResolversFromInterfaces(schema, inputResolveFunctions)
403+
: inputResolveFunctions;
404+
397405
Object.keys(resolveFunctions).forEach(typeName => {
398406
const type = schema.getType(typeName);
399407
if (!type && typeName !== '__schema') {
@@ -430,6 +438,7 @@ function addResolveFunctionsToSchema(
430438
return;
431439
}
432440

441+
// object type
433442
const fields = getFieldsForType(type);
434443
if (!fields) {
435444
if (allowResolversNotInSchema) {
@@ -469,6 +478,30 @@ function addResolveFunctionsToSchema(
469478
checkForResolveTypeResolver(schema, requireResolversForResolveType);
470479
}
471480

481+
function extendResolversFromInterfaces(schema: GraphQLSchema, resolvers: IResolvers) {
482+
const typeNames = new Set([
483+
...Object.keys(schema.getTypeMap()),
484+
...Object.keys(resolvers)
485+
]);
486+
487+
const extendedResolvers: IResolvers = {};
488+
typeNames.forEach((typeName) => {
489+
const typeResolvers = resolvers[typeName];
490+
const type = schema.getType(typeName);
491+
if (!(type instanceof GraphQLObjectType)) {
492+
if (typeResolvers) {
493+
extendedResolvers[typeName] = typeResolvers;
494+
}
495+
return;
496+
}
497+
498+
const interfaceResolvers = (type as GraphQLObjectType).getInterfaces().map((iFace) => resolvers[iFace.name]);
499+
extendedResolvers[typeName] = Object.assign({}, ...interfaceResolvers, typeResolvers);
500+
});
501+
502+
return extendedResolvers;
503+
}
504+
472505
// If we have any union or interface types throw if no there is no resolveType or isTypeOf resolvers
473506
function checkForResolveTypeResolver(schema: GraphQLSchema, requireResolversForResolveType?: boolean) {
474507
Object.keys(schema.getTypeMap())

src/test/testSchemaGenerator.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,6 +2571,119 @@ describe('interfaces', () => {
25712571
});
25722572
});
25732573

2574+
describe('interface resolver inheritance', () => {
2575+
it('copies resolvers from the interfaces', async () => {
2576+
const testSchemaWithInterfaceResolvers = `
2577+
interface Node {
2578+
id: ID!
2579+
}
2580+
type User implements Node {
2581+
id: ID!
2582+
name: String!
2583+
}
2584+
type Query {
2585+
user: User!
2586+
}
2587+
schema {
2588+
query: Query
2589+
}
2590+
`;
2591+
const user = { id: 1, name: 'Ada', type: 'User' };
2592+
const resolvers = {
2593+
Node: {
2594+
__resolveType: ({ type }: { type: string }) => type,
2595+
id: ({ id }: { id: number }) => `Node:${id}`,
2596+
},
2597+
User: {
2598+
name: ({ name }: { name: string}) => `User:${name}`
2599+
},
2600+
Query: {
2601+
user: () => user
2602+
}
2603+
};
2604+
const schema = makeExecutableSchema({
2605+
typeDefs: testSchemaWithInterfaceResolvers,
2606+
resolvers,
2607+
inheritResolversFromInterfaces: true,
2608+
resolverValidationOptions: { requireResolversForAllFields: true, requireResolversForResolveType: true }
2609+
});
2610+
const query = `{ user { id name } }`;
2611+
const response = await graphql(schema, query);
2612+
assert.deepEqual(response, {
2613+
data: {
2614+
user: {
2615+
id: `Node:1`,
2616+
name: `User:Ada`
2617+
}
2618+
}
2619+
});
2620+
});
2621+
2622+
it('respects interface order and existing resolvers', async () => {
2623+
const testSchemaWithInterfaceResolvers = `
2624+
interface Node {
2625+
id: ID!
2626+
}
2627+
interface Person {
2628+
id: ID!
2629+
name: String!
2630+
}
2631+
type Replicant implements Node & Person {
2632+
id: ID!
2633+
name: String!
2634+
}
2635+
type Cyborg implements Person & Node {
2636+
id: ID!
2637+
name: String!
2638+
}
2639+
type Query {
2640+
cyborg: Cyborg!
2641+
replicant: Replicant!
2642+
}
2643+
schema {
2644+
query: Query
2645+
}
2646+
`;
2647+
const cyborg = { id: 1, name: 'Alex Murphy', type: 'Cyborg' };
2648+
const replicant = { id: 2, name: 'Rachael Tyrell', type: 'Replicant' };
2649+
const resolvers = {
2650+
Node: {
2651+
__resolveType: ({ type }: { type: string }) => type,
2652+
id: ({ id }: { id: number }) => `Node:${id}`,
2653+
},
2654+
Person: {
2655+
__resolveType: ({ type }: { type: string }) => type,
2656+
id: ({ id }: { id: number }) => `Person:${id}`,
2657+
name: ({ name }: { name: string}) => `Person:${name}`
2658+
},
2659+
Query: {
2660+
cyborg: () => cyborg,
2661+
replicant: () => replicant,
2662+
}
2663+
};
2664+
const schema = makeExecutableSchema({
2665+
typeDefs: testSchemaWithInterfaceResolvers,
2666+
resolvers,
2667+
inheritResolversFromInterfaces: true,
2668+
resolverValidationOptions: { requireResolversForAllFields: true, requireResolversForResolveType: true }
2669+
});
2670+
const query = `{ cyborg { id name } replicant { id name }}`;
2671+
const response = await graphql(schema, query);
2672+
assert.deepEqual(response, {
2673+
data: {
2674+
cyborg: {
2675+
id: `Node:1`,
2676+
name: `Person:Alex Murphy`
2677+
},
2678+
replicant: {
2679+
id: `Person:2`,
2680+
name: `Person:Rachael Tyrell`
2681+
}
2682+
}
2683+
});
2684+
});
2685+
});
2686+
25742687
describe('unions', () => {
25752688
const testSchemaWithUnions = `
25762689
type Post {

0 commit comments

Comments
 (0)