public function __invoke(ValidationContext $context) { return [Node::FIELD => function (Field $fieldAST) use($context) { $fieldDef = $context->getFieldDef(); if (!$fieldDef) { return Visitor::skipNode(); } $errors = []; $argASTs = $fieldAST->arguments ?: []; $argASTMap = Utils::keyMap($argASTs, function (Argument $arg) { return $arg->name->value; }); foreach ($fieldDef->args as $argDef) { $argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null; if (!$argAST && $argDef->getType() instanceof NonNull) { $errors[] = new Error(Messages::missingArgMessage($fieldAST->name->value, $argDef->name, $argDef->getType()), [$fieldAST]); } } $argDefMap = Utils::keyMap($fieldDef->args, function ($def) { return $def->name; }); foreach ($argASTs as $argAST) { $argDef = $argDefMap[$argAST->name->value]; if ($argDef && !DocumentValidator::isValidLiteralValue($argAST->value, $argDef->getType())) { $errors[] = new Error(Messages::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value)), [$argAST->value]); } } return !empty($errors) ? $errors : null; }]; }
private function doTypesOverlap($t1, $t2) { if ($t1 === $t2) { return true; } if ($t1 instanceof ObjectType) { if ($t2 instanceof ObjectType) { return false; } return in_array($t1, $t2->getPossibleTypes()); } if ($t1 instanceof InterfaceType || $t1 instanceof UnionType) { if ($t2 instanceof ObjectType) { return in_array($t2, $t1->getPossibleTypes()); } $t1TypeNames = Utils::keyMap($t1->getPossibleTypes(), function ($type) { return $type->name; }); foreach ($t2->getPossibleTypes() as $type) { if (!empty($t1TypeNames[$type->name])) { return true; } } } return false; }
/** * Prepares an object map of argument values given a list of argument * definitions and list of argument AST nodes. * * @param FieldArgument[] $argDefs * @param Argument[] $argASTs * @param $variableValues * @return array */ public static function getArgumentValues($argDefs, $argASTs, $variableValues) { if (!$argDefs) { return []; } $argASTMap = $argASTs ? Utils::keyMap($argASTs, function ($arg) { return $arg->name->value; }) : []; $result = []; foreach ($argDefs as $argDef) { $name = $argDef->name; $valueAST = isset($argASTMap[$name]) ? $argASTMap[$name]->value : null; $value = Utils\AST::valueFromAST($valueAST, $argDef->getType(), $variableValues); if (null === $value) { $value = $argDef->defaultValue; } if (null !== $value) { $result[$name] = $value; } } return $result; }
/** * Utility for validators which determines if a value literal AST is valid given * an input type. * * Note that this only validates literal values, variables are assumed to * provide values of the correct type. * * @return array */ public static function isValidLiteralValue(Type $type, $valueNode) { // A value must be provided if the type is non-null. if ($type instanceof NonNull) { if (!$valueNode || $valueNode instanceof NullValueNode) { return ['Expected "' . Utils::printSafe($type) . '", found null.']; } return static::isValidLiteralValue($type->getWrappedType(), $valueNode); } if (!$valueNode || $valueNode instanceof NullValueNode) { return []; } // This function only tests literals, and assumes variables will provide // values of the correct type. if ($valueNode instanceof VariableNode) { return []; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueNode instanceof ListValueNode) { $errors = []; foreach ($valueNode->values as $index => $itemNode) { $tmp = static::isValidLiteralValue($itemType, $itemNode); if ($tmp) { $errors = array_merge($errors, Utils::map($tmp, function ($error) use($index) { return "In element #{$index}: {$error}"; })); } } return $errors; } else { return static::isValidLiteralValue($itemType, $valueNode); } } // Input objects check each defined field and look for undefined fields. if ($type instanceof InputObjectType) { if ($valueNode->kind !== NodeKind::OBJECT) { return ["Expected \"{$type->name}\", found not an object."]; } $fields = $type->getFields(); $errors = []; // Ensure every provided field is defined. $fieldNodes = $valueNode->fields; foreach ($fieldNodes as $providedFieldNode) { if (empty($fields[$providedFieldNode->name->value])) { $errors[] = "In field \"{$providedFieldNode->name->value}\": Unknown field."; } } // Ensure every defined field is valid. $fieldNodeMap = Utils::keyMap($fieldNodes, function ($fieldNode) { return $fieldNode->name->value; }); foreach ($fields as $fieldName => $field) { $result = static::isValidLiteralValue($field->getType(), isset($fieldNodeMap[$fieldName]) ? $fieldNodeMap[$fieldName]->value : null); if ($result) { $errors = array_merge($errors, Utils::map($result, function ($error) use($fieldName) { return "In field \"{$fieldName}\": {$error}"; })); } } return $errors; } if ($type instanceof LeafType) { // Scalar/Enum input checks to ensure the type can parse the value to // a non-null value. $parseResult = $type->parseLiteral($valueNode); if (null === $parseResult) { $printed = Printer::doPrint($valueNode); return ["Expected type \"{$type->name}\", found {$printed}."]; } return []; } throw new InvariantViolation('Must be input type'); }
/** * Produces a PHP value given a GraphQL Value AST. * * A GraphQL type must be provided, which will be used to interpret different * GraphQL Value literals. * * | GraphQL Value | PHP Value | * | -------------------- | ------------- | * | Input Object | Assoc Array | * | List | Array | * | Boolean | Boolean | * | String | String | * | Int / Float | Int / Float | * | Enum Value | Mixed | * * @param $valueAST * @param InputType $type * @param null $variables * @return array|null * @throws \Exception */ public static function valueFromAST($valueAST, InputType $type, $variables = null) { if ($type instanceof NonNull) { // Note: we're not checking that the result of valueFromAST is non-null. // We're assuming that this query has been validated and the value used // here is of the correct type. return self::valueFromAST($valueAST, $type->getWrappedType(), $variables); } if (!$valueAST) { return null; } if ($valueAST instanceof Variable) { $variableName = $valueAST->name->value; if (!$variables || !isset($variables[$variableName])) { return null; } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here // is of the correct type. return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { return array_map(function ($itemAST) use($itemType, $variables) { return self::valueFromAST($itemAST, $itemType, $variables); }, $valueAST->values); } else { return [self::valueFromAST($valueAST, $itemType, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if (!$valueAST instanceof ObjectValue) { return null; } $fieldASTs = Utils::keyMap($valueAST->fields, function ($field) { return $field->name->value; }); $values = []; foreach ($fields as $field) { $fieldAST = isset($fieldASTs[$field->name]) ? $fieldASTs[$field->name] : null; $fieldValue = self::valueFromAST($fieldAST ? $fieldAST->value : null, $field->getType(), $variables); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $values[$field->name] = $fieldValue; } } return $values; } if ($type instanceof LeafType) { return $type->parseLiteral($valueAST); } throw new InvariantViolation('Must be input type'); }
public static function valueFromAST($valueAST, InputType $type, $variables = null) { if ($type instanceof NonNull) { return self::valueFromAST($valueAST, $type->getWrappedType(), $variables); } if (!$valueAST) { return null; } if ($valueAST instanceof Variable) { $variableName = $valueAST->name->value; if (!$variables || !isset($variables[$variableName])) { return null; } return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { return array_map(function ($itemAST) use($itemType, $variables) { return Values::valueFromAST($itemAST, $itemType, $variables); }, $valueAST->values); } else { return [self::valueFromAST($valueAST, $itemType, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if (!$valueAST instanceof ObjectValue) { return null; } $fieldASTs = Utils::keyMap($valueAST->fields, function ($field) { return $field->name->value; }); $values = []; foreach ($fields as $field) { $fieldAST = isset($fieldASTs[$field->name]) ? $fieldASTs[$field->name] : null; $fieldValue = self::valueFromAST($fieldAST ? $fieldAST->value : null, $field->getType(), $variables); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $values[$field->name] = $fieldValue; } } return $values; } Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type'); return $type->parseLiteral($valueAST); }
/** * Produces a PHP value given a GraphQL Value AST. * * A GraphQL type must be provided, which will be used to interpret different * GraphQL Value literals. * * Returns `null` when the value could not be validly coerced according to * the provided type. * * | GraphQL Value | PHP Value | * | -------------------- | ------------- | * | Input Object | Assoc Array | * | List | Array | * | Boolean | Boolean | * | String | String | * | Int / Float | Int / Float | * | Enum Value | Mixed | * | Null Value | stdClass | instance of NullValue::getNullValue() * * @param $valueNode * @param InputType $type * @param null $variables * @return array|null|\stdClass * @throws \Exception */ public static function valueFromAST($valueNode, InputType $type, $variables = null) { $undefined = Utils::undefined(); if (!$valueNode) { // When there is no AST, then there is also no value. // Importantly, this is different from returning the GraphQL null value. return $undefined; } if ($type instanceof NonNull) { if ($valueNode instanceof NullValueNode) { // Invalid: intentionally return no value. return $undefined; } return self::valueFromAST($valueNode, $type->getWrappedType(), $variables); } if ($valueNode instanceof NullValueNode) { // This is explicitly returning the value null. return null; } if ($valueNode instanceof VariableNode) { $variableName = $valueNode->name->value; if (!$variables || !array_key_exists($variableName, $variables)) { // No valid return value. return $undefined; } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here // is of the correct type. return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueNode instanceof ListValueNode) { $coercedValues = []; $itemNodes = $valueNode->values; foreach ($itemNodes as $itemNode) { if (self::isMissingVariable($itemNode, $variables)) { // If an array contains a missing variable, it is either coerced to // null or if the item type is non-null, it considered invalid. if ($itemType instanceof NonNull) { // Invalid: intentionally return no value. return $undefined; } $coercedValues[] = null; } else { $itemValue = self::valueFromAST($itemNode, $itemType, $variables); if ($undefined === $itemValue) { // Invalid: intentionally return no value. return $undefined; } $coercedValues[] = $itemValue; } } return $coercedValues; } $coercedValue = self::valueFromAST($valueNode, $itemType, $variables); if ($undefined === $coercedValue) { // Invalid: intentionally return no value. return $undefined; } return [$coercedValue]; } if ($type instanceof InputObjectType) { if (!$valueNode instanceof ObjectValueNode) { // Invalid: intentionally return no value. return $undefined; } $coercedObj = []; $fields = $type->getFields(); $fieldNodes = Utils::keyMap($valueNode->fields, function ($field) { return $field->name->value; }); foreach ($fields as $field) { /** @var ValueNode $fieldNode */ $fieldName = $field->name; $fieldNode = isset($fieldNodes[$fieldName]) ? $fieldNodes[$fieldName] : null; if (!$fieldNode || self::isMissingVariable($fieldNode->value, $variables)) { if ($field->defaultValueExists()) { $coercedObj[$fieldName] = $field->defaultValue; } else { if ($field->getType() instanceof NonNull) { // Invalid: intentionally return no value. return $undefined; } } continue; } $fieldValue = self::valueFromAST($fieldNode ? $fieldNode->value : null, $field->getType(), $variables); if ($undefined === $fieldValue) { // Invalid: intentionally return no value. return $undefined; } $coercedObj[$fieldName] = $fieldValue; } return $coercedObj; } if ($type instanceof LeafType) { $parsed = $type->parseLiteral($valueNode); if (null === $parsed) { // null represent a failure to parse correctly, // in which case no value is returned. return $undefined; } return $parsed; } throw new InvariantViolation('Must be input type'); }
static function isValidLiteralValue($valueAST, Type $type) { // A value can only be not provided if the type is nullable. if (!$valueAST) { return !$type instanceof NonNull; } // Unwrap non-null. if ($type instanceof NonNull) { return self::isValidLiteralValue($valueAST, $type->getWrappedType()); } // This function only tests literals, and assumes variables will provide // values of the correct type. if ($valueAST instanceof Variable) { return true; } if (!$valueAST instanceof Value) { return false; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { foreach ($valueAST->values as $itemAST) { if (!self::isValidLiteralValue($itemAST, $itemType)) { return false; } } return true; } else { return self::isValidLiteralValue($valueAST, $itemType); } } // Scalar/Enum input checks to ensure the type can serialize the value to // a non-null value. if ($type instanceof ScalarType || $type instanceof EnumType) { return $type->parseLiteral($valueAST) !== null; } // Input objects check each defined field, ensuring it is of the correct // type and provided if non-nullable. if ($type instanceof InputObjectType) { $fields = $type->getFields(); if ($valueAST->kind !== Node::OBJECT) { return false; } $fieldASTs = $valueAST->fields; $fieldASTMap = Utils::keyMap($fieldASTs, function ($field) { return $field->name->value; }); foreach ($fields as $fieldKey => $field) { $fieldName = $field->name ?: $fieldKey; if (!isset($fieldASTMap[$fieldName]) && $field->getType() instanceof NonNull) { // Required fields missing return false; } } foreach ($fieldASTs as $fieldAST) { if (empty($fields[$fieldAST->name->value]) || !self::isValidLiteralValue($fieldAST->value, $fields[$fieldAST->name->value]->getType())) { return false; } } return true; } // Any other kind of type is not an input type, and a literal cannot be used. return false; }
/** * Given a type and a value AST node known to match this type, build a * runtime value. */ private static function coerceValueAST(Type $type, $valueAST, $variables) { if ($type instanceof NonNull) { // Note: we're not checking that the result of coerceValueAST is non-null. // We're assuming that this query has been validated and the value used // here is of the correct type. return self::coerceValueAST($type->getWrappedType(), $valueAST, $variables); } if (!$valueAST) { return null; } if ($valueAST->kind === Node::VARIABLE) { $variableName = $valueAST->name->value; if (!isset($variables, $variables[$variableName])) { return null; } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here // is of the correct type. return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST->kind === Node::ARR) { $tmp = []; foreach ($valueAST->values as $itemAST) { $tmp[] = self::coerceValueAST($itemType, $itemAST, $variables); } return $tmp; } else { return [self::coerceValueAST($itemType, $valueAST, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if ($valueAST->kind !== Node::OBJECT) { return null; } $fieldASTs = Utils::keyMap($valueAST->fields, function ($field) { return $field->name->value; }); $obj = []; foreach ($fields as $fieldName => $field) { $fieldAST = $fieldASTs[$fieldName]; $fieldValue = self::coerceValueAST($field->getType(), $fieldAST ? $fieldAST->value : null, $variables); $obj[$fieldName] = $fieldValue === null ? $field->defaultValue : $fieldValue; } return $obj; } if ($type instanceof ScalarType || $type instanceof EnumType) { $coerced = $type->coerceLiteral($valueAST); if (null !== $coerced) { return $coerced; } } return null; }
/** * Prepares an object map of argument values given a list of argument * definitions and list of argument AST nodes. * * @param FieldDefinition|Directive $def * @param FieldNode|\GraphQL\Language\AST\DirectiveNode $node * @param $variableValues * @return array * @throws Error */ public static function getArgumentValues($def, $node, $variableValues) { $argDefs = $def->args; $argNodes = $node->arguments; if (!$argDefs || null === $argNodes) { return []; } $coercedValues = []; $undefined = Utils::undefined(); /** @var ArgumentNode[] $argNodeMap */ $argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) { return $arg->name->value; }) : []; foreach ($argDefs as $argDef) { $name = $argDef->name; $argType = $argDef->getType(); $argumentNode = isset($argNodeMap[$name]) ? $argNodeMap[$name] : null; if (!$argumentNode) { if ($argDef->defaultValueExists()) { $coercedValues[$name] = $argDef->defaultValue; } else { if ($argType instanceof NonNull) { throw new Error('Argument "' . $name . '" of required type ' . '"' . Utils::printSafe($argType) . '" was not provided.', [$node]); } } } else { if ($argumentNode->value instanceof VariableNode) { $variableName = $argumentNode->value->name->value; if ($variableValues && array_key_exists($variableName, $variableValues)) { // Note: this does not check that this variable value is correct. // This assumes that this query has been validated and the variable // usage here is of the correct type. $coercedValues[$name] = $variableValues[$variableName]; } else { if ($argDef->defaultValueExists()) { $coercedValues[$name] = $argDef->defaultValue; } else { if ($argType instanceof NonNull) { throw new Error('Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' . 'provided the variable "$' . $variableName . '" which was not provided ' . 'a runtime value.', [$argumentNode->value]); } } } } else { $valueNode = $argumentNode->value; $coercedValue = Utils\AST::valueFromAST($valueNode, $argType, $variableValues); if ($coercedValue === $undefined) { $errors = DocumentValidator::isValidLiteralValue($argType, $valueNode); $message = !empty($errors) ? "\n" . implode("\n", $errors) : ''; throw new Error('Argument "' . $name . '" got invalid value ' . Printer::doPrint($valueNode) . '.' . $message, [$argumentNode->value]); } $coercedValues[$name] = $coercedValue; } } } return $coercedValues; }