Skip to content

Commit 5375c9b

Browse files
committed
Deprecated directive (#384)
This adds a new directive as part of the experimental schema language: ``` directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE ``` It also adds support for this directive in the schemaPrinter and buildASTSchema. Additionally exports a new helper `specifiedDirectives` which is encoured to be used when addressing the collection of all directives defined by the spec. The `@deprecated` directive is optimistically added to this collection. While it's currently experimental, it will become part of the schema definition language RFC.
1 parent 0aa78f6 commit 5375c9b

File tree

9 files changed

+164
-39
lines changed

9 files changed

+164
-39
lines changed

src/execution/values.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function getVariableValues(
5454
export function getArgumentValues(
5555
argDefs: ?Array<GraphQLArgument>,
5656
argASTs: ?Array<Argument>,
57-
variableValues: { [key: string]: mixed }
57+
variableValues?: ?{ [key: string]: mixed }
5858
): { [key: string]: mixed } {
5959
if (!argDefs || !argASTs) {
6060
return {};

src/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ export {
6666
GraphQLBoolean,
6767
GraphQLID,
6868

69-
// Built-in Directives
69+
// Built-in Directives defined by the Spec
70+
specifiedDirectives,
7071
GraphQLIncludeDirective,
7172
GraphQLSkipDirective,
73+
GraphQLDeprecatedDirective,
74+
75+
// Constant Deprecation Reason
76+
DEFAULT_DEPRECATION_REASON,
7277

7378
// Meta-field definitions.
7479
SchemaMetaFieldDef,

src/type/directives.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
GraphQLFieldConfigArgumentMap,
1414
GraphQLArgument
1515
} from './definition';
16-
import { GraphQLBoolean } from './scalars';
16+
import { GraphQLString, GraphQLBoolean } from './scalars';
1717
import invariant from '../jsutils/invariant';
1818
import { assertValidName } from '../utilities/assertValidName';
1919

@@ -99,7 +99,7 @@ type GraphQLDirectiveConfig = {
9999
}
100100

101101
/**
102-
* Used to conditionally include fields or fragments
102+
* Used to conditionally include fields or fragments.
103103
*/
104104
export const GraphQLIncludeDirective = new GraphQLDirective({
105105
name: 'include',
@@ -120,7 +120,7 @@ export const GraphQLIncludeDirective = new GraphQLDirective({
120120
});
121121

122122
/**
123-
* Used to conditionally skip (exclude) fields or fragments
123+
* Used to conditionally skip (exclude) fields or fragments.
124124
*/
125125
export const GraphQLSkipDirective = new GraphQLDirective({
126126
name: 'skip',
@@ -139,3 +139,40 @@ export const GraphQLSkipDirective = new GraphQLDirective({
139139
}
140140
},
141141
});
142+
143+
/**
144+
* Constant string used for default reason for a deprecation.
145+
*/
146+
export const DEFAULT_DEPRECATION_REASON = 'No longer supported';
147+
148+
/**
149+
* Used to declare element of a GraphQL schema as deprecated.
150+
*/
151+
export const GraphQLDeprecatedDirective = new GraphQLDirective({
152+
name: 'deprecated',
153+
description:
154+
'Marks an element of a GraphQL schema as no longer supported.',
155+
locations: [
156+
DirectiveLocation.FIELD_DEFINITION,
157+
DirectiveLocation.ENUM_VALUE,
158+
],
159+
args: {
160+
reason: {
161+
type: GraphQLString,
162+
description:
163+
'Explains why this element was deprecated, usually also including a ' +
164+
'suggestion for how to access supported similar data. Formatted' +
165+
'in [Markdown](https://daringfireball.net/projects/markdown/).',
166+
defaultValue: DEFAULT_DEPRECATION_REASON
167+
}
168+
},
169+
});
170+
171+
/**
172+
* The full list of specified directives.
173+
*/
174+
export const specifiedDirectives: Array<GraphQLDirective> = [
175+
GraphQLIncludeDirective,
176+
GraphQLSkipDirective,
177+
GraphQLDeprecatedDirective,
178+
];

src/type/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,14 @@ export {
4242
// Directives Definition
4343
GraphQLDirective,
4444

45-
// Built-in Directives
45+
// Built-in Directives defined by the Spec
46+
specifiedDirectives,
4647
GraphQLIncludeDirective,
4748
GraphQLSkipDirective,
49+
GraphQLDeprecatedDirective,
50+
51+
// Constant Deprecation Reason
52+
DEFAULT_DEPRECATION_REASON,
4853
} from './directives';
4954

5055
// Common built-in scalar instances.

src/type/schema.js

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ import {
1717
GraphQLNonNull
1818
} from './definition';
1919
import type { GraphQLType, GraphQLAbstractType } from './definition';
20-
import {
21-
GraphQLDirective,
22-
GraphQLIncludeDirective,
23-
GraphQLSkipDirective
24-
} from './directives';
20+
import { GraphQLDirective, specifiedDirectives } from './directives';
2521
import { __Schema } from './introspection';
2622
import find from '../jsutils/find';
2723
import invariant from '../jsutils/invariant';
@@ -38,21 +34,20 @@ import { isEqualType, isTypeSubTypeOf } from '../utilities/typeComparators';
3834
* Example:
3935
*
4036
* const MyAppSchema = new GraphQLSchema({
41-
* query: MyAppQueryRootType
42-
* mutation: MyAppMutationRootType
43-
* });
37+
* query: MyAppQueryRootType,
38+
* mutation: MyAppMutationRootType,
39+
* })
4440
*
4541
* Note: If an array of `directives` are provided to GraphQLSchema, that will be
4642
* the exact list of directives represented and allowed. If `directives` is not
47-
* provided then a default set of the built-in `[ @include, @skip ]` directives
48-
* will be used. If you wish to provide *additional* directives to these
49-
* built-ins, you must explicitly declare them. Example:
43+
* provided then a default set of the specified directives (e.g. @include and
44+
* @skip) will be used. If you wish to provide *additional* directives to these
45+
* specified directives, you must explicitly declare them. Example:
5046
*
51-
* directives: [
52-
* myCustomDirective,
53-
* GraphQLIncludeDirective,
54-
* GraphQLSkipDirective
55-
* ]
47+
* const MyAppSchema = new GraphQLSchema({
48+
* ...
49+
* directives: specifiedDirectives.concat([ myCustomDirective ]),
50+
* })
5651
*
5752
*/
5853
export class GraphQLSchema {
@@ -104,11 +99,8 @@ export class GraphQLSchema {
10499
`Schema directives must be Array<GraphQLDirective> if provided but got: ${
105100
config.directives}.`
106101
);
107-
// Provide `@include() and `@skip()` directives by default.
108-
this._directives = config.directives || [
109-
GraphQLIncludeDirective,
110-
GraphQLSkipDirective
111-
];
102+
// Provide specified directives (e.g. @include and @skip) by default.
103+
this._directives = config.directives || specifiedDirectives;
112104

113105
// Build type map now to detect any errors within this schema.
114106
let initialTypes: Array<?GraphQLType> = [

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { buildASTSchema } from '../buildASTSchema';
1515
import {
1616
GraphQLSkipDirective,
1717
GraphQLIncludeDirective,
18+
GraphQLDeprecatedDirective,
1819
} from '../../type/directives';
1920

2021
/**
@@ -77,30 +78,37 @@ type Hello {
7778
}
7879
`;
7980
const schema = buildASTSchema(parse(body));
80-
expect(schema.getDirectives().length).to.equal(2);
81+
expect(schema.getDirectives().length).to.equal(3);
8182
expect(schema.getDirective('skip')).to.equal(GraphQLSkipDirective);
8283
expect(schema.getDirective('include')).to.equal(GraphQLIncludeDirective);
84+
expect(
85+
schema.getDirective('deprecated')
86+
).to.equal(GraphQLDeprecatedDirective);
8387
});
8488

85-
it('Overriding directives excludes built-ins', () => {
89+
it('Overriding directives excludes specified', () => {
8690
const body = `
8791
schema {
8892
query: Hello
8993
}
9094
9195
directive @skip on FIELD
9296
directive @include on FIELD
97+
directive @deprecated on FIELD_DEFINITION
9398
9499
type Hello {
95100
str: String
96101
}
97102
`;
98103
const schema = buildASTSchema(parse(body));
99-
expect(schema.getDirectives().length).to.equal(2);
104+
expect(schema.getDirectives().length).to.equal(3);
100105
expect(schema.getDirective('skip')).to.not.equal(GraphQLSkipDirective);
101106
expect(
102107
schema.getDirective('include')
103108
).to.not.equal(GraphQLIncludeDirective);
109+
expect(
110+
schema.getDirective('deprecated')
111+
).to.not.equal(GraphQLDeprecatedDirective);
104112
});
105113

106114
it('Adding directives maintains @skip & @include', () => {
@@ -116,9 +124,10 @@ type Hello {
116124
}
117125
`;
118126
const schema = buildASTSchema(parse(body));
119-
expect(schema.getDirectives().length).to.equal(3);
127+
expect(schema.getDirectives().length).to.equal(4);
120128
expect(schema.getDirective('skip')).to.not.equal(undefined);
121129
expect(schema.getDirective('include')).to.not.equal(undefined);
130+
expect(schema.getDirective('deprecated')).to.not.equal(undefined);
122131
});
123132

124133
it('Type modifiers', () => {
@@ -453,6 +462,28 @@ type Query {
453462
}
454463
455464
union Union = Concrete
465+
`;
466+
const output = cycleOutput(body);
467+
expect(output).to.equal(body);
468+
});
469+
470+
it('Supports @deprecated', () => {
471+
const body = `
472+
schema {
473+
query: Query
474+
}
475+
476+
enum MyEnum {
477+
VALUE
478+
OLD_VALUE @deprecated
479+
OTHER_VALUE @deprecated(reason: "Terrible reasons")
480+
}
481+
482+
type Query {
483+
field1: String @deprecated
484+
field2: Int @deprecated(reason: "Because I said so")
485+
enum: MyEnum
486+
}
456487
`;
457488
const output = cycleOutput(body);
458489
expect(output).to.equal(body);

src/utilities/__tests__/schemaPrinter-test.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10+
// 80+ char lines are useful in describe/it, so ignore in this file.
11+
/* eslint-disable max-len */
12+
1013
import { describe, it } from 'mocha';
1114
import { expect } from 'chai';
1215
import { printSchema, printIntrospectionSchema } from '../schemaPrinter';
@@ -602,14 +605,16 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
602605
603606
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
604607
608+
directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE
609+
605610
type __Directive {
606611
name: String!
607612
description: String
608613
locations: [__DirectiveLocation!]!
609614
args: [__InputValue!]!
610-
onOperation: Boolean!
611-
onFragment: Boolean!
612-
onField: Boolean!
615+
onOperation: Boolean! @deprecated(reason: "Use \`locations\`.")
616+
onFragment: Boolean! @deprecated(reason: "Use \`locations\`.")
617+
onField: Boolean! @deprecated(reason: "Use \`locations\`.")
613618
}
614619
615620
enum __DirectiveLocation {

src/utilities/buildASTSchema.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
* of patent rights can be found in the PATENTS file in the same directory.
99
*/
1010

11+
import find from '../jsutils/find';
1112
import invariant from '../jsutils/invariant';
1213
import keyMap from '../jsutils/keyMap';
1314
import keyValMap from '../jsutils/keyValMap';
1415
import { valueFromAST } from './valueFromAST';
1516

17+
import { getArgumentValues } from '../execution/values';
18+
1619
import {
1720
LIST_TYPE,
1821
NON_NULL_TYPE,
@@ -29,6 +32,7 @@ import {
2932

3033
import type {
3134
Document,
35+
Directive,
3236
Type,
3337
NamedType,
3438
SchemaDefinition,
@@ -64,6 +68,7 @@ import {
6468
GraphQLDirective,
6569
GraphQLSkipDirective,
6670
GraphQLIncludeDirective,
71+
GraphQLDeprecatedDirective,
6772
} from '../type/directives';
6873

6974
import {
@@ -224,7 +229,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
224229

225230
const directives = directiveDefs.map(getDirective);
226231

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

241+
if (!directives.some(directive => directive.name === 'deprecated')) {
242+
directives.push(GraphQLDeprecatedDirective);
243+
}
244+
236245
return new GraphQLSchema({
237246
query: getObjectType(astMap[queryTypeName]),
238247
mutation: mutationTypeName ? getObjectType(astMap[mutationTypeName]) : null,
@@ -321,6 +330,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
321330
field => ({
322331
type: produceTypeDef(field.type),
323332
args: makeInputValues(field.arguments),
333+
deprecationReason: getDeprecationReason(field.directives)
324334
})
325335
);
326336
}
@@ -353,7 +363,13 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
353363
function makeEnumDef(def: EnumTypeDefinition) {
354364
const enumType = new GraphQLEnumType({
355365
name: def.name.value,
356-
values: keyValMap(def.values, v => v.name.value, () => ({})),
366+
values: keyValMap(
367+
def.values,
368+
enumValue => enumValue.name.value,
369+
enumValue => ({
370+
deprecationReason: getDeprecationReason(enumValue.directives)
371+
})
372+
),
357373
});
358374

359375
return enumType;
@@ -387,3 +403,18 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
387403
});
388404
}
389405
}
406+
407+
function getDeprecationReason(directives: ?Array<Directive>): ?string {
408+
const deprecatedAST = directives && find(
409+
directives,
410+
directive => directive.name.value === GraphQLDeprecatedDirective.name
411+
);
412+
if (!deprecatedAST) {
413+
return;
414+
}
415+
const { reason } = getArgumentValues(
416+
GraphQLDeprecatedDirective.args,
417+
deprecatedAST.arguments
418+
);
419+
return (reason: any);
420+
}

0 commit comments

Comments
 (0)