Skip to content

Commit

Permalink
Add inheritResolversFromInterfaces option
Browse files Browse the repository at this point in the history
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema`

Later on we'll want to support interfaces implementing other interfaces
graphql/graphql-spec#295
  • Loading branch information
reconbot committed Apr 6, 2018
1 parent 33b367a commit bf9e1af
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/Interfaces.ts
Expand Up @@ -85,6 +85,7 @@ export interface IExecutableSchemaDefinition<TContext = any> {
directiveResolvers?: IDirectiveResolvers<any, TContext>;
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
parseOptions?: GraphQLParseOptions;
inheritResolversFromInterfaces?: boolean;
}

export type IFieldIteratorFn = (
Expand Down
37 changes: 35 additions & 2 deletions src/schemaGenerator.ts
Expand Up @@ -69,6 +69,7 @@ function _generateSchema(
allowUndefinedInResolve: boolean,
resolverValidationOptions: IResolverValidationOptions,
parseOptions: GraphQLParseOptions,
inheritResolversFromInterfaces: boolean
) {
if (typeof resolverValidationOptions !== 'object') {
throw new SchemaError(
Expand All @@ -92,7 +93,7 @@ function _generateSchema(

const schema = buildSchemaFromTypeDefinitions(typeDefinitions, parseOptions);

addResolveFunctionsToSchema(schema, resolvers, resolverValidationOptions);
addResolveFunctionsToSchema(schema, resolvers, resolverValidationOptions, inheritResolversFromInterfaces);

assertResolveFunctionsPresent(schema, resolverValidationOptions);

Expand All @@ -117,6 +118,7 @@ function makeExecutableSchema<TContext = any>({
directiveResolvers = null,
schemaDirectives = null,
parseOptions = {},
inheritResolversFromInterfaces = false
}: IExecutableSchemaDefinition<TContext>) {
const jsSchema = _generateSchema(
typeDefs,
Expand All @@ -125,6 +127,7 @@ function makeExecutableSchema<TContext = any>({
allowUndefinedInResolve,
resolverValidationOptions,
parseOptions,
inheritResolversFromInterfaces
);
if (typeof resolvers['__schema'] === 'function') {
// TODO a bit of a hack now, better rewrite generateSchema to attach it there.
Expand Down Expand Up @@ -386,14 +389,19 @@ function getFieldsForType(type: GraphQLType): GraphQLFieldMap<any, any> {

function addResolveFunctionsToSchema(
schema: GraphQLSchema,
resolveFunctions: IResolvers,
inputResolveFunctions: IResolvers,
resolverValidationOptions: IResolverValidationOptions = {},
inheritResolversFromInterfaces: boolean = false
) {
const {
allowResolversNotInSchema = false,
requireResolversForResolveType,
} = resolverValidationOptions;

const resolveFunctions = inheritResolversFromInterfaces
? extendResolversFromInterfaces(schema, inputResolveFunctions)
: inputResolveFunctions;

Object.keys(resolveFunctions).forEach(typeName => {
const type = schema.getType(typeName);
if (!type && typeName !== '__schema') {
Expand Down Expand Up @@ -430,6 +438,7 @@ function addResolveFunctionsToSchema(
return;
}

// object type
const fields = getFieldsForType(type);
if (!fields) {
if (allowResolversNotInSchema) {
Expand Down Expand Up @@ -469,6 +478,30 @@ function addResolveFunctionsToSchema(
checkForResolveTypeResolver(schema, requireResolversForResolveType);
}

function extendResolversFromInterfaces(schema: GraphQLSchema, resolvers: IResolvers) {
const typeNames = new Set([
...Object.keys(schema.getTypeMap()),
...Object.keys(resolvers)
]);

const extendedResolvers: IResolvers = {};
typeNames.forEach((typeName) => {
const typeResolvers = resolvers[typeName];
const type = schema.getType(typeName);
if (!(type instanceof GraphQLObjectType)) {
if (typeResolvers) {
extendedResolvers[typeName] = typeResolvers;
}
return;
}

const interfaceResolvers = (type as GraphQLObjectType).getInterfaces().map((iFace) => resolvers[iFace.name]);
extendedResolvers[typeName] = Object.assign({}, ...interfaceResolvers, typeResolvers);
});

return extendedResolvers;
}

// If we have any union or interface types throw if no there is no resolveType or isTypeOf resolvers
function checkForResolveTypeResolver(schema: GraphQLSchema, requireResolversForResolveType?: boolean) {
Object.keys(schema.getTypeMap())
Expand Down
113 changes: 113 additions & 0 deletions src/test/testSchemaGenerator.ts
Expand Up @@ -2571,6 +2571,119 @@ describe('interfaces', () => {
});
});

describe('interface resolver inheritance', () => {
it('copies resolvers from the interfaces', async () => {
const testSchemaWithInterfaceResolvers = `
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
type Query {
user: User!
}
schema {
query: Query
}
`;
const user = { id: 1, name: 'Ada', type: 'User' };
const resolvers = {
Node: {
__resolveType: ({ type }: { type: string }) => type,
id: ({ id }: { id: number }) => `Node:${id}`,
},
User: {
name: ({ name }: { name: string}) => `User:${name}`
},
Query: {
user: () => user
}
};
const schema = makeExecutableSchema({
typeDefs: testSchemaWithInterfaceResolvers,
resolvers,
inheritResolversFromInterfaces: true,
resolverValidationOptions: { requireResolversForAllFields: true, requireResolversForResolveType: true }
});
const query = `{ user { id name } }`;
const response = await graphql(schema, query);
assert.deepEqual(response, {
data: {
user: {
id: `Node:1`,
name: `User:Ada`
}
}
});
});

it('respects interface order and existing resolvers', async () => {
const testSchemaWithInterfaceResolvers = `
interface Node {
id: ID!
}
interface Person {
id: ID!
name: String!
}
type Replicant implements Node & Person {
id: ID!
name: String!
}
type Cyborg implements Person & Node {
id: ID!
name: String!
}
type Query {
cyborg: Cyborg!
replicant: Replicant!
}
schema {
query: Query
}
`;
const cyborg = { id: 1, name: 'Alex Murphy', type: 'Cyborg' };
const replicant = { id: 2, name: 'Rachael Tyrell', type: 'Replicant' };
const resolvers = {
Node: {
__resolveType: ({ type }: { type: string }) => type,
id: ({ id }: { id: number }) => `Node:${id}`,
},
Person: {
__resolveType: ({ type }: { type: string }) => type,
id: ({ id }: { id: number }) => `Person:${id}`,
name: ({ name }: { name: string}) => `Person:${name}`
},
Query: {
cyborg: () => cyborg,
replicant: () => replicant,
}
};
const schema = makeExecutableSchema({
typeDefs: testSchemaWithInterfaceResolvers,
resolvers,
inheritResolversFromInterfaces: true,
resolverValidationOptions: { requireResolversForAllFields: true, requireResolversForResolveType: true }
});
const query = `{ cyborg { id name } replicant { id name }}`;
const response = await graphql(schema, query);
assert.deepEqual(response, {
data: {
cyborg: {
id: `Node:1`,
name: `Person:Alex Murphy`
},
replicant: {
id: `Person:2`,
name: `Person:Rachael Tyrell`
}
}
});
});
});

describe('unions', () => {
const testSchemaWithUnions = `
type Post {
Expand Down

0 comments on commit bf9e1af

Please sign in to comment.