Skip to content

Allow providing directives to GraphQLSchema #215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/type/__tests__/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,17 @@ describe('Type System: A Schema must have Object root types', () => {
);
});

it('rejects a Schema whose directives are incorrectly typed', () => {
expect(
() => new GraphQLSchema({
query: SomeObjectType,
directives: [ 'somedirective' ]
})
).to.throw(
'Schema directives must be Array<GraphQLDirective> if provided but got: somedirective.'
);
});

});

describe('Type System: A Schema must contain uniquely named types', () => {
Expand Down
1 change: 1 addition & 0 deletions src/type/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ export type GraphQLFieldConfigArgumentMap = {
export type GraphQLArgumentConfig = {
type: GraphQLInputType;
defaultValue?: any;
description?: ?string;
}

export type GraphQLFieldConfigMap = {
Expand Down
18 changes: 9 additions & 9 deletions src/type/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ export class GraphQLDirective {
constructor(config: GraphQLDirectiveConfig) {
this.name = config.name;
this.description = config.description;
this.args = config.args;
this.onOperation = config.onOperation;
this.onFragment = config.onFragment;
this.onField = config.onField;
this.args = config.args || [];
this.onOperation = Boolean(config.onOperation);
this.onFragment = Boolean(config.onFragment);
this.onField = Boolean(config.onField);
}
}

type GraphQLDirectiveConfig = {
name: string;
description?: string;
args: Array<GraphQLArgument>;
onOperation: boolean;
onFragment: boolean;
onField: boolean;
description?: ?string;
args?: ?Array<GraphQLArgument>;
onOperation?: ?boolean;
onFragment?: ?boolean;
onField?: ?boolean;
}

/**
Expand Down
46 changes: 34 additions & 12 deletions src/type/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import {
GraphQLNonNull
} from './definition';
import type { GraphQLType } from './definition';
import { GraphQLIncludeDirective, GraphQLSkipDirective } from './directives';
import type { GraphQLDirective } from './directives';
import {
GraphQLDirective,
GraphQLIncludeDirective,
GraphQLSkipDirective
} from './directives';
import { __Schema } from './introspection';
import find from '../jsutils/find';
import invariant from '../jsutils/invariant';
Expand All @@ -40,30 +43,51 @@ import invariant from '../jsutils/invariant';
*
*/
export class GraphQLSchema {
_schemaConfig: GraphQLSchemaConfig;
_typeMap: TypeMap;
_queryType: GraphQLObjectType;
_mutationType: ?GraphQLObjectType;
_subscriptionType: ?GraphQLObjectType;
_directives: Array<GraphQLDirective>;
_typeMap: TypeMap;

constructor(config: GraphQLSchemaConfig) {
invariant(
typeof config === 'object',
'Must provide configuration object.'
);

invariant(
config.query instanceof GraphQLObjectType,
`Schema query must be Object Type but got: ${config.query}.`
);
this._queryType = config.query;

invariant(
!config.mutation || config.mutation instanceof GraphQLObjectType,
`Schema mutation must be Object Type if provided but ` +
`got: ${config.mutation}.`
);
this._mutationType = config.mutation;

invariant(
!config.subscription || config.subscription instanceof GraphQLObjectType,
`Schema subscription must be Object Type if provided but ` +
`got: ${config.subscription}.`
);
this._schemaConfig = config;
this._subscriptionType = config.subscription;

invariant(
!config.directives ||
Array.isArray(config.directives) && config.directives.every(
directive => directive instanceof GraphQLDirective
),
`Schema directives must be Array<GraphQLDirective> if provided but ` +
`got: ${config.directives}.`
);
// Provide `@include() and `@skip()` directives by default.
this._directives = config.directives || [
GraphQLIncludeDirective,
GraphQLSkipDirective
];

// Build type map now to detect any errors within this schema.
this._typeMap = [
Expand All @@ -85,15 +109,15 @@ export class GraphQLSchema {
}

getQueryType(): GraphQLObjectType {
return this._schemaConfig.query;
return this._queryType;
}

getMutationType(): ?GraphQLObjectType {
return this._schemaConfig.mutation;
return this._mutationType;
}

getSubscriptionType(): ?GraphQLObjectType {
return this._schemaConfig.subscription;
return this._subscriptionType;
}

getTypeMap(): TypeMap {
Expand All @@ -105,10 +129,7 @@ export class GraphQLSchema {
}

getDirectives(): Array<GraphQLDirective> {
return this._directives || (this._directives = [
GraphQLIncludeDirective,
GraphQLSkipDirective
]);
return this._directives;
}

getDirective(name: string): ?GraphQLDirective {
Expand All @@ -122,6 +143,7 @@ type GraphQLSchemaConfig = {
query: GraphQLObjectType;
mutation?: ?GraphQLObjectType;
subscription?: ?GraphQLObjectType;
directives?: ?Array<GraphQLDirective>;
}

function typeMapReducer(map: TypeMap, type: ?GraphQLType): TypeMap {
Expand Down
26 changes: 26 additions & 0 deletions src/utilities/__tests__/buildClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
GraphQLBoolean,
GraphQLID,
} from '../../';
import { GraphQLDirective } from '../../type/directives';


// Test property:
Expand Down Expand Up @@ -477,6 +478,31 @@ describe('Type System: build schema from introspection', () => {
await testSchema(schema);
});

it('builds a schema with custom directives', async () => {

var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Simple',
description: 'This is a simple type',
fields: {
string: {
type: GraphQLString,
description: 'This is a string field'
}
}
}),
directives: [
new GraphQLDirective({
name: 'customDirective',
description: 'This is a custom directive',
onField: true,
})
]
});

await testSchema(schema);
});

it('cannot use client schema for general execution', async () => {
var customScalar = new GraphQLScalarType({
name: 'CustomScalar',
Expand Down
61 changes: 43 additions & 18 deletions src/utilities/buildClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
GraphQLID
} from '../type/scalars';

import { GraphQLDirective } from '../type/directives';

import { TypeKind } from '../type/introspection';

import type {
Expand Down Expand Up @@ -292,19 +294,35 @@ export function buildClientSchema(
return keyValMap(
inputValueIntrospections,
inputValue => inputValue.name,
inputValue => {
var description = inputValue.description;
var type = getInputType(inputValue.type);
var defaultValue = inputValue.defaultValue ?
valueFromAST(parseValue(inputValue.defaultValue), type) :
null;
return { description, type, defaultValue };
}
buildInputValue
);
}

function buildInputValue(inputValueIntrospection) {
var type = getInputType(inputValueIntrospection.type);
var defaultValue = inputValueIntrospection.defaultValue ?
valueFromAST(parseValue(inputValueIntrospection.defaultValue), type) :
null;
return {
name: inputValueIntrospection.name,
description: inputValueIntrospection.description,
type,
defaultValue,
};
}

function buildDirective(directiveIntrospection) {
return new GraphQLDirective({
name: directiveIntrospection.name,
description: directiveIntrospection.description,
args: directiveIntrospection.args.map(buildInputValue),
onOperation: directiveIntrospection.onOperation,
onFragment: directiveIntrospection.onFragment,
onField: directiveIntrospection.onField,
});
}

// TODO: deprecation
// TODO: directives

// Iterate through all types, getting the type definition for each, ensuring
// that any type not directly referenced by a field will get created.
Expand All @@ -313,20 +331,27 @@ export function buildClientSchema(
);

// Get the root Query, Mutation, and Subscription types.
var queryType = getType(schemaIntrospection.queryType);
var queryType = getObjectType(schemaIntrospection.queryType);

var mutationType = schemaIntrospection.mutationType ?
getType(schemaIntrospection.mutationType) :
getObjectType(schemaIntrospection.mutationType) :
null;

var subscriptionType = schemaIntrospection.subscriptionType ?
getType(schemaIntrospection.subscriptionType) :
getObjectType(schemaIntrospection.subscriptionType) :
null;

// Get the directives supported by Introspection, assuming empty-set if
// directives were not queried for.
var directives = schemaIntrospection.directives ?
schemaIntrospection.directives.map(buildDirective) :
[];

// Then produce and return a Schema with these types.
var schema = new GraphQLSchema({
query: (queryType: any),
mutation: (mutationType: any),
subscription: (subscriptionType: any)
return new GraphQLSchema({
query: queryType,
mutation: mutationType,
subscription: subscriptionType,
directives,
});

return schema;
}
8 changes: 5 additions & 3 deletions src/validation/__tests__/KnownDirectives.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ describe('Validate: Known directives', () => {
it('with misplaced directives', () => {
expectFailsRule(KnownDirectives, `
query Foo @include(if: true) {
name
...Frag
name @operationOnly
...Frag @operationOnly
}
`, [
misplacedDirective('include', 'operation', 2, 17)
misplacedDirective('include', 'operation', 2, 17),
misplacedDirective('operationOnly', 'field', 3, 14),
misplacedDirective('operationOnly', 'fragment', 4, 17),
]);
});

Expand Down
15 changes: 14 additions & 1 deletion src/validation/__tests__/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ import {
GraphQLBoolean,
GraphQLID
} from '../../type';
import {
GraphQLDirective,
GraphQLIncludeDirective,
GraphQLSkipDirective,
} from '../../type/directives';


var Being = new GraphQLInterfaceType({
Expand Down Expand Up @@ -288,7 +293,15 @@ var QueryRoot = new GraphQLObjectType({
});

var defaultSchema = new GraphQLSchema({
query: QueryRoot
query: QueryRoot,
directives: [
new GraphQLDirective({
name: 'operationOnly',
onOperation: true
}),
GraphQLIncludeDirective,
GraphQLSkipDirective,
]
});

function expectValid(schema, rules, queryString) {
Expand Down