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

Commit

Permalink
fix(SchemaDirectiveVisitor): visit directives added via extend
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Mar 12, 2020
1 parent 9ac6810 commit 27b39ad
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 22 deletions.
130 changes: 113 additions & 17 deletions src/test/testDirectives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isNonNullType,
isScalarType,
isListType,
TypeSystemExtensionNode,
} from 'graphql';
import { assert } from 'chai';
import formatDate from 'dateformat';
Expand All @@ -36,60 +37,87 @@ import { visitSchema } from '../utils/visitSchema';

const typeDefs = `
directive @schemaDirective(role: String) on SCHEMA
directive @schemaExtensionDirective(role: String) on SCHEMA
directive @queryTypeDirective on OBJECT
directive @queryTypeExtensionDirective on OBJECT
directive @queryFieldDirective on FIELD_DEFINITION
directive @enumTypeDirective on ENUM
directive @enumTypeExtensionDirective on ENUM
directive @enumValueDirective on ENUM_VALUE
directive @dateDirective(tz: String) on SCALAR
directive @dateExtensionDirective(tz: String) on SCALAR
directive @interfaceDirective on INTERFACE
directive @interfaceExtensionDirective on INTERFACE
directive @interfaceFieldDirective on FIELD_DEFINITION
directive @inputTypeDirective on INPUT_OBJECT
directive @inputTypeExtensionDirective on INPUT_OBJECT
directive @inputFieldDirective on INPUT_FIELD_DEFINITION
directive @mutationTypeDirective on OBJECT
directive @mutationTypeExtensionDirective on OBJECT
directive @mutationArgumentDirective on ARGUMENT_DEFINITION
directive @mutationMethodDirective on FIELD_DEFINITION
directive @objectTypeDirective on OBJECT
directive @objectTypeExtensionDirective on OBJECT
directive @objectFieldDirective on FIELD_DEFINITION
directive @unionDirective on UNION
directive @unionExtensionDirective on UNION
schema @schemaDirective(role: "admin") {
query: Query
mutation: Mutation
}
extend schema @schemaExtensionDirective(role: "admin")
type Query @queryTypeDirective {
people: [Person] @queryFieldDirective
}
extend type Query @queryTypeExtensionDirective
enum Gender @enumTypeDirective {
NONBINARY @enumValueDirective
FEMALE
MALE
}
extend enum Gender @enumTypeExtensionDirective
scalar Date @dateDirective(tz: "utc")
extend scalar Date @dateExtensionDirective(tz: "utc")
interface Named @interfaceDirective {
name: String! @interfaceFieldDirective
}
extend interface Named @interfaceExtensionDirective
input PersonInput @inputTypeDirective {
name: String! @inputFieldDirective
gender: Gender
}
extend input PersonInput @inputTypeExtensionDirective
type Mutation @mutationTypeDirective {
addPerson(
input: PersonInput @mutationArgumentDirective
): Person @mutationMethodDirective
}
extend type Mutation @mutationTypeExtensionDirective
type Person implements Named @objectTypeDirective {
id: ID! @objectFieldDirective
name: String!
}
extend type Person @objectTypeExtensionDirective
union WhateverUnion @unionDirective = Person | Query | Mutation
extend union WhateverUnion @unionExtensionDirective
`;

describe('@directives', () => {
Expand All @@ -107,7 +135,7 @@ describe('@directives', () => {

function checkDirectives(
type: VisitableSchemaType,
typeDirectiveNames: [string],
typeDirectiveNames: Array<string>,
fieldDirectiveMap: { [key: string]: Array<string> } = {},
) {
assert.deepEqual(getDirectiveNames(type), typeDirectiveNames);
Expand All @@ -121,17 +149,36 @@ describe('@directives', () => {
}

function getDirectiveNames(type: VisitableSchemaType): Array<string> {
return type.astNode.directives.map(d => d.name.value);
let directives = type.astNode.directives.map(d => d.name.value);
const extensionASTNodes = (type as {
extensionASTNodes?: Array<TypeSystemExtensionNode>;
}).extensionASTNodes;
if (extensionASTNodes != null) {
extensionASTNodes.forEach(extensionASTNode => {
directives = directives.concat(
extensionASTNode.directives.map(d => d.name.value),
);
});
}
return directives;
}

assert.deepEqual(getDirectiveNames(schema), ['schemaDirective']);
assert.deepEqual(getDirectiveNames(schema), [
'schemaDirective',
'schemaExtensionDirective',
]);

checkDirectives(schema.getQueryType(), ['queryTypeDirective'], {
people: ['queryFieldDirective'],
});
checkDirectives(
schema.getQueryType(),
['queryTypeDirective', 'queryTypeExtensionDirective'],
{
people: ['queryFieldDirective'],
},
);

assert.deepEqual(getDirectiveNames(schema.getType('Gender')), [
'enumTypeDirective',
'enumTypeExtensionDirective',
]);

const nonBinary = (schema.getType(
Expand All @@ -141,39 +188,51 @@ describe('@directives', () => {

checkDirectives(schema.getType('Date') as GraphQLObjectType, [
'dateDirective',
'dateExtensionDirective',
]);

checkDirectives(
schema.getType('Named') as GraphQLObjectType,
['interfaceDirective'],
['interfaceDirective', 'interfaceExtensionDirective'],
{
name: ['interfaceFieldDirective'],
},
);

checkDirectives(
schema.getType('PersonInput') as GraphQLObjectType,
['inputTypeDirective'],
['inputTypeDirective', 'inputTypeExtensionDirective'],
{
name: ['inputFieldDirective'],
gender: [],
},
);

checkDirectives(schema.getMutationType(), ['mutationTypeDirective'], {
addPerson: ['mutationMethodDirective'],
});
checkDirectives(
schema.getMutationType(),
['mutationTypeDirective', 'mutationTypeExtensionDirective'],
{
addPerson: ['mutationMethodDirective'],
},
);
assert.deepEqual(
getDirectiveNames(schema.getMutationType().getFields().addPerson.args[0]),
['mutationArgumentDirective'],
);

checkDirectives(schema.getType('Person'), ['objectTypeDirective'], {
id: ['objectFieldDirective'],
name: [],
});
checkDirectives(
schema.getType('Person'),
['objectTypeDirective', 'objectTypeExtensionDirective'],
{
id: ['objectFieldDirective'],
name: [],
},
);

checkDirectives(schema.getType('WhateverUnion'), ['unionDirective']);
checkDirectives(schema.getType('WhateverUnion'), [
'unionDirective',
'unionExtensionDirective',
]);
});

it('works with enum and its resolvers', () => {
Expand Down Expand Up @@ -218,6 +277,13 @@ describe('@directives', () => {
visited.add(object);
}
},
queryTypeExtensionDirective: class extends SchemaDirectiveVisitor {
public static description = 'A @directive for query object types';
public visitObject(object: GraphQLObjectType) {
assert.strictEqual(object, schema.getQueryType());
visited.add(object);
}
},
});

assert.strictEqual(visited.size, 1);
Expand All @@ -232,9 +298,15 @@ describe('@directives', () => {
visited.push(s);
}
},
schemaExtensionDirective: class extends SchemaDirectiveVisitor {
public visitSchema(s: GraphQLSchema) {
visited.push(s);
}
},
});
assert.strictEqual(visited.length, 1);
assert.strictEqual(visited.length, 2);
assert.strictEqual(visited[0], schema);
assert.strictEqual(visited[1], schema);
});

it('can visit fields within object types', () => {
Expand All @@ -254,6 +326,14 @@ describe('@directives', () => {
}
},

mutationTypeExtensionDirective: class extends SchemaDirectiveVisitor {
public visitObject(object: GraphQLObjectType) {
mutationObjectType = object;
assert.strictEqual(this.visitedType, object);
assert.strictEqual(object.name, 'Mutation');
}
},

mutationMethodDirective: class extends SchemaDirectiveVisitor {
public visitFieldDefinition(
field: GraphQLField<any, any>,
Expand Down Expand Up @@ -293,6 +373,14 @@ describe('@directives', () => {
}
},

enumTypeExtensionDirective: class extends SchemaDirectiveVisitor {
public visitEnum(enumType: GraphQLEnumType) {
assert.strictEqual(this.visitedType, enumType);
assert.strictEqual(enumType.name, 'Gender');
enumObjectType = enumType;
}
},

enumValueDirective: class extends SchemaDirectiveVisitor {
public visitEnumValue(
value: GraphQLEnumValue,
Expand All @@ -315,6 +403,14 @@ describe('@directives', () => {
}
},

inputTypeExtensionDirective: class extends SchemaDirectiveVisitor {
public visitInputObject(object: GraphQLInputObjectType) {
inputObjectType = object;
assert.strictEqual(this.visitedType, object);
assert.strictEqual(object.name, 'PersonInput');
}
},

inputFieldDirective: class extends SchemaDirectiveVisitor {
public visitInputFieldDefinition(
field: GraphQLInputField,
Expand Down
17 changes: 12 additions & 5 deletions src/utils/SchemaDirectiveVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
GraphQLDirective,
GraphQLSchema,
DirectiveLocationEnum,
TypeSystemExtensionNode,
} from 'graphql';
import { getArgumentValues } from 'graphql/execution/values';

Expand Down Expand Up @@ -138,13 +139,19 @@ export class SchemaDirectiveVisitor extends SchemaVisitor {
type: VisitableSchemaType,
methodName: string,
): Array<SchemaDirectiveVisitor> {
const visitors: Array<SchemaDirectiveVisitor> = [];
const directiveNodes =
type.astNode != null ? type.astNode.directives : null;
if (!directiveNodes) {
return visitors;
let directiveNodes = type.astNode != null ? type.astNode.directives : [];

const extensionASTNodes: ReadonlyArray<TypeSystemExtensionNode> = (type as {
extensionASTNodes?: Array<TypeSystemExtensionNode>;
}).extensionASTNodes;

if (extensionASTNodes != null) {
extensionASTNodes.forEach(extensionASTNode => {
directiveNodes = directiveNodes.concat(extensionASTNode.directives);
});
}

const visitors: Array<SchemaDirectiveVisitor> = [];
directiveNodes.forEach(directiveNode => {
const directiveName = directiveNode.name.value;
if (!hasOwn.call(directiveVisitors, directiveName)) {
Expand Down

0 comments on commit 27b39ad

Please sign in to comment.