Skip to content

Commit

Permalink
Adds Unique Directives Validation Rule.
Browse files Browse the repository at this point in the history
This implements graphql/graphql-spec#229, in order to adhere with the October2016 edition of the spec.
  • Loading branch information
leebyron committed Nov 1, 2016
1 parent c23f578 commit 4c944d9
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/validation/__tests__/UniqueDirectivesPerLocation-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import { describe, it } from 'mocha';
import { expectPassesRule, expectFailsRule } from './harness';
import {
UniqueDirectivesPerLocation,
duplicateDirectiveMessage,
} from '../rules/UniqueDirectivesPerLocation';


function duplicateDirective(directiveName, l1, c1, l2, c2) {
return {
message: duplicateDirectiveMessage(directiveName),
locations: [ { line: l1, column: c1 }, { line: l2, column: c2 } ],
};
}

describe('Validate: Directives Are Unique Per Location', () => {

it('no directives', () => {
expectPassesRule(UniqueDirectivesPerLocation, `
fragment Test on Type {
field
}
`);
});

it('unique directives in different locations', () => {
expectPassesRule(UniqueDirectivesPerLocation, `
fragment Test on Type @directiveA {
field @directiveB
}
`);
});

it('unique directives in same locations', () => {
expectPassesRule(UniqueDirectivesPerLocation, `
fragment Test on Type @directiveA @directiveB {
field @directiveA @directiveB
}
`);
});

it('same directives in different locations', () => {
expectPassesRule(UniqueDirectivesPerLocation, `
fragment Test on Type @directiveA {
field @directiveA
}
`);
});

it('same directives in similar locations', () => {
expectPassesRule(UniqueDirectivesPerLocation, `
fragment Test on Type {
field @directive
field @directive
}
`);
});

it('duplicate directives in one location', () => {
expectFailsRule(UniqueDirectivesPerLocation, `
fragment Test on Type {
field @directive @directive
}
`, [
duplicateDirective('directive', 3, 15, 3, 26)
]);
});

it('many duplicate directives in one location', () => {
expectFailsRule(UniqueDirectivesPerLocation, `
fragment Test on Type {
field @directive @directive @directive
}
`, [
duplicateDirective('directive', 3, 15, 3, 26),
duplicateDirective('directive', 3, 15, 3, 37)
]);
});

it('different duplicate directives in one location', () => {
expectFailsRule(UniqueDirectivesPerLocation, `
fragment Test on Type {
field @directiveA @directiveB @directiveA @directiveB
}
`, [
duplicateDirective('directiveA', 3, 15, 3, 39),
duplicateDirective('directiveB', 3, 27, 3, 51)
]);
});

it('duplicate directives in many locations', () => {
expectFailsRule(UniqueDirectivesPerLocation, `
fragment Test on Type @directive @directive {
field @directive @directive
}
`, [
duplicateDirective('directive', 2, 29, 2, 40),
duplicateDirective('directive', 3, 15, 3, 26)
]);
});

});
48 changes: 48 additions & 0 deletions src/validation/rules/UniqueDirectivesPerLocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* @flow */
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import type { ValidationContext } from '../index';
import { GraphQLError } from '../../error';


export function duplicateDirectiveMessage(directiveName: string): string {
return `The directive "${directiveName}" can only be used once at ` +
'this location.';
}

/**
* Unique directive names per location
*
* A GraphQL document is only valid if all directives at a given location
* are uniquely named.
*/
export function UniqueDirectivesPerLocation(context: ValidationContext): any {
return {
// Many different AST nodes may contain directives. Rather than listing
// them all, just listen for entering any node, and check to see if it
// defines any directives.
enter(node) {
if (node.directives) {
const knownDirectives = Object.create(null);
node.directives.forEach(directive => {
const directiveName = directive.name.value;
if (knownDirectives[directiveName]) {
context.reportError(new GraphQLError(
duplicateDirectiveMessage(directiveName),
[ knownDirectives[directiveName], directive ]
));
} else {
knownDirectives[directiveName] = directive;
}
});
}
}
};
}
6 changes: 6 additions & 0 deletions src/validation/specifiedRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ import { NoUnusedVariables } from './rules/NoUnusedVariables';
// Spec Section: "Directives Are Defined"
import { KnownDirectives } from './rules/KnownDirectives';

// Spec Section: "Directives Are Unique Per Location"
import {
UniqueDirectivesPerLocation
} from './rules/UniqueDirectivesPerLocation';

// Spec Section: "Argument Names"
import { KnownArgumentNames } from './rules/KnownArgumentNames';

Expand Down Expand Up @@ -104,6 +109,7 @@ export const specifiedRules: Array<(context: ValidationContext) => any> = [
NoUndefinedVariables,
NoUnusedVariables,
KnownDirectives,
UniqueDirectivesPerLocation,
KnownArgumentNames,
UniqueArgumentNames,
ArgumentsOfCorrectType,
Expand Down

0 comments on commit 4c944d9

Please sign in to comment.