public function __invoke(ValidationContext $context) { $varDefMap = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject(); return ['visitSpreadFragments' => true, Node::OPERATION_DEFINITION => function () use(&$varDefMap, &$visitedFragmentNames) { $varDefMap = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject(); }, Node::VARIABLE_DEFINITION => function (VariableDefinition $varDefAST) use($varDefMap) { $varDefMap[$varDefAST->variable->name->value] = $varDefAST; }, Node::FRAGMENT_SPREAD => function (FragmentSpread $spreadAST) use($visitedFragmentNames) { // Only visit fragments of a particular name once per operation if (!empty($visitedFragmentNames[$spreadAST->name->value])) { return Visitor::skipNode(); } $visitedFragmentNames[$spreadAST->name->value] = true; }, Node::VARIABLE => function (Variable $variableAST) use($context, $varDefMap) { $varName = $variableAST->name->value; $varDef = isset($varDefMap[$varName]) ? $varDefMap[$varName] : null; $varType = $varDef ? TypeInfo::typeFromAST($context->getSchema(), $varDef->type) : null; $inputType = $context->getInputType(); if ($varType && $inputType && !$this->varTypeAllowedForType($this->effectiveType($varType, $varDef), $inputType)) { return new Error(Messages::badVarPosMessage($varName, $varType, $inputType), [$variableAST]); } }]; }
/** * Given a variable definition, and any value of input, return a value which * adheres to the variable definition, or throw an error. */ private static function getVariableValue(Schema $schema, VariableDefinition $definitionAST, $input) { $type = Utils\TypeInfo::typeFromAST($schema, $definitionAST->type); $variable = $definitionAST->variable; if (!$type || !Type::isInputType($type)) { $printed = Printer::doPrint($definitionAST->type); throw new Error("Variable \"\${$variable->name->value}\" expected value of type " . "\"{$printed}\" which cannot be used as an input type.", [$definitionAST]); } $inputType = $type; $errors = self::isValidPHPValue($input, $inputType); if (empty($errors)) { if (null === $input) { $defaultValue = $definitionAST->defaultValue; if ($defaultValue) { return Utils\AST::valueFromAST($defaultValue, $inputType); } } return self::coerceValue($inputType, $input); } if (null === $input) { $printed = Printer::doPrint($definitionAST->type); throw new Error("Variable \"\${$variable->name->value}\" of required type " . "\"{$printed}\" was not provided.", [$definitionAST]); } $message = $errors ? "\n" . implode("\n", $errors) : ''; $val = json_encode($input); throw new Error("Variable \"\${$variable->name->value}\" got invalid value " . "{$val}.{$message}", [$definitionAST]); }
/** * Prepares an object map of variables of the correct type based on the provided * variable definitions and arbitrary input. If the input cannot be coerced * to match the variable definitions, a Error will be thrown. * * @param Schema $schema * @param VariableDefinitionNode[] $definitionNodes * @param array $inputs * @return array * @throws Error */ public static function getVariableValues(Schema $schema, $definitionNodes, array $inputs) { $coercedValues = []; foreach ($definitionNodes as $definitionNode) { $varName = $definitionNode->variable->name->value; $varType = Utils\TypeInfo::typeFromAST($schema, $definitionNode->type); if (!Type::isInputType($varType)) { throw new Error('Variable "$' . $varName . '" expected value of type ' . '"' . Printer::doPrint($definitionNode->type) . '" which cannot be used as an input type.', [$definitionNode->type]); } if (!array_key_exists($varName, $inputs)) { $defaultValue = $definitionNode->defaultValue; if ($defaultValue) { $coercedValues[$varName] = Utils\AST::valueFromAST($defaultValue, $varType); } if ($varType instanceof NonNull) { throw new Error('Variable "$' . $varName . '" of required type ' . '"' . Utils::printSafe($varType) . '" was not provided.', [$definitionNode]); } } else { $value = $inputs[$varName]; $errors = self::isValidPHPValue($value, $varType); if (!empty($errors)) { $message = "\n" . implode("\n", $errors); throw new Error('Variable "$' . $varName . '" got invalid value ' . json_encode($value) . '.' . $message, [$definitionNode]); } $coercedValue = self::coerceValue($varType, $value); Utils::invariant($coercedValue !== Utils::undefined(), 'Should have reported error.'); $coercedValues[$varName] = $coercedValue; } } return $coercedValues; }
public function __invoke(ValidationContext $context) { return [NodeKind::OPERATION_DEFINITION => ['enter' => function () { $this->varDefMap = []; }, 'leave' => function (OperationDefinitionNode $operation) use($context) { $usages = $context->getRecursiveVariableUsages($operation); foreach ($usages as $usage) { $node = $usage['node']; $type = $usage['type']; $varName = $node->name->value; $varDef = isset($this->varDefMap[$varName]) ? $this->varDefMap[$varName] : null; if ($varDef && $type) { // A var type is allowed if it is the same or more strict (e.g. is // a subtype of) than the expected type. It can be more strict if // the variable type is non-null when the expected type is nullable. // If both are list types, the variable item type can be more strict // than the expected item type (contravariant). $schema = $context->getSchema(); $varType = TypeInfo::typeFromAST($schema, $varDef->type); if ($varType && !TypeInfo::isTypeSubTypeOf($schema, $this->effectiveType($varType, $varDef), $type)) { $context->reportError(new Error(self::badVarPosMessage($varName, $varType, $type), [$varDef, $node])); } } } }], NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) { $this->varDefMap[$varDefNode->variable->name->value] = $varDefNode; }]; }
public function __invoke(ValidationContext $context) { return [NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use($context) { $type = Utils\TypeInfo::typeFromAST($context->getSchema(), $node->type); // If the variable type is not an input type, return an error. if ($type && !Type::isInputType($type)) { $variableName = $node->variable->name->value; $context->reportError(new Error(self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)), [$node->type])); } }]; }
/** * Given a variable definition, and any value of input, return a value which * adheres to the variable definition, or throw an error. */ private static function getVariableValue(Schema $schema, VariableDefinition $definitionAST, $input) { $type = Utils\TypeInfo::typeFromAST($schema, $definitionAST->type); if (!$type) { return null; } if (self::isValidValue($type, $input)) { if (null === $input) { $defaultValue = $definitionAST->defaultValue; if ($defaultValue) { return self::coerceValueAST($type, $defaultValue); } } return self::coerceValue($type, $input); } throw new Error("Variable \${$definitionAST->variable->name->value} expected value of type " . Printer::doPrint($definitionAST->type) . " but got: " . json_encode($input) . '.', [$definitionAST]); }
/** * Creates a new visitor instance which maintains a provided TypeInfo instance * along with visiting visitor. */ static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor) { return ['enter' => function ($node) use($typeInfo, $visitor) { $typeInfo->enter($node); $fn = self::getVisitFn($visitor, $node->kind, false); if ($fn) { $result = call_user_func_array($fn, func_get_args()); if ($result) { $typeInfo->leave($node); if ($result instanceof Node) { $typeInfo->enter($result); } } return $result; } return null; }, 'leave' => function ($node) use($typeInfo, $visitor) { $fn = self::getVisitFn($visitor, $node->kind, true); $result = $fn ? call_user_func_array($fn, func_get_args()) : null; $typeInfo->leave($node); return $result; }]; }
/** * Determines if a fragment is applicable to the given type. * * @param ExecutionContext $exeContext * @param $fragment * @param ObjectType $type * @return bool */ private static function doesFragmentConditionMatch(ExecutionContext $exeContext, $fragment, ObjectType $type) { $typeConditionNode = $fragment->typeCondition; if (!$typeConditionNode) { return true; } $conditionalType = Utils\TypeInfo::typeFromAST($exeContext->schema, $typeConditionNode); if ($conditionalType === $type) { return true; } if ($conditionalType instanceof AbstractType) { return $exeContext->schema->isPossibleType($conditionalType, $type); } return false; }
/** * Determines if a fragment is applicable to the given type. */ private static function doesFragmentConditionMatch(ExecutionContext $exeContext, $fragment, ObjectType $type) { $conditionalType = Utils\TypeInfo::typeFromAST($exeContext->schema, $fragment->typeCondition); if ($conditionalType === $type) { return true; } if ($conditionalType instanceof InterfaceType || $conditionalType instanceof UnionType) { return $conditionalType->isPossibleType($type); } return false; }
/** * Given a variable definition, and any value of input, return a value which * adheres to the variable definition, or throw an error. */ private static function getVariableValue(Schema $schema, VariableDefinition $definitionAST, $input) { $type = Utils\TypeInfo::typeFromAST($schema, $definitionAST->type); $variable = $definitionAST->variable; if (!$type || !Type::isInputType($type)) { $printed = Printer::doPrint($definitionAST->type); throw new Error("Variable \"\${$variable->name->value}\" expected value of type " . "\"{$printed}\" which cannot be used as an input type.", [$definitionAST]); } if (self::isValidValue($input, $type)) { if (null === $input) { $defaultValue = $definitionAST->defaultValue; if ($defaultValue) { return self::valueFromAST($defaultValue, $type); } } return self::coerceValue($type, $input); } throw new Error("Variable \${$definitionAST->variable->name->value} expected value of type " . Printer::doPrint($definitionAST->type) . " but got: " . json_encode($input) . '.', [$definitionAST]); }
function getArgument() { return $this->_typeInfo->getArgument(); }
/** * Given a selectionSet, adds all of the fields in that selection to * the passed in map of fields, and returns it at the end. * * Note: This is not the same as execution's collectFields because at static * time we do not know what object type will be used, so we unconditionally * spread in all fragments. * * @see GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged * * @param ValidationContext $context * @param Type|null $parentType * @param SelectionSetNode $selectionSet * @param \ArrayObject $visitedFragmentNames * @param \ArrayObject $astAndDefs * * @return \ArrayObject */ protected function collectFieldASTsAndDefs(ValidationContext $context, $parentType, SelectionSetNode $selectionSet, \ArrayObject $visitedFragmentNames = null, \ArrayObject $astAndDefs = null) { $_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject(); $_astAndDefs = $astAndDefs ?: new \ArrayObject(); foreach ($selectionSet->selections as $selection) { switch ($selection->kind) { case NodeKind::FIELD: /* @var FieldNode $selection */ $fieldName = $selection->name->value; $fieldDef = null; if ($parentType && method_exists($parentType, 'getFields')) { $tmp = $parentType->getFields(); $schemaMetaFieldDef = Introspection::schemaMetaFieldDef(); $typeMetaFieldDef = Introspection::typeMetaFieldDef(); $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef(); if ($fieldName === $schemaMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) { $fieldDef = $schemaMetaFieldDef; } elseif ($fieldName === $typeMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) { $fieldDef = $typeMetaFieldDef; } elseif ($fieldName === $typeNameMetaFieldDef->name) { $fieldDef = $typeNameMetaFieldDef; } elseif (isset($tmp[$fieldName])) { $fieldDef = $tmp[$fieldName]; } } $responseName = $this->getFieldName($selection); if (!isset($_astAndDefs[$responseName])) { $_astAndDefs[$responseName] = new \ArrayObject(); } // create field context $_astAndDefs[$responseName][] = [$selection, $fieldDef]; break; case NodeKind::INLINE_FRAGMENT: /* @var InlineFragmentNode $selection */ $_astAndDefs = $this->collectFieldASTsAndDefs($context, TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition), $selection->selectionSet, $_visitedFragmentNames, $_astAndDefs); break; case NodeKind::FRAGMENT_SPREAD: /* @var FragmentSpreadNode $selection */ $fragName = $selection->name->value; if (empty($_visitedFragmentNames[$fragName])) { $_visitedFragmentNames[$fragName] = true; $fragment = $context->getFragment($fragName); if ($fragment) { $_astAndDefs = $this->collectFieldASTsAndDefs($context, TypeInfo::typeFromAST($context->getSchema(), $fragment->typeCondition), $fragment->selectionSet, $_visitedFragmentNames, $_astAndDefs); } } break; } } return $_astAndDefs; }
/** * @param ObjectType $object * @param InterfaceType $iface * @throws \Exception */ private static function assertObjectImplementsInterface(Schema $schema, ObjectType $object, InterfaceType $iface) { $objectFieldMap = $object->getFields(); $ifaceFieldMap = $iface->getFields(); foreach ($ifaceFieldMap as $fieldName => $ifaceField) { Utils::invariant(isset($objectFieldMap[$fieldName]), "\"{$iface}\" expects field \"{$fieldName}\" but \"{$object}\" does not provide it"); /** @var $ifaceField FieldDefinition */ /** @var $objectField FieldDefinition */ $objectField = $objectFieldMap[$fieldName]; Utils::invariant(Utils\TypeInfo::isTypeSubTypeOf($schema, $objectField->getType(), $ifaceField->getType()), "{$iface}.{$fieldName} expects type \"{$ifaceField->getType()}\" but " . "{$object}.{$fieldName} provides type \"{$objectField->getType()}\"."); foreach ($ifaceField->args as $ifaceArg) { /** @var $ifaceArg FieldArgument */ /** @var $objectArg FieldArgument */ $argName = $ifaceArg->name; $objectArg = $objectField->getArg($argName); // Assert interface field arg exists on object field. Utils::invariant($objectArg, "{$iface}.{$fieldName} expects argument \"{$argName}\" but {$object}.{$fieldName} does not provide it."); // Assert interface field arg type matches object field arg type. // (invariant) Utils::invariant(Utils\TypeInfo::isEqualType($ifaceArg->getType(), $objectArg->getType()), "{$iface}.{$fieldName}({$argName}:) expects type \"{$ifaceArg->getType()}\" " . "but {$object}.{$fieldName}({$argName}:) provides " . "type \"{$objectArg->getType()}\""); // Assert argument set invariance. foreach ($objectField->args as $objectArg) { $argName = $objectArg->name; $ifaceArg = $ifaceField->getArg($argName); Utils::invariant($ifaceArg, "{$iface}.{$fieldName} does not define argument \"{$argName}\" but " . "{$object}.{$fieldName} provides it."); } } } }
/** * This uses a specialized visitor which runs multiple visitors in parallel, * while maintaining the visitor skip and break API. * * @param Schema $schema * @param Document $documentAST * @param array $rules * @return array */ public static function visitUsingRules(Schema $schema, Document $documentAST, array $rules) { $typeInfo = new TypeInfo($schema); $context = new ValidationContext($schema, $documentAST, $typeInfo); $errors = []; // TODO: convert to class $visitInstances = function ($ast, $instances) use($typeInfo, $context, &$errors, &$visitInstances) { $skipUntil = new \SplFixedArray(count($instances)); $skipCount = 0; Visitor::visit($ast, ['enter' => function ($node, $key) use($typeInfo, $instances, $skipUntil, &$skipCount, &$errors, $context, $visitInstances) { $typeInfo->enter($node); for ($i = 0; $i < count($instances); $i++) { // Do not visit this instance if it returned false for a previous node if ($skipUntil[$i]) { continue; } $result = null; // Do not visit top level fragment definitions if this instance will // visit those fragments inline because it // provided `visitSpreadFragments`. if ($node->kind === Node::FRAGMENT_DEFINITION && $key !== null && !empty($instances[$i]['visitSpreadFragments'])) { $result = Visitor::skipNode(); } else { $enter = Visitor::getVisitFn($instances[$i], false, $node->kind); if ($enter instanceof \Closure) { // $enter = $enter->bindTo($instances[$i]); $result = call_user_func_array($enter, func_get_args()); } else { $result = null; } } if ($result instanceof VisitorOperation) { if ($result->doContinue) { $skipUntil[$i] = $node; $skipCount++; // If all instances are being skipped over, skip deeper traversal if ($skipCount === count($instances)) { for ($k = 0; $k < count($instances); $k++) { if ($skipUntil[$k] === $node) { $skipUntil[$k] = null; $skipCount--; } } return Visitor::skipNode(); } } else { if ($result->doBreak) { $instances[$i] = null; } } } else { if ($result && self::isError($result)) { self::append($errors, $result); for ($j = $i - 1; $j >= 0; $j--) { $leaveFn = Visitor::getVisitFn($instances[$j], true, $node->kind); if ($leaveFn) { // $leaveFn = $leaveFn->bindTo($instances[$j]) $result = call_user_func_array($leaveFn, func_get_args()); if ($result instanceof VisitorOperation) { if ($result->doBreak) { $instances[$j] = null; } } else { if (self::isError($result)) { self::append($errors, $result); } else { if ($result !== null) { throw new \Exception("Config cannot edit document."); } } } } } $typeInfo->leave($node); return Visitor::skipNode(); } else { if ($result !== null) { throw new \Exception("Config cannot edit document."); } } } } // If any validation instances provide the flag `visitSpreadFragments` // and this node is a fragment spread, validate the fragment from // this point. if ($node instanceof FragmentSpread) { $fragment = $context->getFragment($node->name->value); if ($fragment) { $fragVisitingInstances = []; foreach ($instances as $idx => $inst) { if (!empty($inst['visitSpreadFragments']) && !$skipUntil[$idx]) { $fragVisitingInstances[] = $inst; } } if (!empty($fragVisitingInstances)) { $visitInstances($fragment, $fragVisitingInstances); } } } }, 'leave' => function ($node) use($instances, $typeInfo, $skipUntil, &$skipCount, &$errors) { for ($i = count($instances) - 1; $i >= 0; $i--) { if ($skipUntil[$i]) { if ($skipUntil[$i] === $node) { $skipUntil[$i] = null; $skipCount--; } continue; } $leaveFn = Visitor::getVisitFn($instances[$i], true, $node->kind); if ($leaveFn) { // $leaveFn = $leaveFn.bindTo($instances[$i]); $result = call_user_func_array($leaveFn, func_get_args()); if ($result instanceof VisitorOperation) { if ($result->doBreak) { $instances[$i] = null; } } else { if (self::isError($result)) { self::append($errors, $result); } else { if ($result !== null) { throw new \Exception("Config cannot edit document."); } } } } } $typeInfo->leave($node); }]); }; // Visit the whole document with instances of all provided rules. $allRuleInstances = []; foreach ($rules as $rule) { $allRuleInstances[] = $rule($context); } $visitInstances($documentAST, $allRuleInstances); return $errors; }
private function getFragmentType(ValidationContext $context, $name) { $frag = $context->getFragment($name); return $frag ? TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition) : null; }
/** * Given a selectionSet, adds all of the fields in that selection to * the passed in map of fields, and returns it at the end. * * Note: This is not the same as execution's collectFields because at static * time we do not know what object type will be used, so we unconditionally * spread in all fragments. * * @param ValidationContext $context * @param mixed $parentType * @param SelectionSetNode $selectionSet * @param \ArrayObject $visitedFragmentNames * @param \ArrayObject $astAndDefs * @return mixed */ private function collectFieldNodesAndDefs(ValidationContext $context, $parentType, SelectionSetNode $selectionSet, \ArrayObject $visitedFragmentNames = null, \ArrayObject $astAndDefs = null) { $_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject(); $_astAndDefs = $astAndDefs ?: new \ArrayObject(); for ($i = 0; $i < count($selectionSet->selections); $i++) { $selection = $selectionSet->selections[$i]; switch ($selection->kind) { case NodeKind::FIELD: $fieldName = $selection->name->value; $fieldDef = null; if ($parentType && method_exists($parentType, 'getFields')) { $tmp = $parentType->getFields(); if (isset($tmp[$fieldName])) { $fieldDef = $tmp[$fieldName]; } } $responseName = $selection->alias ? $selection->alias->value : $fieldName; if (!isset($_astAndDefs[$responseName])) { $_astAndDefs[$responseName] = new \ArrayObject(); } $_astAndDefs[$responseName][] = [$parentType, $selection, $fieldDef]; break; case NodeKind::INLINE_FRAGMENT: $typeCondition = $selection->typeCondition; $inlineFragmentType = $typeCondition ? TypeInfo::typeFromAST($context->getSchema(), $typeCondition) : $parentType; $_astAndDefs = $this->collectFieldNodesAndDefs($context, $inlineFragmentType, $selection->selectionSet, $_visitedFragmentNames, $_astAndDefs); break; case NodeKind::FRAGMENT_SPREAD: /** @var FragmentSpreadNode $selection */ $fragName = $selection->name->value; if (!empty($_visitedFragmentNames[$fragName])) { continue; } $_visitedFragmentNames[$fragName] = true; $fragment = $context->getFragment($fragName); if (!$fragment) { continue; } $fragmentType = TypeInfo::typeFromAST($context->getSchema(), $fragment->typeCondition); $_astAndDefs = $this->collectFieldNodesAndDefs($context, $fragmentType, $fragment->selectionSet, $_visitedFragmentNames, $_astAndDefs); break; } } return $_astAndDefs; }
/** * @return FieldDefinition */ function getFieldDef() { return $this->_typeInfo->getFieldDef(); }