Skip to content

Deprecated directive #384

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
May 8, 2016
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
2 changes: 1 addition & 1 deletion src/execution/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function getVariableValues(
export function getArgumentValues(
argDefs: ?Array<GraphQLArgument>,
argASTs: ?Array<Argument>,
variableValues: { [key: string]: mixed }
variableValues?: ?{ [key: string]: mixed }
): { [key: string]: mixed } {
if (!argDefs || !argASTs) {
return {};
Expand Down
7 changes: 6 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ export {
GraphQLBoolean,
GraphQLID,

// Built-in Directives
// Built-in Directives defined by the Spec
specifiedDirectives,
GraphQLIncludeDirective,
GraphQLSkipDirective,
GraphQLDeprecatedDirective,

// Constant Deprecation Reason
DEFAULT_DEPRECATION_REASON,

// Meta-field definitions.
SchemaMetaFieldDef,
Expand Down
43 changes: 40 additions & 3 deletions src/type/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
GraphQLFieldConfigArgumentMap,
GraphQLArgument
} from './definition';
import { GraphQLBoolean } from './scalars';
import { GraphQLString, GraphQLBoolean } from './scalars';
import invariant from '../jsutils/invariant';
import { assertValidName } from '../utilities/assertValidName';

Expand Down Expand Up @@ -99,7 +99,7 @@ type GraphQLDirectiveConfig = {
}

/**
* Used to conditionally include fields or fragments
* Used to conditionally include fields or fragments.
*/
export const GraphQLIncludeDirective = new GraphQLDirective({
name: 'include',
Expand All @@ -120,7 +120,7 @@ export const GraphQLIncludeDirective = new GraphQLDirective({
});

/**
* Used to conditionally skip (exclude) fields or fragments
* Used to conditionally skip (exclude) fields or fragments.
*/
export const GraphQLSkipDirective = new GraphQLDirective({
name: 'skip',
Expand All @@ -139,3 +139,40 @@ export const GraphQLSkipDirective = new GraphQLDirective({
}
},
});

/**
* Constant string used for default reason for a deprecation.
*/
export const DEFAULT_DEPRECATION_REASON = 'No longer supported';

/**
* Used to declare element of a GraphQL schema as deprecated.
*/
export const GraphQLDeprecatedDirective = new GraphQLDirective({
name: 'deprecated',
description:
'Marks an element of a GraphQL schema as no longer supported.',
locations: [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we intend to support more directive locations later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly so. Right now these are the only two kinds of things in the schema which can be deprecated according to the spec.

DirectiveLocation.FIELD_DEFINITION,
DirectiveLocation.ENUM_VALUE,
],
args: {
reason: {
type: GraphQLString,
description:
'Explains why this element was deprecated, usually also including a ' +
'suggestion for how to access supported similar data. Formatted' +
'in [Markdown](https://daringfireball.net/projects/markdown/).',
defaultValue: DEFAULT_DEPRECATION_REASON
}
},
});

/**
* The full list of specified directives.
*/
export const specifiedDirectives: Array<GraphQLDirective> = [
GraphQLIncludeDirective,
GraphQLSkipDirective,
GraphQLDeprecatedDirective,
];
7 changes: 6 additions & 1 deletion src/type/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ export {
// Directives Definition
GraphQLDirective,

// Built-in Directives
// Built-in Directives defined by the Spec
specifiedDirectives,
GraphQLIncludeDirective,
GraphQLSkipDirective,
GraphQLDeprecatedDirective,

// Constant Deprecation Reason
DEFAULT_DEPRECATION_REASON,
} from './directives';

// Common built-in scalar instances.
Expand Down
34 changes: 13 additions & 21 deletions src/type/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import {
GraphQLNonNull
} from './definition';
import type { GraphQLType, GraphQLAbstractType } from './definition';
import {
GraphQLDirective,
GraphQLIncludeDirective,
GraphQLSkipDirective
} from './directives';
import { GraphQLDirective, specifiedDirectives } from './directives';
import { __Schema } from './introspection';
import find from '../jsutils/find';
import invariant from '../jsutils/invariant';
Expand All @@ -38,21 +34,20 @@ import { isEqualType, isTypeSubTypeOf } from '../utilities/typeComparators';
* Example:
*
* const MyAppSchema = new GraphQLSchema({
* query: MyAppQueryRootType
* mutation: MyAppMutationRootType
* });
* query: MyAppQueryRootType,
* mutation: MyAppMutationRootType,
* })
*
* Note: If an array of `directives` are provided to GraphQLSchema, that will be
* the exact list of directives represented and allowed. If `directives` is not
* provided then a default set of the built-in `[ @include, @skip ]` directives
* will be used. If you wish to provide *additional* directives to these
* built-ins, you must explicitly declare them. Example:
* provided then a default set of the specified directives (e.g. @include and
* @skip) will be used. If you wish to provide *additional* directives to these
* specified directives, you must explicitly declare them. Example:
*
* directives: [
* myCustomDirective,
* GraphQLIncludeDirective,
* GraphQLSkipDirective
* ]
* const MyAppSchema = new GraphQLSchema({
* ...
* directives: specifiedDirectives.concat([ myCustomDirective ]),
* })
*
*/
export class GraphQLSchema {
Expand Down Expand Up @@ -104,11 +99,8 @@ export class GraphQLSchema {
`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
];
// Provide specified directives (e.g. @include and @skip) by default.
this._directives = config.directives || specifiedDirectives;

// Build type map now to detect any errors within this schema.
let initialTypes: Array<?GraphQLType> = [
Expand Down
39 changes: 35 additions & 4 deletions src/utilities/__tests__/buildASTSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { buildASTSchema } from '../buildASTSchema';
import {
GraphQLSkipDirective,
GraphQLIncludeDirective,
GraphQLDeprecatedDirective,
} from '../../type/directives';

/**
Expand Down Expand Up @@ -77,30 +78,37 @@ type Hello {
}
`;
const schema = buildASTSchema(parse(body));
expect(schema.getDirectives().length).to.equal(2);
expect(schema.getDirectives().length).to.equal(3);
expect(schema.getDirective('skip')).to.equal(GraphQLSkipDirective);
expect(schema.getDirective('include')).to.equal(GraphQLIncludeDirective);
expect(
schema.getDirective('deprecated')
).to.equal(GraphQLDeprecatedDirective);
});

it('Overriding directives excludes built-ins', () => {
it('Overriding directives excludes specified', () => {
const body = `
schema {
query: Hello
}

directive @skip on FIELD
directive @include on FIELD
directive @deprecated on FIELD_DEFINITION

type Hello {
str: String
}
`;
const schema = buildASTSchema(parse(body));
expect(schema.getDirectives().length).to.equal(2);
expect(schema.getDirectives().length).to.equal(3);
expect(schema.getDirective('skip')).to.not.equal(GraphQLSkipDirective);
expect(
schema.getDirective('include')
).to.not.equal(GraphQLIncludeDirective);
expect(
schema.getDirective('deprecated')
).to.not.equal(GraphQLDeprecatedDirective);
});

it('Adding directives maintains @skip & @include', () => {
Expand All @@ -116,9 +124,10 @@ type Hello {
}
`;
const schema = buildASTSchema(parse(body));
expect(schema.getDirectives().length).to.equal(3);
expect(schema.getDirectives().length).to.equal(4);
expect(schema.getDirective('skip')).to.not.equal(undefined);
expect(schema.getDirective('include')).to.not.equal(undefined);
expect(schema.getDirective('deprecated')).to.not.equal(undefined);
});

it('Type modifiers', () => {
Expand Down Expand Up @@ -453,6 +462,28 @@ type Query {
}

union Union = Concrete
`;
const output = cycleOutput(body);
expect(output).to.equal(body);
});

it('Supports @deprecated', () => {
const body = `
schema {
query: Query
}

enum MyEnum {
VALUE
OLD_VALUE @deprecated
OTHER_VALUE @deprecated(reason: "Terrible reasons")
}

type Query {
field1: String @deprecated
field2: Int @deprecated(reason: "Because I said so")
enum: MyEnum
}
`;
const output = cycleOutput(body);
expect(output).to.equal(body);
Expand Down
11 changes: 8 additions & 3 deletions src/utilities/__tests__/schemaPrinter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

// 80+ char lines are useful in describe/it, so ignore in this file.
/* eslint-disable max-len */

import { describe, it } from 'mocha';
import { expect } from 'chai';
import { printSchema, printIntrospectionSchema } from '../schemaPrinter';
Expand Down Expand Up @@ -602,14 +605,16 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

type __Directive {
name: String!
description: String
locations: [__DirectiveLocation!]!
args: [__InputValue!]!
onOperation: Boolean!
onFragment: Boolean!
onField: Boolean!
onOperation: Boolean! @deprecated(reason: "Use \`locations\`.")
onFragment: Boolean! @deprecated(reason: "Use \`locations\`.")
onField: Boolean! @deprecated(reason: "Use \`locations\`.")
}

enum __DirectiveLocation {
Expand Down
35 changes: 33 additions & 2 deletions src/utilities/buildASTSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

import find from '../jsutils/find';
import invariant from '../jsutils/invariant';
import keyMap from '../jsutils/keyMap';
import keyValMap from '../jsutils/keyValMap';
import { valueFromAST } from './valueFromAST';

import { getArgumentValues } from '../execution/values';

import {
LIST_TYPE,
NON_NULL_TYPE,
Expand All @@ -29,6 +32,7 @@ import {

import type {
Document,
Directive,
Type,
NamedType,
SchemaDefinition,
Expand Down Expand Up @@ -64,6 +68,7 @@ import {
GraphQLDirective,
GraphQLSkipDirective,
GraphQLIncludeDirective,
GraphQLDeprecatedDirective,
} from '../type/directives';

import {
Expand Down Expand Up @@ -224,7 +229,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema {

const directives = directiveDefs.map(getDirective);

// If skip and include were not explicitly declared, add them.
// If specified directives were not explicitly declared, add them.
if (!directives.some(directive => directive.name === 'skip')) {
directives.push(GraphQLSkipDirective);
}
Expand All @@ -233,6 +238,10 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
directives.push(GraphQLIncludeDirective);
}

if (!directives.some(directive => directive.name === 'deprecated')) {
directives.push(GraphQLDeprecatedDirective);
}

return new GraphQLSchema({
query: getObjectType(astMap[queryTypeName]),
mutation: mutationTypeName ? getObjectType(astMap[mutationTypeName]) : null,
Expand Down Expand Up @@ -321,6 +330,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
field => ({
type: produceTypeDef(field.type),
args: makeInputValues(field.arguments),
deprecationReason: getDeprecationReason(field.directives)
})
);
}
Expand Down Expand Up @@ -353,7 +363,13 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
function makeEnumDef(def: EnumTypeDefinition) {
const enumType = new GraphQLEnumType({
name: def.name.value,
values: keyValMap(def.values, v => v.name.value, () => ({})),
values: keyValMap(
def.values,
enumValue => enumValue.name.value,
enumValue => ({
deprecationReason: getDeprecationReason(enumValue.directives)
})
),
});

return enumType;
Expand Down Expand Up @@ -387,3 +403,18 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
});
}
}

function getDeprecationReason(directives: ?Array<Directive>): ?string {
const deprecatedAST = directives && find(
directives,
directive => directive.name.value === GraphQLDeprecatedDirective.name
);
if (!deprecatedAST) {
return;
}
const { reason } = getArgumentValues(
GraphQLDeprecatedDirective.args,
deprecatedAST.arguments
);
return (reason: any);
}
Loading