Skip to content

Commit 9c2b206

Browse files
committed
Adapt "KnownDirectives" to work with SDL
1 parent 9984589 commit 9c2b206

File tree

6 files changed

+160
-112
lines changed

6 files changed

+160
-112
lines changed

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,24 @@ describe('Schema Builder', () => {
787787
const errors = validateSchema(schema);
788788
expect(errors.length).to.equal(0);
789789
});
790+
791+
it('Rejects invalid SDL', () => {
792+
const doc = parse(`
793+
type Query {
794+
foo: String @unknown
795+
}
796+
`);
797+
expect(() => buildASTSchema(doc)).to.throw('Unknown directive "unknown".');
798+
});
799+
800+
it('Allows to disable SDL validation', () => {
801+
const body = `
802+
type Query {
803+
foo: String @unknown
804+
}
805+
`;
806+
buildSchema(body, { assumeValidSDL: true });
807+
});
790808
});
791809

792810
describe('Failures', () => {

src/utilities/__tests__/extendSchema-test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,22 @@ describe('extendSchema', () => {
10181018
expect(isScalarType(arg1.type)).to.equal(true);
10191019
});
10201020

1021+
it('Rejects invalid SDL', () => {
1022+
const sdl = `
1023+
extend schema @unknown
1024+
`;
1025+
expect(() => extendTestSchema(sdl)).to.throw(
1026+
'Unknown directive "unknown".',
1027+
);
1028+
});
1029+
1030+
it('Allows to disable SDL validation', () => {
1031+
const sdl = `
1032+
extend schema @unknown
1033+
`;
1034+
extendTestSchema(sdl, { assumeValidSDL: true });
1035+
});
1036+
10211037
it('does not allow replacing a default directive', () => {
10221038
const sdl = `
10231039
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD

src/validation/__tests__/KnownDirectives-test.js

Lines changed: 92 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@
66
*/
77

88
import { describe, it } from 'mocha';
9-
import { expectPassesRule, expectFailsRule } from './harness';
9+
import { buildSchema } from '../../utilities';
10+
import {
11+
expectPassesRule,
12+
expectFailsRule,
13+
expectSDLErrorsFromRule,
14+
} from './harness';
15+
1016
import {
1117
KnownDirectives,
1218
unknownDirectiveMessage,
1319
misplacedDirectiveMessage,
1420
} from '../rules/KnownDirectives';
1521

22+
const expectSDLErrors = expectSDLErrorsFromRule.bind(
23+
undefined,
24+
KnownDirectives,
25+
);
26+
1627
function unknownDirective(directiveName, line, column) {
1728
return {
1829
message: unknownDirectiveMessage(directiveName),
@@ -27,6 +38,20 @@ function misplacedDirective(directiveName, placement, line, column) {
2738
};
2839
}
2940

41+
const schemaWithSDLDirectives = buildSchema(`
42+
directive @onSchema on SCHEMA
43+
directive @onScalar on SCALAR
44+
directive @onObject on OBJECT
45+
directive @onFieldDefinition on FIELD_DEFINITION
46+
directive @onArgumentDefinition on ARGUMENT_DEFINITION
47+
directive @onInterface on INTERFACE
48+
directive @onUnion on UNION
49+
directive @onEnum on ENUM
50+
directive @onEnumValue on ENUM_VALUE
51+
directive @onInputObject on INPUT_OBJECT
52+
directive @onInputFieldDefinition on INPUT_FIELD_DEFINITION
53+
`);
54+
3055
describe('Validate: Known directives', () => {
3156
it('with no directives', () => {
3257
expectPassesRule(
@@ -138,10 +163,36 @@ describe('Validate: Known directives', () => {
138163
);
139164
});
140165

141-
describe('within schema language', () => {
166+
describe('within SDL', () => {
167+
it('with directive defined inside SDL', () => {
168+
expectSDLErrors(`
169+
type Query {
170+
foo: String @test
171+
}
172+
173+
directive @test on FIELD_DEFINITION
174+
`).to.deep.equal([]);
175+
});
176+
177+
it('with standard directive', () => {
178+
expectSDLErrors(`
179+
type Query {
180+
foo: String @deprecated
181+
}
182+
`).to.deep.equal([]);
183+
});
184+
185+
it('with overrided standard directive', () => {
186+
expectSDLErrors(`
187+
schema @deprecated {
188+
query: Query
189+
}
190+
directive @deprecated on SCHEMA
191+
`).to.deep.equal([]);
192+
});
193+
142194
it('with well placed directives', () => {
143-
expectPassesRule(
144-
KnownDirectives,
195+
expectSDLErrors(
145196
`
146197
type MyObj implements MyInterface @onObject {
147198
myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition
@@ -180,13 +231,13 @@ describe('Validate: Known directives', () => {
180231
}
181232
182233
extend schema @onSchema
183-
`,
184-
);
234+
`,
235+
schemaWithSDLDirectives,
236+
).to.deep.equal([]);
185237
});
186238

187239
it('with misplaced directives', () => {
188-
expectFailsRule(
189-
KnownDirectives,
240+
expectSDLErrors(
190241
`
191242
type MyObj implements MyInterface @onInterface {
192243
myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition
@@ -213,49 +264,39 @@ describe('Validate: Known directives', () => {
213264
}
214265
215266
extend schema @onObject
216-
`,
217-
[
218-
misplacedDirective('onInterface', 'OBJECT', 2, 43),
219-
misplacedDirective(
220-
'onInputFieldDefinition',
221-
'ARGUMENT_DEFINITION',
222-
3,
223-
30,
224-
),
225-
misplacedDirective(
226-
'onInputFieldDefinition',
227-
'FIELD_DEFINITION',
228-
3,
229-
63,
230-
),
231-
misplacedDirective('onEnum', 'SCALAR', 6, 25),
232-
misplacedDirective('onObject', 'INTERFACE', 8, 31),
233-
misplacedDirective(
234-
'onInputFieldDefinition',
235-
'ARGUMENT_DEFINITION',
236-
9,
237-
30,
238-
),
239-
misplacedDirective(
240-
'onInputFieldDefinition',
241-
'FIELD_DEFINITION',
242-
9,
243-
63,
244-
),
245-
misplacedDirective('onEnumValue', 'UNION', 12, 23),
246-
misplacedDirective('onScalar', 'ENUM', 14, 21),
247-
misplacedDirective('onUnion', 'ENUM_VALUE', 15, 20),
248-
misplacedDirective('onEnum', 'INPUT_OBJECT', 18, 23),
249-
misplacedDirective(
250-
'onArgumentDefinition',
251-
'INPUT_FIELD_DEFINITION',
252-
19,
253-
24,
254-
),
255-
misplacedDirective('onObject', 'SCHEMA', 22, 16),
256-
misplacedDirective('onObject', 'SCHEMA', 26, 23),
257-
],
258-
);
267+
`,
268+
schemaWithSDLDirectives,
269+
).to.deep.equal([
270+
misplacedDirective('onInterface', 'OBJECT', 2, 43),
271+
misplacedDirective(
272+
'onInputFieldDefinition',
273+
'ARGUMENT_DEFINITION',
274+
3,
275+
30,
276+
),
277+
misplacedDirective('onInputFieldDefinition', 'FIELD_DEFINITION', 3, 63),
278+
misplacedDirective('onEnum', 'SCALAR', 6, 25),
279+
misplacedDirective('onObject', 'INTERFACE', 8, 31),
280+
misplacedDirective(
281+
'onInputFieldDefinition',
282+
'ARGUMENT_DEFINITION',
283+
9,
284+
30,
285+
),
286+
misplacedDirective('onInputFieldDefinition', 'FIELD_DEFINITION', 9, 63),
287+
misplacedDirective('onEnumValue', 'UNION', 12, 23),
288+
misplacedDirective('onScalar', 'ENUM', 14, 21),
289+
misplacedDirective('onUnion', 'ENUM_VALUE', 15, 20),
290+
misplacedDirective('onEnum', 'INPUT_OBJECT', 18, 23),
291+
misplacedDirective(
292+
'onArgumentDefinition',
293+
'INPUT_FIELD_DEFINITION',
294+
19,
295+
24,
296+
),
297+
misplacedDirective('onObject', 'SCHEMA', 22, 16),
298+
misplacedDirective('onObject', 'SCHEMA', 26, 23),
299+
]);
259300
});
260301
});
261302
});

src/validation/__tests__/harness.js

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -374,50 +374,6 @@ export const testSchema = new GraphQLSchema({
374374
name: 'onInlineFragment',
375375
locations: ['INLINE_FRAGMENT'],
376376
}),
377-
new GraphQLDirective({
378-
name: 'onSchema',
379-
locations: ['SCHEMA'],
380-
}),
381-
new GraphQLDirective({
382-
name: 'onScalar',
383-
locations: ['SCALAR'],
384-
}),
385-
new GraphQLDirective({
386-
name: 'onObject',
387-
locations: ['OBJECT'],
388-
}),
389-
new GraphQLDirective({
390-
name: 'onFieldDefinition',
391-
locations: ['FIELD_DEFINITION'],
392-
}),
393-
new GraphQLDirective({
394-
name: 'onArgumentDefinition',
395-
locations: ['ARGUMENT_DEFINITION'],
396-
}),
397-
new GraphQLDirective({
398-
name: 'onInterface',
399-
locations: ['INTERFACE'],
400-
}),
401-
new GraphQLDirective({
402-
name: 'onUnion',
403-
locations: ['UNION'],
404-
}),
405-
new GraphQLDirective({
406-
name: 'onEnum',
407-
locations: ['ENUM'],
408-
}),
409-
new GraphQLDirective({
410-
name: 'onEnumValue',
411-
locations: ['ENUM_VALUE'],
412-
}),
413-
new GraphQLDirective({
414-
name: 'onInputObject',
415-
locations: ['INPUT_OBJECT'],
416-
}),
417-
new GraphQLDirective({
418-
name: 'onInputFieldDefinition',
419-
locations: ['INPUT_FIELD_DEFINITION'],
420-
}),
421377
],
422378
});
423379

src/validation/rules/KnownDirectives.js

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../ValidationContext';
10+
import type {
11+
ValidationContext,
12+
SDLValidationContext,
13+
} from '../ValidationContext';
1114
import { GraphQLError } from '../../error';
12-
import find from '../../jsutils/find';
1315
import { Kind } from '../../language/kinds';
1416
import { DirectiveLocation } from '../../language/directiveLocation';
1517
import type { ASTVisitor } from '../../language/visitor';
18+
import { specifiedDirectives } from '../../type/directives';
1619

1720
export function unknownDirectiveMessage(directiveName: string): string {
1821
return `Unknown directive "${directiveName}".`;
@@ -31,29 +34,42 @@ export function misplacedDirectiveMessage(
3134
* A GraphQL document is only valid if all `@directives` are known by the
3235
* schema and legally positioned.
3336
*/
34-
export function KnownDirectives(context: ValidationContext): ASTVisitor {
37+
export function KnownDirectives(
38+
context: ValidationContext | SDLValidationContext,
39+
): ASTVisitor {
40+
const locationsMap = Object.create(null);
41+
const schema = context.getSchema();
42+
const definedDirectives = schema
43+
? schema.getDirectives()
44+
: specifiedDirectives;
45+
for (const directive of definedDirectives) {
46+
locationsMap[directive.name] = directive.locations;
47+
}
48+
3549
return {
50+
Document(node) {
51+
for (const def of node.definitions) {
52+
if (def.kind === Kind.DIRECTIVE_DEFINITION) {
53+
locationsMap[def.name.value] = def.locations.map(name => name.value);
54+
}
55+
}
56+
},
3657
Directive(node, key, parent, path, ancestors) {
37-
const directiveDef = find(
38-
context.getSchema().getDirectives(),
39-
def => def.name === node.name.value,
40-
);
41-
if (!directiveDef) {
58+
const name = node.name.value;
59+
const locations = locationsMap[name];
60+
61+
if (!locations) {
4262
context.reportError(
43-
new GraphQLError(unknownDirectiveMessage(node.name.value), [node]),
63+
new GraphQLError(unknownDirectiveMessage(name), [node]),
4464
);
4565
return;
4666
}
4767
const candidateLocation = getDirectiveLocationForASTPath(ancestors);
48-
if (
49-
candidateLocation &&
50-
directiveDef.locations.indexOf(candidateLocation) === -1
51-
) {
68+
if (candidateLocation && locations.indexOf(candidateLocation) === -1) {
5269
context.reportError(
53-
new GraphQLError(
54-
misplacedDirectiveMessage(node.name.value, candidateLocation),
55-
[node],
56-
),
70+
new GraphQLError(misplacedDirectiveMessage(name, candidateLocation), [
71+
node,
72+
]),
5773
);
5874
}
5975
},

src/validation/specifiedRules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,6 @@ import { LoneSchemaDefinition } from './rules/LoneSchemaDefinition';
130130

131131
export const specifiedSDLRules: Array<(SDLValidationContext) => ASTVisitor> = [
132132
LoneSchemaDefinition,
133+
KnownDirectives,
133134
UniqueDirectivesPerLocation,
134135
];

0 commit comments

Comments
 (0)