vendor/webonyx/graphql-php/src/Type/SchemaValidationContext.php line 293

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace GraphQL\Type;
  4. use GraphQL\Error\Error;
  5. use GraphQL\Language\AST\DirectiveDefinitionNode;
  6. use GraphQL\Language\AST\DirectiveNode;
  7. use GraphQL\Language\AST\EnumValueDefinitionNode;
  8. use GraphQL\Language\AST\FieldDefinitionNode;
  9. use GraphQL\Language\AST\InputValueDefinitionNode;
  10. use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
  11. use GraphQL\Language\AST\InterfaceTypeExtensionNode;
  12. use GraphQL\Language\AST\ListTypeNode;
  13. use GraphQL\Language\AST\NamedTypeNode;
  14. use GraphQL\Language\AST\Node;
  15. use GraphQL\Language\AST\NodeList;
  16. use GraphQL\Language\AST\NonNullTypeNode;
  17. use GraphQL\Language\AST\ObjectTypeDefinitionNode;
  18. use GraphQL\Language\AST\ObjectTypeExtensionNode;
  19. use GraphQL\Language\AST\SchemaDefinitionNode;
  20. use GraphQL\Language\AST\TypeDefinitionNode;
  21. use GraphQL\Language\AST\TypeNode;
  22. use GraphQL\Language\DirectiveLocation;
  23. use GraphQL\Type\Definition\Directive;
  24. use GraphQL\Type\Definition\EnumType;
  25. use GraphQL\Type\Definition\EnumValueDefinition;
  26. use GraphQL\Type\Definition\FieldDefinition;
  27. use GraphQL\Type\Definition\ImplementingType;
  28. use GraphQL\Type\Definition\InputObjectField;
  29. use GraphQL\Type\Definition\InputObjectType;
  30. use GraphQL\Type\Definition\InterfaceType;
  31. use GraphQL\Type\Definition\NamedType;
  32. use GraphQL\Type\Definition\NonNull;
  33. use GraphQL\Type\Definition\ObjectType;
  34. use GraphQL\Type\Definition\ScalarType;
  35. use GraphQL\Type\Definition\Type;
  36. use GraphQL\Type\Definition\UnionType;
  37. use GraphQL\Type\Validation\InputObjectCircularRefs;
  38. use GraphQL\Utils\TypeComparators;
  39. use GraphQL\Utils\Utils;
  40. use function array_filter;
  41. use function array_key_exists;
  42. use function array_merge;
  43. use function count;
  44. use function in_array;
  45. use function is_array;
  46. use function is_object;
  47. use function sprintf;
  48. class SchemaValidationContext
  49. {
  50.     /** @var Error[] */
  51.     private $errors = [];
  52.     /** @var Schema */
  53.     private $schema;
  54.     /** @var InputObjectCircularRefs */
  55.     private $inputObjectCircularRefs;
  56.     public function __construct(Schema $schema)
  57.     {
  58.         $this->schema                  $schema;
  59.         $this->inputObjectCircularRefs = new InputObjectCircularRefs($this);
  60.     }
  61.     /**
  62.      * @return Error[]
  63.      */
  64.     public function getErrors()
  65.     {
  66.         return $this->errors;
  67.     }
  68.     public function validateRootTypes() : void
  69.     {
  70.         $queryType $this->schema->getQueryType();
  71.         if (! $queryType) {
  72.             $this->reportError(
  73.                 'Query root type must be provided.',
  74.                 $this->schema->getAstNode()
  75.             );
  76.         } elseif (! $queryType instanceof ObjectType) {
  77.             $this->reportError(
  78.                 'Query root type must be Object type, it cannot be ' Utils::printSafe($queryType) . '.',
  79.                 $this->getOperationTypeNode($queryType'query')
  80.             );
  81.         }
  82.         $mutationType $this->schema->getMutationType();
  83.         if ($mutationType && ! $mutationType instanceof ObjectType) {
  84.             $this->reportError(
  85.                 'Mutation root type must be Object type if provided, it cannot be ' Utils::printSafe($mutationType) . '.',
  86.                 $this->getOperationTypeNode($mutationType'mutation')
  87.             );
  88.         }
  89.         $subscriptionType $this->schema->getSubscriptionType();
  90.         if ($subscriptionType === null || $subscriptionType instanceof ObjectType) {
  91.             return;
  92.         }
  93.         $this->reportError(
  94.             'Subscription root type must be Object type if provided, it cannot be ' Utils::printSafe($subscriptionType) . '.',
  95.             $this->getOperationTypeNode($subscriptionType'subscription')
  96.         );
  97.     }
  98.     /**
  99.      * @param string                                       $message
  100.      * @param Node[]|Node|TypeNode|TypeDefinitionNode|null $nodes
  101.      */
  102.     public function reportError($message$nodes null)
  103.     {
  104.         $nodes array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]);
  105.         $this->addError(new Error($message$nodes));
  106.     }
  107.     /**
  108.      * @param Error $error
  109.      */
  110.     private function addError($error)
  111.     {
  112.         $this->errors[] = $error;
  113.     }
  114.     /**
  115.      * @param Type   $type
  116.      * @param string $operation
  117.      *
  118.      * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|TypeDefinitionNode
  119.      */
  120.     private function getOperationTypeNode($type$operation)
  121.     {
  122.         $astNode $this->schema->getAstNode();
  123.         $operationTypeNode null;
  124.         if ($astNode instanceof SchemaDefinitionNode) {
  125.             $operationTypeNode null;
  126.             foreach ($astNode->operationTypes as $operationType) {
  127.                 if ($operationType->operation === $operation) {
  128.                     $operationTypeNode $operationType;
  129.                     break;
  130.                 }
  131.             }
  132.         }
  133.         return $operationTypeNode $operationTypeNode->type : ($type $type->astNode null);
  134.     }
  135.     public function validateDirectives()
  136.     {
  137.         $this->validateDirectiveDefinitions();
  138.         // Validate directives that are used on the schema
  139.         $this->validateDirectivesAtLocation(
  140.             $this->getDirectives($this->schema),
  141.             DirectiveLocation::SCHEMA
  142.         );
  143.     }
  144.     public function validateDirectiveDefinitions()
  145.     {
  146.         $directiveDefinitions = [];
  147.         $directives $this->schema->getDirectives();
  148.         foreach ($directives as $directive) {
  149.             // Ensure all directives are in fact GraphQL directives.
  150.             if (! $directive instanceof Directive) {
  151.                 $nodes is_object($directive)
  152.                     ? $directive->astNode
  153.                     null;
  154.                 $this->reportError(
  155.                     'Expected directive but got: ' Utils::printSafe($directive) . '.',
  156.                     $nodes
  157.                 );
  158.                 continue;
  159.             }
  160.             $existingDefinitions                    $directiveDefinitions[$directive->name] ?? [];
  161.             $existingDefinitions[]                  = $directive;
  162.             $directiveDefinitions[$directive->name] = $existingDefinitions;
  163.             // Ensure they are named correctly.
  164.             $this->validateName($directive);
  165.             // TODO: Ensure proper locations.
  166.             $argNames = [];
  167.             foreach ($directive->args as $arg) {
  168.                 $argName $arg->name;
  169.                 // Ensure they are named correctly.
  170.                 $this->validateName($directive);
  171.                 if (isset($argNames[$argName])) {
  172.                     $this->reportError(
  173.                         sprintf('Argument @%s(%s:) can only be defined once.'$directive->name$argName),
  174.                         $this->getAllDirectiveArgNodes($directive$argName)
  175.                     );
  176.                     continue;
  177.                 }
  178.                 $argNames[$argName] = true;
  179.                 // Ensure the type is an input type.
  180.                 if (Type::isInputType($arg->getType())) {
  181.                     continue;
  182.                 }
  183.                 $this->reportError(
  184.                     sprintf(
  185.                         'The type of @%s(%s:) must be Input Type but got: %s.',
  186.                         $directive->name,
  187.                         $argName,
  188.                         Utils::printSafe($arg->getType())
  189.                     ),
  190.                     $this->getDirectiveArgTypeNode($directive$argName)
  191.                 );
  192.             }
  193.         }
  194.         foreach ($directiveDefinitions as $directiveName => $directiveList) {
  195.             if (count($directiveList) <= 1) {
  196.                 continue;
  197.             }
  198.             $nodes Utils::map(
  199.                 $directiveList,
  200.                 static function (Directive $directive) : ?DirectiveDefinitionNode {
  201.                     return $directive->astNode;
  202.                 }
  203.             );
  204.             $this->reportError(
  205.                 sprintf('Directive @%s defined multiple times.'$directiveName),
  206.                 array_filter($nodes)
  207.             );
  208.         }
  209.     }
  210.     /**
  211.      * @param Type|Directive|FieldDefinition|EnumValueDefinition|InputObjectField $node
  212.      */
  213.     private function validateName($node)
  214.     {
  215.         // Ensure names are valid, however introspection types opt out.
  216.         $error Utils::isValidNameError($node->name$node->astNode);
  217.         if (! $error || Introspection::isIntrospectionType($node)) {
  218.             return;
  219.         }
  220.         $this->addError($error);
  221.     }
  222.     /**
  223.      * @param string $argName
  224.      *
  225.      * @return InputValueDefinitionNode[]
  226.      */
  227.     private function getAllDirectiveArgNodes(Directive $directive$argName)
  228.     {
  229.         $subNodes $this->getAllSubNodes(
  230.             $directive,
  231.             static function ($directiveNode) {
  232.                 return $directiveNode->arguments;
  233.             }
  234.         );
  235.         return Utils::filter(
  236.             $subNodes,
  237.             static function ($argNode) use ($argName) : bool {
  238.                 return $argNode->name->value === $argName;
  239.             }
  240.         );
  241.     }
  242.     /**
  243.      * @param string $argName
  244.      *
  245.      * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|null
  246.      */
  247.     private function getDirectiveArgTypeNode(Directive $directive$argName) : ?TypeNode
  248.     {
  249.         $argNode $this->getAllDirectiveArgNodes($directive$argName)[0];
  250.         return $argNode $argNode->type null;
  251.     }
  252.     public function validateTypes() : void
  253.     {
  254.         $typeMap $this->schema->getTypeMap();
  255.         foreach ($typeMap as $typeName => $type) {
  256.             // Ensure all provided types are in fact GraphQL type.
  257.             if (! $type instanceof NamedType) {
  258.                 $this->reportError(
  259.                     'Expected GraphQL named type but got: ' Utils::printSafe($type) . '.',
  260.                     $type instanceof Type $type->astNode null
  261.                 );
  262.                 continue;
  263.             }
  264.             $this->validateName($type);
  265.             if ($type instanceof ObjectType) {
  266.                 // Ensure fields are valid
  267.                 $this->validateFields($type);
  268.                 // Ensure objects implement the interfaces they claim to.
  269.                 $this->validateInterfaces($type);
  270.                 // Ensure directives are valid
  271.                 $this->validateDirectivesAtLocation(
  272.                     $this->getDirectives($type),
  273.                     DirectiveLocation::OBJECT
  274.                 );
  275.             } elseif ($type instanceof InterfaceType) {
  276.                 // Ensure fields are valid.
  277.                 $this->validateFields($type);
  278.                 // Ensure interfaces implement the interfaces they claim to.
  279.                 $this->validateInterfaces($type);
  280.                 // Ensure directives are valid
  281.                 $this->validateDirectivesAtLocation(
  282.                     $this->getDirectives($type),
  283.                     DirectiveLocation::IFACE
  284.                 );
  285.             } elseif ($type instanceof UnionType) {
  286.                 // Ensure Unions include valid member types.
  287.                 $this->validateUnionMembers($type);
  288.                 // Ensure directives are valid
  289.                 $this->validateDirectivesAtLocation(
  290.                     $this->getDirectives($type),
  291.                     DirectiveLocation::UNION
  292.                 );
  293.             } elseif ($type instanceof EnumType) {
  294.                 // Ensure Enums have valid values.
  295.                 $this->validateEnumValues($type);
  296.                 // Ensure directives are valid
  297.                 $this->validateDirectivesAtLocation(
  298.                     $this->getDirectives($type),
  299.                     DirectiveLocation::ENUM
  300.                 );
  301.             } elseif ($type instanceof InputObjectType) {
  302.                 // Ensure Input Object fields are valid.
  303.                 $this->validateInputFields($type);
  304.                 // Ensure directives are valid
  305.                 $this->validateDirectivesAtLocation(
  306.                     $this->getDirectives($type),
  307.                     DirectiveLocation::INPUT_OBJECT
  308.                 );
  309.                 // Ensure Input Objects do not contain non-nullable circular references
  310.                 $this->inputObjectCircularRefs->validate($type);
  311.             } elseif ($type instanceof ScalarType) {
  312.                 // Ensure directives are valid
  313.                 $this->validateDirectivesAtLocation(
  314.                     $this->getDirectives($type),
  315.                     DirectiveLocation::SCALAR
  316.                 );
  317.             }
  318.         }
  319.     }
  320.     /**
  321.      * @param NodeList<DirectiveNode> $directives
  322.      */
  323.     private function validateDirectivesAtLocation($directivesstring $location)
  324.     {
  325.         /** @var array<string, array<int, DirectiveNode>> $potentiallyDuplicateDirectives */
  326.         $potentiallyDuplicateDirectives = [];
  327.         $schema                         $this->schema;
  328.         foreach ($directives as $directive) {
  329.             $directiveName $directive->name->value;
  330.             // Ensure directive used is also defined
  331.             $schemaDirective $schema->getDirective($directiveName);
  332.             if ($schemaDirective === null) {
  333.                 $this->reportError(
  334.                     sprintf('No directive @%s defined.'$directiveName),
  335.                     $directive
  336.                 );
  337.                 continue;
  338.             }
  339.             $includes Utils::some(
  340.                 $schemaDirective->locations,
  341.                 static function ($schemaLocation) use ($location) : bool {
  342.                     return $schemaLocation === $location;
  343.                 }
  344.             );
  345.             if (! $includes) {
  346.                 $errorNodes $schemaDirective->astNode
  347.                     ? [$directive$schemaDirective->astNode]
  348.                     : [$directive];
  349.                 $this->reportError(
  350.                     sprintf('Directive @%s not allowed at %s location.'$directiveName$location),
  351.                     $errorNodes
  352.                 );
  353.             }
  354.             if ($schemaDirective->isRepeatable) {
  355.                 continue;
  356.             }
  357.             $existingNodes                                  $potentiallyDuplicateDirectives[$directiveName] ?? [];
  358.             $existingNodes[]                                = $directive;
  359.             $potentiallyDuplicateDirectives[$directiveName] = $existingNodes;
  360.         }
  361.         foreach ($potentiallyDuplicateDirectives as $directiveName => $directiveList) {
  362.             if (count($directiveList) <= 1) {
  363.                 continue;
  364.             }
  365.             $this->reportError(
  366.                 sprintf('Non-repeatable directive @%s used more than once at the same location.'$directiveName),
  367.                 $directiveList
  368.             );
  369.         }
  370.     }
  371.     /**
  372.      * @param ObjectType|InterfaceType $type
  373.      */
  374.     private function validateFields($type)
  375.     {
  376.         $fieldMap $type->getFields();
  377.         // Objects and Interfaces both must define one or more fields.
  378.         if ($fieldMap === []) {
  379.             $this->reportError(
  380.                 sprintf('Type %s must define one or more fields.'$type->name),
  381.                 $this->getAllNodes($type)
  382.             );
  383.         }
  384.         foreach ($fieldMap as $fieldName => $field) {
  385.             // Ensure they are named correctly.
  386.             $this->validateName($field);
  387.             // Ensure they were defined at most once.
  388.             $fieldNodes $this->getAllFieldNodes($type$fieldName);
  389.             if ($fieldNodes && count($fieldNodes) > 1) {
  390.                 $this->reportError(
  391.                     sprintf('Field %s.%s can only be defined once.'$type->name$fieldName),
  392.                     $fieldNodes
  393.                 );
  394.                 continue;
  395.             }
  396.             // Ensure the type is an output type
  397.             if (! Type::isOutputType($field->getType())) {
  398.                 $this->reportError(
  399.                     sprintf(
  400.                         'The type of %s.%s must be Output Type but got: %s.',
  401.                         $type->name,
  402.                         $fieldName,
  403.                         Utils::printSafe($field->getType())
  404.                     ),
  405.                     $this->getFieldTypeNode($type$fieldName)
  406.                 );
  407.             }
  408.             // Ensure the arguments are valid
  409.             $argNames = [];
  410.             foreach ($field->args as $arg) {
  411.                 $argName $arg->name;
  412.                 // Ensure they are named correctly.
  413.                 $this->validateName($arg);
  414.                 if (isset($argNames[$argName])) {
  415.                     $this->reportError(
  416.                         sprintf(
  417.                             'Field argument %s.%s(%s:) can only be defined once.',
  418.                             $type->name,
  419.                             $fieldName,
  420.                             $argName
  421.                         ),
  422.                         $this->getAllFieldArgNodes($type$fieldName$argName)
  423.                     );
  424.                 }
  425.                 $argNames[$argName] = true;
  426.                 // Ensure the type is an input type
  427.                 if (! Type::isInputType($arg->getType())) {
  428.                     $this->reportError(
  429.                         sprintf(
  430.                             'The type of %s.%s(%s:) must be Input Type but got: %s.',
  431.                             $type->name,
  432.                             $fieldName,
  433.                             $argName,
  434.                             Utils::printSafe($arg->getType())
  435.                         ),
  436.                         $this->getFieldArgTypeNode($type$fieldName$argName)
  437.                     );
  438.                 }
  439.                 // Ensure argument definition directives are valid
  440.                 if (! isset($arg->astNode$arg->astNode->directives)) {
  441.                     continue;
  442.                 }
  443.                 $this->validateDirectivesAtLocation(
  444.                     $arg->astNode->directives,
  445.                     DirectiveLocation::ARGUMENT_DEFINITION
  446.                 );
  447.             }
  448.             // Ensure any directives are valid
  449.             if (! isset($field->astNode$field->astNode->directives)) {
  450.                 continue;
  451.             }
  452.             $this->validateDirectivesAtLocation(
  453.                 $field->astNode->directives,
  454.                 DirectiveLocation::FIELD_DEFINITION
  455.             );
  456.         }
  457.     }
  458.     /**
  459.      * @param Schema|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType|Directive $obj
  460.      *
  461.      * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]|InterfaceTypeExtensionNode[]
  462.      */
  463.     private function getAllNodes($obj)
  464.     {
  465.         if ($obj instanceof Schema) {
  466.             $astNode        $obj->getAstNode();
  467.             $extensionNodes $obj->extensionASTNodes;
  468.         } else {
  469.             $astNode        $obj->astNode;
  470.             $extensionNodes $obj->extensionASTNodes;
  471.         }
  472.         return $astNode
  473.             ? ($extensionNodes
  474.                 array_merge([$astNode], $extensionNodes)
  475.                 : [$astNode])
  476.             : ($extensionNodes ?? []);
  477.     }
  478.     /**
  479.      * @param Schema|ObjectType|InterfaceType|UnionType|EnumType|Directive $obj
  480.      */
  481.     private function getAllSubNodes($obj, callable $getter) : NodeList
  482.     {
  483.         $result = new NodeList([]);
  484.         foreach ($this->getAllNodes($obj) as $astNode) {
  485.             if (! $astNode) {
  486.                 continue;
  487.             }
  488.             $subNodes $getter($astNode);
  489.             if (! $subNodes) {
  490.                 continue;
  491.             }
  492.             $result $result->merge($subNodes);
  493.         }
  494.         return $result;
  495.     }
  496.     /**
  497.      * @param ObjectType|InterfaceType $type
  498.      * @param string                   $fieldName
  499.      *
  500.      * @return FieldDefinitionNode[]
  501.      */
  502.     private function getAllFieldNodes($type$fieldName)
  503.     {
  504.         $subNodes $this->getAllSubNodes($type, static function ($typeNode) {
  505.             return $typeNode->fields;
  506.         });
  507.         return Utils::filter($subNodes, static function ($fieldNode) use ($fieldName) : bool {
  508.             return $fieldNode->name->value === $fieldName;
  509.         });
  510.     }
  511.     /**
  512.      * @param ObjectType|InterfaceType $type
  513.      * @param string                   $fieldName
  514.      *
  515.      * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|null
  516.      */
  517.     private function getFieldTypeNode($type$fieldName) : ?TypeNode
  518.     {
  519.         $fieldNode $this->getFieldNode($type$fieldName);
  520.         return $fieldNode $fieldNode->type null;
  521.     }
  522.     /**
  523.      * @param ObjectType|InterfaceType $type
  524.      * @param string                   $fieldName
  525.      *
  526.      * @return FieldDefinitionNode|null
  527.      */
  528.     private function getFieldNode($type$fieldName)
  529.     {
  530.         $nodes $this->getAllFieldNodes($type$fieldName);
  531.         return $nodes[0] ?? null;
  532.     }
  533.     /**
  534.      * @param ObjectType|InterfaceType $type
  535.      * @param string                   $fieldName
  536.      * @param string                   $argName
  537.      *
  538.      * @return InputValueDefinitionNode[]
  539.      */
  540.     private function getAllFieldArgNodes($type$fieldName$argName)
  541.     {
  542.         $argNodes  = [];
  543.         $fieldNode $this->getFieldNode($type$fieldName);
  544.         if ($fieldNode && $fieldNode->arguments) {
  545.             foreach ($fieldNode->arguments as $node) {
  546.                 if ($node->name->value !== $argName) {
  547.                     continue;
  548.                 }
  549.                 $argNodes[] = $node;
  550.             }
  551.         }
  552.         return $argNodes;
  553.     }
  554.     /**
  555.      * @param ObjectType|InterfaceType $type
  556.      * @param string                   $fieldName
  557.      * @param string                   $argName
  558.      *
  559.      * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|null
  560.      */
  561.     private function getFieldArgTypeNode($type$fieldName$argName) : ?TypeNode
  562.     {
  563.         $fieldArgNode $this->getFieldArgNode($type$fieldName$argName);
  564.         return $fieldArgNode $fieldArgNode->type null;
  565.     }
  566.     /**
  567.      * @param ObjectType|InterfaceType $type
  568.      * @param string                   $fieldName
  569.      * @param string                   $argName
  570.      *
  571.      * @return InputValueDefinitionNode|null
  572.      */
  573.     private function getFieldArgNode($type$fieldName$argName)
  574.     {
  575.         $nodes $this->getAllFieldArgNodes($type$fieldName$argName);
  576.         return $nodes[0] ?? null;
  577.     }
  578.     /**
  579.      * @param ObjectType|InterfaceType $type
  580.      */
  581.     private function validateInterfaces(ImplementingType $type) : void
  582.     {
  583.         $ifaceTypeNames = [];
  584.         foreach ($type->getInterfaces() as $iface) {
  585.             if (! $iface instanceof InterfaceType) {
  586.                 $this->reportError(
  587.                     sprintf(
  588.                         'Type %s must only implement Interface types, it cannot implement %s.',
  589.                         $type->name,
  590.                         Utils::printSafe($iface)
  591.                     ),
  592.                     $this->getImplementsInterfaceNode($type$iface)
  593.                 );
  594.                 continue;
  595.             }
  596.             if ($type === $iface) {
  597.                 $this->reportError(
  598.                     sprintf(
  599.                         'Type %s cannot implement itself because it would create a circular reference.',
  600.                         $type->name
  601.                     ),
  602.                     $this->getImplementsInterfaceNode($type$iface)
  603.                 );
  604.                 continue;
  605.             }
  606.             if (isset($ifaceTypeNames[$iface->name])) {
  607.                 $this->reportError(
  608.                     sprintf('Type %s can only implement %s once.'$type->name$iface->name),
  609.                     $this->getAllImplementsInterfaceNodes($type$iface)
  610.                 );
  611.                 continue;
  612.             }
  613.             $ifaceTypeNames[$iface->name] = true;
  614.             $this->validateTypeImplementsAncestors($type$iface);
  615.             $this->validateTypeImplementsInterface($type$iface);
  616.         }
  617.     }
  618.     /**
  619.      * @param Schema|Type $object
  620.      *
  621.      * @return NodeList<DirectiveNode>
  622.      */
  623.     private function getDirectives($object)
  624.     {
  625.         return $this->getAllSubNodes($object, static function ($node) {
  626.             return $node->directives;
  627.         });
  628.     }
  629.     /**
  630.      * @param ObjectType|InterfaceType $type
  631.      */
  632.     private function getImplementsInterfaceNode(ImplementingType $typeType $shouldBeInterface) : ?NamedTypeNode
  633.     {
  634.         $nodes $this->getAllImplementsInterfaceNodes($type$shouldBeInterface);
  635.         return $nodes[0] ?? null;
  636.     }
  637.     /**
  638.      * @param ObjectType|InterfaceType $type
  639.      *
  640.      * @return array<int, NamedTypeNode>
  641.      */
  642.     private function getAllImplementsInterfaceNodes(ImplementingType $typeType $shouldBeInterface) : array
  643.     {
  644.         $subNodes $this->getAllSubNodes($type, static function (Node $typeNode) : NodeList {
  645.             /** @var ObjectTypeDefinitionNode|ObjectTypeExtensionNode|InterfaceTypeDefinitionNode|InterfaceTypeExtensionNode $typeNode */
  646.             return $typeNode->interfaces;
  647.         });
  648.         return Utils::filter($subNodes, static function (NamedTypeNode $ifaceNode) use ($shouldBeInterface) : bool {
  649.             return $ifaceNode->name->value === $shouldBeInterface->name;
  650.         });
  651.     }
  652.     /**
  653.      * @param ObjectType|InterfaceType $type
  654.      */
  655.     private function validateTypeImplementsInterface(ImplementingType $typeInterfaceType $iface)
  656.     {
  657.         $typeFieldMap  $type->getFields();
  658.         $ifaceFieldMap $iface->getFields();
  659.         // Assert each interface field is implemented.
  660.         foreach ($ifaceFieldMap as $fieldName => $ifaceField) {
  661.             $typeField array_key_exists($fieldName$typeFieldMap)
  662.                 ? $typeFieldMap[$fieldName]
  663.                 : null;
  664.             // Assert interface field exists on type.
  665.             if (! $typeField) {
  666.                 $this->reportError(
  667.                     sprintf(
  668.                         'Interface field %s.%s expected but %s does not provide it.',
  669.                         $iface->name,
  670.                         $fieldName,
  671.                         $type->name
  672.                     ),
  673.                     array_merge(
  674.                         [$this->getFieldNode($iface$fieldName)],
  675.                         $this->getAllNodes($type)
  676.                     )
  677.                 );
  678.                 continue;
  679.             }
  680.             // Assert interface field type is satisfied by type field type, by being
  681.             // a valid subtype. (covariant)
  682.             if (! TypeComparators::isTypeSubTypeOf(
  683.                 $this->schema,
  684.                 $typeField->getType(),
  685.                 $ifaceField->getType()
  686.             )
  687.             ) {
  688.                 $this->reportError(
  689.                     sprintf(
  690.                         'Interface field %s.%s expects type %s but %s.%s is type %s.',
  691.                         $iface->name,
  692.                         $fieldName,
  693.                         $ifaceField->getType(),
  694.                         $type->name,
  695.                         $fieldName,
  696.                         Utils::printSafe($typeField->getType())
  697.                     ),
  698.                     [
  699.                         $this->getFieldTypeNode($iface$fieldName),
  700.                         $this->getFieldTypeNode($type$fieldName),
  701.                     ]
  702.                 );
  703.             }
  704.             // Assert each interface field arg is implemented.
  705.             foreach ($ifaceField->args as $ifaceArg) {
  706.                 $argName $ifaceArg->name;
  707.                 $typeArg null;
  708.                 foreach ($typeField->args as $arg) {
  709.                     if ($arg->name === $argName) {
  710.                         $typeArg $arg;
  711.                         break;
  712.                     }
  713.                 }
  714.                 // Assert interface field arg exists on type field.
  715.                 if (! $typeArg) {
  716.                     $this->reportError(
  717.                         sprintf(
  718.                             'Interface field argument %s.%s(%s:) expected but %s.%s does not provide it.',
  719.                             $iface->name,
  720.                             $fieldName,
  721.                             $argName,
  722.                             $type->name,
  723.                             $fieldName
  724.                         ),
  725.                         [
  726.                             $this->getFieldArgNode($iface$fieldName$argName),
  727.                             $this->getFieldNode($type$fieldName),
  728.                         ]
  729.                     );
  730.                     continue;
  731.                 }
  732.                 // Assert interface field arg type matches type field arg type.
  733.                 // (invariant)
  734.                 // TODO: change to contravariant?
  735.                 if (! TypeComparators::isEqualType($ifaceArg->getType(), $typeArg->getType())) {
  736.                     $this->reportError(
  737.                         sprintf(
  738.                             'Interface field argument %s.%s(%s:) expects type %s but %s.%s(%s:) is type %s.',
  739.                             $iface->name,
  740.                             $fieldName,
  741.                             $argName,
  742.                             Utils::printSafe($ifaceArg->getType()),
  743.                             $type->name,
  744.                             $fieldName,
  745.                             $argName,
  746.                             Utils::printSafe($typeArg->getType())
  747.                         ),
  748.                         [
  749.                             $this->getFieldArgTypeNode($iface$fieldName$argName),
  750.                             $this->getFieldArgTypeNode($type$fieldName$argName),
  751.                         ]
  752.                     );
  753.                 }
  754.                 // TODO: validate default values?
  755.             }
  756.             // Assert additional arguments must not be required.
  757.             foreach ($typeField->args as $typeArg) {
  758.                 $argName  $typeArg->name;
  759.                 $ifaceArg null;
  760.                 foreach ($ifaceField->args as $arg) {
  761.                     if ($arg->name === $argName) {
  762.                         $ifaceArg $arg;
  763.                         break;
  764.                     }
  765.                 }
  766.                 if ($ifaceArg || ! $typeArg->isRequired()) {
  767.                     continue;
  768.                 }
  769.                 $this->reportError(
  770.                     sprintf(
  771.                         'Object field %s.%s includes required argument %s that is missing from the Interface field %s.%s.',
  772.                         $type->name,
  773.                         $fieldName,
  774.                         $argName,
  775.                         $iface->name,
  776.                         $fieldName
  777.                     ),
  778.                     [
  779.                         $this->getFieldArgNode($type$fieldName$argName),
  780.                         $this->getFieldNode($iface$fieldName),
  781.                     ]
  782.                 );
  783.             }
  784.         }
  785.     }
  786.     /**
  787.      * @param ObjectType|InterfaceType $type
  788.      */
  789.     private function validateTypeImplementsAncestors(ImplementingType $typeInterfaceType $iface) : void
  790.     {
  791.         $typeInterfaces $type->getInterfaces();
  792.         foreach ($iface->getInterfaces() as $transitive) {
  793.             if (in_array($transitive$typeInterfacestrue)) {
  794.                 continue;
  795.             }
  796.             $error $transitive === $type ?
  797.                 sprintf(
  798.                     'Type %s cannot implement %s because it would create a circular reference.',
  799.                     $type->name,
  800.                     $iface->name
  801.                 ) :
  802.                 sprintf(
  803.                     'Type %s must implement %s because it is implemented by %s.',
  804.                     $type->name,
  805.                     $transitive->name,
  806.                     $iface->name
  807.                 );
  808.             $this->reportError(
  809.                 $error,
  810.                 array_merge(
  811.                     $this->getAllImplementsInterfaceNodes($iface$transitive),
  812.                     $this->getAllImplementsInterfaceNodes($type$iface)
  813.                 )
  814.             );
  815.         }
  816.     }
  817.     private function validateUnionMembers(UnionType $union)
  818.     {
  819.         $memberTypes $union->getTypes();
  820.         if (! $memberTypes) {
  821.             $this->reportError(
  822.                 sprintf('Union type %s must define one or more member types.'$union->name),
  823.                 $this->getAllNodes($union)
  824.             );
  825.         }
  826.         $includedTypeNames = [];
  827.         foreach ($memberTypes as $memberType) {
  828.             if (isset($includedTypeNames[$memberType->name])) {
  829.                 $this->reportError(
  830.                     sprintf('Union type %s can only include type %s once.'$union->name$memberType->name),
  831.                     $this->getUnionMemberTypeNodes($union$memberType->name)
  832.                 );
  833.                 continue;
  834.             }
  835.             $includedTypeNames[$memberType->name] = true;
  836.             if ($memberType instanceof ObjectType) {
  837.                 continue;
  838.             }
  839.             $this->reportError(
  840.                 sprintf(
  841.                     'Union type %s can only include Object types, it cannot include %s.',
  842.                     $union->name,
  843.                     Utils::printSafe($memberType)
  844.                 ),
  845.                 $this->getUnionMemberTypeNodes($unionUtils::printSafe($memberType))
  846.             );
  847.         }
  848.     }
  849.     /**
  850.      * @param string $typeName
  851.      *
  852.      * @return NamedTypeNode[]
  853.      */
  854.     private function getUnionMemberTypeNodes(UnionType $union$typeName)
  855.     {
  856.         $subNodes $this->getAllSubNodes($union, static function ($unionNode) {
  857.             return $unionNode->types;
  858.         });
  859.         return Utils::filter($subNodes, static function ($typeNode) use ($typeName) : bool {
  860.             return $typeNode->name->value === $typeName;
  861.         });
  862.     }
  863.     private function validateEnumValues(EnumType $enumType)
  864.     {
  865.         $enumValues $enumType->getValues();
  866.         if (! $enumValues) {
  867.             $this->reportError(
  868.                 sprintf('Enum type %s must define one or more values.'$enumType->name),
  869.                 $this->getAllNodes($enumType)
  870.             );
  871.         }
  872.         foreach ($enumValues as $enumValue) {
  873.             $valueName $enumValue->name;
  874.             // Ensure no duplicates
  875.             $allNodes $this->getEnumValueNodes($enumType$valueName);
  876.             if ($allNodes && count($allNodes) > 1) {
  877.                 $this->reportError(
  878.                     sprintf('Enum type %s can include value %s only once.'$enumType->name$valueName),
  879.                     $allNodes
  880.                 );
  881.             }
  882.             // Ensure valid name.
  883.             $this->validateName($enumValue);
  884.             if ($valueName === 'true' || $valueName === 'false' || $valueName === 'null') {
  885.                 $this->reportError(
  886.                     sprintf('Enum type %s cannot include value: %s.'$enumType->name$valueName),
  887.                     $enumValue->astNode
  888.                 );
  889.             }
  890.             // Ensure valid directives
  891.             if (! isset($enumValue->astNode$enumValue->astNode->directives)) {
  892.                 continue;
  893.             }
  894.             $this->validateDirectivesAtLocation(
  895.                 $enumValue->astNode->directives,
  896.                 DirectiveLocation::ENUM_VALUE
  897.             );
  898.         }
  899.     }
  900.     /**
  901.      * @param string $valueName
  902.      *
  903.      * @return EnumValueDefinitionNode[]
  904.      */
  905.     private function getEnumValueNodes(EnumType $enum$valueName)
  906.     {
  907.         $subNodes $this->getAllSubNodes($enum, static function ($enumNode) {
  908.             return $enumNode->values;
  909.         });
  910.         return Utils::filter($subNodes, static function ($valueNode) use ($valueName) : bool {
  911.             return $valueNode->name->value === $valueName;
  912.         });
  913.     }
  914.     private function validateInputFields(InputObjectType $inputObj)
  915.     {
  916.         $fieldMap $inputObj->getFields();
  917.         if (! $fieldMap) {
  918.             $this->reportError(
  919.                 sprintf('Input Object type %s must define one or more fields.'$inputObj->name),
  920.                 $this->getAllNodes($inputObj)
  921.             );
  922.         }
  923.         // Ensure the arguments are valid
  924.         foreach ($fieldMap as $fieldName => $field) {
  925.             // Ensure they are named correctly.
  926.             $this->validateName($field);
  927.             // TODO: Ensure they are unique per field.
  928.             // Ensure the type is an input type
  929.             if (! Type::isInputType($field->getType())) {
  930.                 $this->reportError(
  931.                     sprintf(
  932.                         'The type of %s.%s must be Input Type but got: %s.',
  933.                         $inputObj->name,
  934.                         $fieldName,
  935.                         Utils::printSafe($field->getType())
  936.                     ),
  937.                     $field->astNode $field->astNode->type null
  938.                 );
  939.             }
  940.             // Ensure valid directives
  941.             if (! isset($field->astNode$field->astNode->directives)) {
  942.                 continue;
  943.             }
  944.             $this->validateDirectivesAtLocation(
  945.                 $field->astNode->directives,
  946.                 DirectiveLocation::INPUT_FIELD_DEFINITION
  947.             );
  948.         }
  949.     }
  950. }