Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Commit

Permalink
Support merging of schema directives (ardatan#1003)
Browse files Browse the repository at this point in the history
* Support merging of schema directives

* Update CHANGELOG.md

* Add missing branch

* Fix recreation of directives
  • Loading branch information
freiksenet authored and yaacovCR committed Jun 2, 2019
1 parent 796e701 commit 11f36f2
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* Fixes a bug where schemas with scalars could not be merged when passed to
`mergeSchemas` as a string or `GraphQLSchema`. <br/>
[@hayes](https://github.com/hayes) in [#1062](https://github.com/apollographql/graphql-tools/pull/1062)
* Make `mergeSchemas` optionally merge directive definitions. <br/>
[@freiksenet](https://github.com/freiksenet) in [#1003](https://github.com/apollographql/graphql-tools/pull/1003)

### 4.0.4

Expand Down
51 changes: 42 additions & 9 deletions src/stitching/mergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
getNamedType,
isNamedType,
parse,
Kind
Kind,
GraphQLDirective,
} from 'graphql';
import {
IDelegateToSchemaOptions,
Expand All @@ -28,6 +29,7 @@ import {
} from '../makeExecutableSchema';
import {
recreateType,
recreateDirective,
fieldMapToFieldConfigMap,
createResolveType,
} from './schemaRecreation';
Expand Down Expand Up @@ -59,32 +61,49 @@ export default function mergeSchemas({
onTypeConflict,
resolvers,
schemaDirectives,
inheritResolversFromInterfaces
inheritResolversFromInterfaces,
mergeDirectives,
}: {
schemas: Array<string | GraphQLSchema | DocumentNode | Array<GraphQLNamedType>>;
schemas: Array<
string | GraphQLSchema | DocumentNode | Array<GraphQLNamedType>
>;
onTypeConflict?: OnTypeConflict;
resolvers?: IResolversParameter;
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
inheritResolversFromInterfaces?: boolean;
mergeDirectives?: boolean,

}): GraphQLSchema {
return mergeSchemasImplementation({ schemas, resolvers, schemaDirectives, inheritResolversFromInterfaces });
return mergeSchemasImplementation({
schemas,
resolvers,
schemaDirectives,
inheritResolversFromInterfaces,
mergeDirectives,
});
}

function mergeSchemasImplementation({
schemas,
resolvers,
schemaDirectives,
inheritResolversFromInterfaces
inheritResolversFromInterfaces,
mergeDirectives,
}: {
schemas: Array<string | GraphQLSchema | DocumentNode | Array<GraphQLNamedType>>;
schemas: Array<
string | GraphQLSchema | DocumentNode | Array<GraphQLNamedType>
>;
resolvers?: IResolversParameter;
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
inheritResolversFromInterfaces?: boolean;
mergeDirectives?: boolean,

}): GraphQLSchema {
const allSchemas: Array<GraphQLSchema> = [];
const typeCandidates: { [name: string]: Array<MergeTypeCandidate> } = {};
const types: { [name: string]: GraphQLNamedType } = {};
const extensions: Array<DocumentNode> = [];
const directives: Array<GraphQLDirective> = [];
const fragments: Array<{
field: string;
fragment: string;
Expand Down Expand Up @@ -122,6 +141,13 @@ function mergeSchemasImplementation({
});
}

if (mergeDirectives) {
const directiveInstances = schema.getDirectives();
directiveInstances.forEach(directive => {
directives.push(directive);
});
}

const typeMap = schema.getTypeMap();
Object.keys(typeMap).forEach(typeName => {
const type: GraphQLNamedType = typeMap[typeName];
Expand All @@ -138,11 +164,17 @@ function mergeSchemasImplementation({
});
}
});
} else if (typeof schema === 'string' || (schema && (schema as DocumentNode).kind === Kind.DOCUMENT)) {
let parsedSchemaDocument = typeof schema === 'string' ? parse(schema) : (schema as DocumentNode);
} else if (
typeof schema === 'string' ||
(schema && (schema as DocumentNode).kind === Kind.DOCUMENT)
) {
let parsedSchemaDocument =
typeof schema === 'string' ? parse(schema) : (schema as DocumentNode);
parsedSchemaDocument.definitions.forEach(def => {
const type = typeFromAST(def);
if (type) {
if (type instanceof GraphQLDirective && mergeDirectives) {
directives.push(type);
} else if (type && !(type instanceof GraphQLDirective)) {
addTypeCandidate(typeCandidates, type.name, {
type: type,
});
Expand Down Expand Up @@ -219,6 +251,7 @@ function mergeSchemasImplementation({
mutation: types.Mutation as GraphQLObjectType,
subscription: types.Subscription as GraphQLObjectType,
types: Object.keys(types).map(key => types[key]),
directives: directives.map((directive) => recreateDirective(directive, resolveType))
});

extensions.forEach(extension => {
Expand Down
14 changes: 14 additions & 0 deletions src/stitching/schemaRecreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
GraphQLScalarType,
GraphQLType,
GraphQLUnionType,
GraphQLDirective,
Kind,
ValueNode,
getNamedType,
Expand Down Expand Up @@ -129,6 +130,19 @@ export function recreateType(
}
}

export function recreateDirective(
directive: GraphQLDirective,
resolveType: ResolveType<any>,
): GraphQLDirective {
return new GraphQLDirective({
name: directive.name,
description: directive.description,
locations: directive.locations,
args: argsToFieldConfigArgumentMap(directive.args, resolveType),
astNode: directive.astNode,
});
}

function parseLiteral(ast: ValueNode): any {
switch (ast.kind) {
case Kind.STRING:
Expand Down
23 changes: 22 additions & 1 deletion src/stitching/typeFromAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {
valueFromAST,
getDescription,
GraphQLString,
GraphQLDirective,
DirectiveDefinitionNode,
DirectiveLocationEnum,
DirectiveLocation,
GraphQLFieldConfig,
StringValueNode,
} from 'graphql';
Expand All @@ -39,7 +43,7 @@ export type GetType = (

export default function typeFromAST(
node: DefinitionNode,
): GraphQLNamedType | null {
): GraphQLNamedType | GraphQLDirective | null {
switch (node.kind) {
case Kind.OBJECT_TYPE_DEFINITION:
return makeObjectType(node);
Expand All @@ -53,6 +57,8 @@ export default function typeFromAST(
return makeScalarType(node);
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
return makeInputObjectType(node);
case Kind.DIRECTIVE_DEFINITION:
return makeDirective(node);
default:
return null;
}
Expand Down Expand Up @@ -220,3 +226,18 @@ function createNamedStub(
},
});
}

function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective {
const locations: Array<DirectiveLocationEnum> = [];
node.locations.forEach(location => {
if (<DirectiveLocationEnum>location.value in DirectiveLocation) {
locations.push(<DirectiveLocationEnum>location.value);
}
});
return new GraphQLDirective({
name: node.name.value,
description: node.description ? node.description.value : null,
args: makeValues(node.arguments),
locations,
});
}
64 changes: 64 additions & 0 deletions src/test/testMergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
subscribe,
parse,
ExecutionResult,
defaultFieldResolver,
GraphQLField,
findDeprecatedUsages,
} from 'graphql';
import mergeSchemas from '../stitching/mergeSchemas';
Expand All @@ -23,6 +25,7 @@ import {
subscriptionPubSub,
subscriptionPubSubTrigger,
} from './testingSchemas';
import { SchemaDirectiveVisitor } from '../schemaVisitor';
import { forAwaitEach } from 'iterall';
import { makeExecutableSchema } from '../makeExecutableSchema';
import { IResolvers } from '../Interfaces';
Expand Down Expand Up @@ -238,6 +241,23 @@ const codeCoverageTypeDefs = `
}
`;

let schemaDirectiveTypeDefs = `
directive @upper on FIELD_DEFINITION
directive @withEnumArg(enumArg: DirectiveEnum = FOO) on FIELD_DEFINITION
enum DirectiveEnum {
FOO
BAR
}
extend type Property {
someField: String! @upper
someOtherField: String! @withEnumArg
someThirdField: String! @withEnumArg(enumArg: BAR)
}
`;

testCombinations.forEach(async combination => {
describe('merging ' + combination.name, () => {
let mergedSchema: GraphQLSchema,
Expand All @@ -262,7 +282,23 @@ testCombinations.forEach(async combination => {
loneExtend,
localSubscriptionSchema,
codeCoverageTypeDefs,
schemaDirectiveTypeDefs,
],
schemaDirectives: {
upper: class extends SchemaDirectiveVisitor {
public visitFieldDefinition(field: GraphQLField<any, any>) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function(...args: any[]) {
const result = await resolve.apply(this, args);
if (typeof result === 'string') {
return result.toUpperCase();
}
return result;
};
}
},
},
mergeDirectives: true,
resolvers: {
Property: {
bookings: {
Expand All @@ -282,6 +318,11 @@ testCombinations.forEach(async combination => {
);
},
},
someField: {
resolve() {
return 'someField';
},
},
},
Booking: {
property: {
Expand Down Expand Up @@ -2645,6 +2686,29 @@ fragment BookingFragment on Booking {
});
});

describe('schema directives', () => {
it('should work with schema directives', async () => {
const result = await graphql(
mergedSchema,
`
query {
propertyById(id: "p1") {
someField
}
}
`,
);

expect(result).to.deep.equal({
data: {
propertyById: {
someField: 'SOMEFIELD',
},
},
});
});
});

describe('regression', () => {
it('should not pass extra arguments to delegates', async () => {
const result = await graphql(
Expand Down

0 comments on commit 11f36f2

Please sign in to comment.