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; }]; }
public function __invoke(ValidationContext $context) { return [Node::ARGUMENT => function (Argument $argAST) use($context) { $argDef = $context->getArgument(); if ($argDef && !DocumentValidator::isValidLiteralValue($argAST->value, $argDef->getType())) { return new Error(self::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value)), [$argAST->value]); } }]; }
public static function doPrint($ast) { return Visitor::visit($ast, array('leave' => array(Node::NAME => function ($node) { return $node->value . ''; }, Node::VARIABLE => function ($node) { return '$' . $node->name; }, Node::DOCUMENT => function (Document $node) { return self::join($node->definitions, "\n\n") . "\n"; }, Node::OPERATION_DEFINITION => function (OperationDefinition $node) { $op = $node->operation; $name = $node->name; $defs = Printer::manyList('(', $node->variableDefinitions, ', ', ')'); $directives = self::join($node->directives, ' '); $selectionSet = $node->selectionSet; return !$name ? $selectionSet : self::join([$op, self::join([$name, $defs]), $directives, $selectionSet], ' '); }, Node::VARIABLE_DEFINITION => function (VariableDefinition $node) { return self::join([$node->variable . ': ' . $node->type, $node->defaultValue], ' = '); }, Node::SELECTION_SET => function (SelectionSet $node) { return self::blockList($node->selections, ",\n"); }, Node::FIELD => function (Field $node) { $r11 = self::join([$node->alias, $node->name], ': '); $r1 = self::join([$r11, self::manyList('(', $node->arguments, ', ', ')')]); $r2 = self::join($node->directives, ' '); return self::join([$r1, $r2, $node->selectionSet], ' '); }, Node::ARGUMENT => function (Argument $node) { return $node->name . ': ' . $node->value; }, Node::FRAGMENT_SPREAD => function (FragmentSpread $node) { return self::join(['...' . $node->name, self::join($node->directives, '')], ' '); }, Node::INLINE_FRAGMENT => function (InlineFragment $node) { return self::join(['... on', $node->typeCondition, self::join($node->directives, ' '), $node->selectionSet], ' '); }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) { return self::join(['fragment', $node->name, 'on', $node->typeCondition, self::join($node->directives, ' '), $node->selectionSet], ' '); }, Node::INT => function (IntValue $node) { return $node->value; }, Node::FLOAT => function (FloatValue $node) { return $node->value; }, Node::STRING => function (StringValue $node) { return json_encode($node->value); }, Node::BOOLEAN => function (BooleanValue $node) { return $node->value ? 'true' : 'false'; }, Node::ENUM => function (EnumValue $node) { return $node->value; }, Node::ARR => function (ArrayValue $node) { return '[' . self::join($node->values, ', ') . ']'; }, Node::OBJECT => function (ObjectValue $node) { return '{' . self::join($node->fields, ', ') . '}'; }, Node::OBJECT_FIELD => function (ObjectField $node) { return $node->name . ': ' . $node->value; }, Node::DIRECTIVE => function (Directive $node) { return self::join(['@' . $node->name, $node->value], ': '); }, Node::LIST_TYPE => function (ListType $node) { return '[' . $node->type . ']'; }, Node::NON_NULL_TYPE => function (NonNullType $node) { return $node->type . '!'; }))); }
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])); } }]; }
public function __invoke(ValidationContext $context) { return [Node::VARIABLE_DEFINITION => function (VariableDefinition $node) use($context) { $typeName = $this->getTypeASTName($node->type); $type = $context->getSchema()->getType($typeName); if (!$type instanceof InputType) { $variableName = $node->variable->name->value; return new Error(Messages::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)), [$node->type]); } }]; }
public function __invoke(ValidationContext $context) { return [Node::ARGUMENT => function (Argument $argAST) use($context) { $argDef = $context->getArgument(); if ($argDef) { $errors = DocumentValidator::isValidLiteralValue($argDef->getType(), $argAST->value); if (!empty($errors)) { $context->reportError(new Error(self::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value), $errors), [$argAST->value])); } } return Visitor::skipNode(); }]; }
public function __invoke(ValidationContext $context) { return [Node::INLINE_FRAGMENT => function (InlineFragment $node) use($context) { $type = $context->getType(); if ($node->typeCondition && $type && !Type::isCompositeType($type)) { $context->reportError(new Error(static::inlineFragmentOnNonCompositeErrorMessage($type), [$node->typeCondition])); } }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) use($context) { $type = $context->getType(); if ($type && !Type::isCompositeType($type)) { $context->reportError(new Error(static::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)), [$node->typeCondition])); } }]; }
public function testPrintsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = <<<'EOT' query queryName($foo: ComplexType, $site: Site = MOBILE) { whoever123is: node(id: [123, 456]) { id, ... on User @defer { field2 { id, alias: field1(first: 10, after: $foo) @if: $foo { id, ...frag } } } } } mutation likeStory { like(story: 123) @defer { story { id } } } fragment frag on Friend { foo(size: $size, bar: $b, obj: {key: "value"}) } { unnamed(truthy: true, falsey: false), query } EOT; $this->assertEquals($expected, $printed); }
public function __invoke(ValidationContext $context) { return [Node::VARIABLE_DEFINITION => function (VariableDefinition $varDefAST) use($context) { $name = $varDefAST->variable->name->value; $defaultValue = $varDefAST->defaultValue; $type = $context->getInputType(); if ($type instanceof NonNull && $defaultValue) { $context->reportError(new Error(static::defaultForNonNullArgMessage($name, $type, $type->getWrappedType()), [$defaultValue])); } if ($type && $defaultValue) { $errors = DocumentValidator::isValidLiteralValue($type, $defaultValue); if (!empty($errors)) { $context->reportError(new Error(static::badValueForDefaultArgMessage($name, $type, Printer::doPrint($defaultValue), $errors), [$defaultValue])); } } return Visitor::skipNode(); }, Node::SELECTION_SET => function () { return Visitor::skipNode(); }, Node::FRAGMENT_DEFINITION => function () { return Visitor::skipNode(); }]; }
/** * 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'); }
/** * 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]); }
/** * 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; }
public static function _inputValue() { if (!isset(self::$map['__InputValue'])) { self::$map['__InputValue'] = new ObjectType(['name' => '__InputValue', 'description' => 'Arguments provided to Fields or Directives and the input fields of an ' . 'InputObject are represented as Input Values which describe their type ' . 'and optionally a default value.', 'fields' => function () { return ['name' => ['type' => Type::nonNull(Type::string())], 'description' => ['type' => Type::string()], 'type' => ['type' => Type::nonNull(self::_type()), 'resolve' => function ($value) { return method_exists($value, 'getType') ? $value->getType() : $value->type; }], 'defaultValue' => ['type' => Type::string(), 'resolve' => function ($inputValue) { return $inputValue->defaultValue === null ? null : Printer::doPrint(AST::astFromValue($inputValue->defaultValue, $inputValue->getType())); }]]; }]); } return self::$map['__InputValue']; }
/** * @it prints kitchen sink */ public function testPrintsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = <<<'EOT' query queryName($foo: ComplexType, $site: Site = MOBILE) { whoever123is: node(id: [123, 456]) { id ... on User @defer { field2 { id alias: field1(first: 10, after: $foo) @include(if: $foo) { id ...frag } } } ... @skip(unless: $foo) { id } ... { id } } } mutation likeStory { like(story: 123) @defer { story { id } } } subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { storyLikeSubscribe(input: $input) { story { likers { count } likeSentence { text } } } } fragment frag on Friend { foo(size: $size, bar: $b, obj: {key: "value"}) } { unnamed(truthy: true, falsey: false) query } EOT; $this->assertEquals($expected, $printed); }
/** * 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]); }
public function testPrintsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = 'schema { query: QueryType mutation: MutationType } type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B scalar CustomScalar scalar AnnotatedScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObjectType { annotatedField: Type @onField } extend type Foo { seven(argument: [String]): Type } extend type Foo @onType {} type NoFields {} directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT '; $this->assertEquals($expected, $printed); }
/** * @it maintains type info during edit */ public function testMaintainsTypeInfoDuringEdit() { $visited = []; $typeInfo = new TypeInfo(TestCase::getDefaultSchema()); $ast = Parser::parse('{ human(id: 4) { name, pets }, alien }'); $editedAst = Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, ['enter' => function ($node) use($typeInfo, &$visited) { $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); $inputType = $typeInfo->getInputType(); $visited[] = ['enter', $node->kind, $node->kind === 'Name' ? $node->value : null, $parentType ? (string) $parentType : null, $type ? (string) $type : null, $inputType ? (string) $inputType : null]; // Make a query valid by adding missing selection sets. if ($node->kind === 'Field' && !$node->selectionSet && Type::isCompositeType(Type::getNamedType($type))) { return new FieldNode(['alias' => $node->alias, 'name' => $node->name, 'arguments' => $node->arguments, 'directives' => $node->directives, 'selectionSet' => new SelectionSetNode(['kind' => 'SelectionSet', 'selections' => [new FieldNode(['name' => new NameNode(['value' => '__typename'])])]])]); } }, 'leave' => function ($node) use($typeInfo, &$visited) { $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); $inputType = $typeInfo->getInputType(); $visited[] = ['leave', $node->kind, $node->kind === 'Name' ? $node->value : null, $parentType ? (string) $parentType : null, $type ? (string) $type : null, $inputType ? (string) $inputType : null]; }])); $this->assertEquals(Printer::doPrint(Parser::parse('{ human(id: 4) { name, pets }, alien }')), Printer::doPrint($ast)); $this->assertEquals(Printer::doPrint(Parser::parse('{ human(id: 4) { name, pets { __typename } }, alien { __typename } }')), Printer::doPrint($editedAst)); $this->assertEquals([['enter', 'Document', null, null, null, null], ['enter', 'OperationDefinition', null, null, 'QueryRoot', null], ['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['enter', 'Field', null, 'QueryRoot', 'Human', null], ['enter', 'Name', 'human', 'QueryRoot', 'Human', null], ['leave', 'Name', 'human', 'QueryRoot', 'Human', null], ['enter', 'Argument', null, 'QueryRoot', 'Human', 'ID'], ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['enter', 'IntValue', null, 'QueryRoot', 'Human', 'ID'], ['leave', 'IntValue', null, 'QueryRoot', 'Human', 'ID'], ['leave', 'Argument', null, 'QueryRoot', 'Human', 'ID'], ['enter', 'SelectionSet', null, 'Human', 'Human', null], ['enter', 'Field', null, 'Human', 'String', null], ['enter', 'Name', 'name', 'Human', 'String', null], ['leave', 'Name', 'name', 'Human', 'String', null], ['leave', 'Field', null, 'Human', 'String', null], ['enter', 'Field', null, 'Human', '[Pet]', null], ['enter', 'Name', 'pets', 'Human', '[Pet]', null], ['leave', 'Name', 'pets', 'Human', '[Pet]', null], ['enter', 'SelectionSet', null, 'Pet', '[Pet]', null], ['enter', 'Field', null, 'Pet', 'String!', null], ['enter', 'Name', '__typename', 'Pet', 'String!', null], ['leave', 'Name', '__typename', 'Pet', 'String!', null], ['leave', 'Field', null, 'Pet', 'String!', null], ['leave', 'SelectionSet', null, 'Pet', '[Pet]', null], ['leave', 'Field', null, 'Human', '[Pet]', null], ['leave', 'SelectionSet', null, 'Human', 'Human', null], ['leave', 'Field', null, 'QueryRoot', 'Human', null], ['enter', 'Field', null, 'QueryRoot', 'Alien', null], ['enter', 'Name', 'alien', 'QueryRoot', 'Alien', null], ['leave', 'Name', 'alien', 'QueryRoot', 'Alien', null], ['enter', 'SelectionSet', null, 'Alien', 'Alien', null], ['enter', 'Field', null, 'Alien', 'String!', null], ['enter', 'Name', '__typename', 'Alien', 'String!', null], ['leave', 'Name', '__typename', 'Alien', 'String!', null], ['leave', 'Field', null, 'Alien', 'String!', null], ['leave', 'SelectionSet', null, 'Alien', 'Alien', null], ['leave', 'Field', null, 'QueryRoot', 'Alien', null], ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], ['leave', 'Document', null, null, null, null]], $visited); }
private function sameValue($value1, $value2) { return !$value1 && !$value2 || Printer::doPrint($value1) === Printer::doPrint($value2); }
/** * 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]); }