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

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($directives, string $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 $type, Type $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 $type, Type $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 $type, InterfaceType $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 $type, InterfaceType $iface) : void
  790. {
  791. $typeInterfaces = $type->getInterfaces();
  792. foreach ($iface->getInterfaces() as $transitive) {
  793. if (in_array($transitive, $typeInterfaces, true)) {
  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($union, Utils::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. }