diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 0fd0299..f61a440 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -43,6 +43,7 @@ use GraphQL\Validator\Rules\QueryComplexity; use GraphQL\Validator\Rules\QueryDepth; use GraphQL\Validator\Rules\ScalarLeafs; use GraphQL\Validator\Rules\UniqueArgumentNames; +use GraphQL\Validator\Rules\UniqueDirectivesPerLocation; use GraphQL\Validator\Rules\UniqueFragmentNames; use GraphQL\Validator\Rules\UniqueInputFieldNames; use GraphQL\Validator\Rules\UniqueOperationNames; @@ -88,6 +89,7 @@ class DocumentValidator 'NoUndefinedVariables' => new NoUndefinedVariables(), 'NoUnusedVariables' => new NoUnusedVariables(), 'KnownDirectives' => new KnownDirectives(), + 'UniqueDirectivesPerLocation' => new UniqueDirectivesPerLocation(), 'KnownArgumentNames' => new KnownArgumentNames(), 'UniqueArgumentNames' => new UniqueArgumentNames(), 'ArgumentsOfCorrectType' => new ArgumentsOfCorrectType(), diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php new file mode 100644 index 0000000..297e4f2 --- /dev/null +++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php @@ -0,0 +1,38 @@ + function(Node $node) use ($context) { + if (isset($node->directives)) { + $knownDirectives = []; + foreach ($node->directives as $directive) { + /** @var Directive $directive */ + $directiveName = $directive->name->value; + if (isset($knownDirectives[$directiveName])) { + $context->reportError(new Error( + self::duplicateDirectiveMessage($directiveName), + [$knownDirectives[$directiveName], $directive] + )); + } else { + $knownDirectives[$directiveName] = $directive; + } + } + } + } + ]; + } +} diff --git a/tests/Validator/UniqueDirectivesPerLocationTest.php b/tests/Validator/UniqueDirectivesPerLocationTest.php new file mode 100644 index 0000000..84662f1 --- /dev/null +++ b/tests/Validator/UniqueDirectivesPerLocationTest.php @@ -0,0 +1,138 @@ +expectPassesRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type { + field + } + '); + } + + /** + * @it unique directives in different locations + */ + public function testUniqueDirectivesInDifferentLocations() + { + $this->expectPassesRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type @directiveA { + field @directiveB + } + '); + } + + /** + * @it unique directives in same locations + */ + public function testUniqueDirectivesInSameLocations() + { + $this->expectPassesRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type @directiveA @directiveB { + field @directiveA @directiveB + } + '); + } + + /** + * @it same directives in different locations + */ + public function testSameDirectivesInDifferentLocations() + { + $this->expectPassesRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type @directiveA { + field @directiveA + } + '); + } + + /** + * @it same directives in similar locations + */ + public function testSameDirectivesInSimilarLocations() + { + $this->expectPassesRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type { + field @directive + field @directive + } + '); + } + + /** + * @it duplicate directives in one location + */ + public function testDuplicateDirectivesInOneLocation() + { + $this->expectFailsRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type { + field @directive @directive + } + ', [ + $this->duplicateDirective('directive', 3, 15, 3, 26) + ]); + } + + /** + * @it many duplicate directives in one location + */ + public function testManyDuplicateDirectivesInOneLocation() + { + $this->expectFailsRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type { + field @directive @directive @directive + } + ', [ + $this->duplicateDirective('directive', 3, 15, 3, 26), + $this->duplicateDirective('directive', 3, 15, 3, 37) + ]); + } + + /** + * @it different duplicate directives in one location + */ + public function testDifferentDuplicateDirectivesInOneLocation() + { + $this->expectFailsRule(new UniqueDirectivesPerLocation, ' + fragment Test on Type { + field @directiveA @directiveB @directiveA @directiveB + } + ', [ + $this->duplicateDirective('directiveA', 3, 15, 3, 39), + $this->duplicateDirective('directiveB', 3, 27, 3, 51) + ]); + } + + /** + * @it duplicate directives in many locations + */ + public function testDuplicateDirectivesInManyLocations() + { + $this->expectFailsRule(new UniqueDirectivesPerLocation(), ' + fragment Test on Type @directive @directive { + field @directive @directive + } + ', [ + $this->duplicateDirective('directive', 2, 29, 2, 40), + $this->duplicateDirective('directive', 3, 15, 3, 26) + ]); + } + + private function duplicateDirective($directiveName, $l1, $c1, $l2, $c2) + { + return [ + 'message' =>UniqueDirectivesPerLocation::duplicateDirectiveMessage($directiveName), + 'locations' => [ + ['line' => $l1, 'column' => $c1], + ['line' => $l2, 'column' => $c2] + ] + ]; + } +}