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; }
/** * 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, $valueAST) { // A value must be provided if the type is non-null. if ($type instanceof NonNull) { $wrappedType = $type->getWrappedType(); if (!$valueAST) { if ($wrappedType->name) { return ["Expected \"{$wrappedType->name}!\", found null."]; } return ['Expected non-null value, found null.']; } return static::isValidLiteralValue($wrappedType, $valueAST); } if (!$valueAST) { return []; } // This function only tests literals, and assumes variables will provide // values of the correct type. if ($valueAST instanceof Variable) { return []; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { $errors = []; foreach ($valueAST->values as $index => $itemAST) { $tmp = static::isValidLiteralValue($itemType, $itemAST); 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, $valueAST); } } // Input objects check each defined field and look for undefined fields. if ($type instanceof InputObjectType) { if ($valueAST->kind !== Node::OBJECT) { return ["Expected \"{$type->name}\", found not an object."]; } $fields = $type->getFields(); $errors = []; // Ensure every provided field is defined. $fieldASTs = $valueAST->fields; foreach ($fieldASTs as $providedFieldAST) { if (empty($fields[$providedFieldAST->name->value])) { $errors[] = "In field \"{$providedFieldAST->name->value}\": Unknown field."; } } // Ensure every defined field is valid. $fieldASTMap = Utils::keyMap($fieldASTs, function ($fieldAST) { return $fieldAST->name->value; }); foreach ($fields as $fieldName => $field) { $result = static::isValidLiteralValue($field->getType(), isset($fieldASTMap[$fieldName]) ? $fieldASTMap[$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($valueAST); if (null === $parseResult) { $printed = Printer::doPrint($valueAST); return ["Expected type \"{$type->name}\", found {$printed}."]; } return []; } throw new InvariantViolation('Must be input type'); }