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) { $operation = null; $visitedFragmentNames = []; $definedVariableNames = []; return ['visitSpreadFragments' => true, Node::OPERATION_DEFINITION => function (OperationDefinition $node, $key, $parent, $path, $ancestors) use(&$operation, &$visitedFragmentNames, &$definedVariableNames) { $operation = $node; $visitedFragmentNames = []; $definedVariableNames = []; }, Node::VARIABLE_DEFINITION => function (VariableDefinition $def) use(&$definedVariableNames) { $definedVariableNames[$def->variable->name->value] = true; }, Node::VARIABLE => function (Variable $variable, $key, $parent, $path, $ancestors) use(&$definedVariableNames, &$visitedFragmentNames, &$operation) { $varName = $variable->name->value; if (empty($definedVariableNames[$varName])) { $withinFragment = false; foreach ($ancestors as $ancestor) { if ($ancestor instanceof FragmentDefinition) { $withinFragment = true; break; } } if ($withinFragment && $operation && $operation->name) { return new Error(self::undefinedVarByOpMessage($varName, $operation->name->value), [$variable, $operation]); } return new Error(self::undefinedVarMessage($varName), [$variable]); } }, 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; }]; }
public function __invoke(ValidationContext $context) { $visitedFragmentNames = new \stdClass(); $variableDefs = []; $variableNameUsed = new \stdClass(); return ['visitSpreadFragments' => true, Node::OPERATION_DEFINITION => ['enter' => function () use(&$visitedFragmentNames, &$variableDefs, &$variableNameUsed) { $visitedFragmentNames = new \stdClass(); $variableDefs = []; $variableNameUsed = new \stdClass(); }, 'leave' => function () use(&$visitedFragmentNames, &$variableDefs, &$variableNameUsed) { $errors = []; foreach ($variableDefs as $def) { if (empty($variableNameUsed->{$def->variable->name->value})) { $errors[] = new Error(Messages::unusedVariableMessage($def->variable->name->value), [$def]); } } return !empty($errors) ? $errors : null; }], Node::VARIABLE_DEFINITION => function ($def) use(&$variableDefs) { $variableDefs[] = $def; return Visitor::skipNode(); }, Node::VARIABLE => function ($variable) use(&$variableNameUsed) { $variableNameUsed->{$variable->name->value} = true; }, Node::FRAGMENT_SPREAD => function ($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; }]; }
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]); } }]; }
public function __invoke(ValidationContext $context) { $this->operationDefs = []; $this->fragmentDefs = []; return [NodeKind::OPERATION_DEFINITION => function ($node) { $this->operationDefs[] = $node; return Visitor::skipNode(); }, NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) { $this->fragmentDefs[] = $def; return Visitor::skipNode(); }, NodeKind::DOCUMENT => ['leave' => function () use($context) { $fragmentNameUsed = []; foreach ($this->operationDefs as $operation) { foreach ($context->getRecursivelyReferencedFragments($operation) as $fragment) { $fragmentNameUsed[$fragment->name->value] = true; } } foreach ($this->fragmentDefs as $fragmentDef) { $fragName = $fragmentDef->name->value; if (empty($fragmentNameUsed[$fragName])) { $context->reportError(new Error(self::unusedFragMessage($fragName), [$fragmentDef])); } } }]]; }
private function gatherSpreads($node) { $spreadNodes = []; Visitor::visit($node, [Node::FRAGMENT_SPREAD => function (FragmentSpread $spread) use(&$spreadNodes) { $spreadNodes[] = $spread; }]); return $spreadNodes; }
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 [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) { $skip = function () { return Visitor::skipNode(); }; return [Node::OBJECT_TYPE_DEFINITION => $skip, Node::INTERFACE_TYPE_DEFINITION => $skip, Node::UNION_TYPE_DEFINITION => $skip, Node::INPUT_OBJECT_TYPE_DEFINITION => $skip, Node::NAMED_TYPE => function (NamedType $node, $key) use($context) { $typeName = $node->name->value; $type = $context->getSchema()->getType($typeName); if (!$type) { $context->reportError(new Error(self::unknownTypeMessage($typeName), [$node])); } }]; }
public function __invoke(ValidationContext $context) { $this->knownFragmentNames = []; return [Node::OPERATION_DEFINITION => function () { return Visitor::skipNode(); }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) use($context) { $fragmentName = $node->name->value; if (!empty($this->knownFragmentNames[$fragmentName])) { $context->reportError(new Error(self::duplicateFragmentNameMessage($fragmentName), [$this->knownFragmentNames[$fragmentName], $node->name])); } else { $this->knownFragmentNames[$fragmentName] = $node->name; } return Visitor::skipNode(); }]; }
public function __invoke(ValidationContext $context) { $this->knownArgNames = []; return [Node::FIELD => function () { $this->knownArgNames = []; }, Node::DIRECTIVE => function () { $this->knownArgNames = []; }, Node::ARGUMENT => function (Argument $node) use($context) { $argName = $node->name->value; if (!empty($this->knownArgNames[$argName])) { $context->reportError(new Error(self::duplicateArgMessage($argName), [$this->knownArgNames[$argName], $node->name])); } else { $this->knownArgNames[$argName] = $node->name; } return Visitor::skipNode(); }]; }
public function __invoke(ValidationContext $context) { $this->knownOperationNames = []; return [NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use($context) { $operationName = $node->name; if ($operationName) { if (!empty($this->knownOperationNames[$operationName->value])) { $context->reportError(new Error(self::duplicateOperationNameMessage($operationName->value), [$this->knownOperationNames[$operationName->value], $operationName])); } else { $this->knownOperationNames[$operationName->value] = $operationName; } } return Visitor::skipNode(); }, NodeKind::FRAGMENT_DEFINITION => function () { return Visitor::skipNode(); }]; }
public function __invoke(ValidationContext $context) { // Tracks already visited fragments to maintain O(N) and to ensure that cycles // are not redundantly reported. $this->visitedFrags = []; // Array of AST nodes used to produce meaningful errors $this->spreadPath = []; // Position in the spread path $this->spreadPathIndexByName = []; return [Node::OPERATION_DEFINITION => function () { return Visitor::skipNode(); }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) use($context) { if (!isset($this->visitedFrags[$node->name->value])) { $this->detectCycleRecursive($node, $context); } return Visitor::skipNode(); }]; }
public function __invoke(ValidationContext $context) { $this->context = $context; $this->variableDefs = new \ArrayObject(); $this->fieldNodeAndDefs = new \ArrayObject(); $complexity = 0; return $this->invokeIfNeeded($context, [NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use($context) { $this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs($context, $context->getParentType(), $selectionSet, null, $this->fieldNodeAndDefs); }, NodeKind::VARIABLE_DEFINITION => function ($def) { $this->variableDefs[] = $def; return Visitor::skipNode(); }, NodeKind::OPERATION_DEFINITION => ['leave' => function (OperationDefinitionNode $operationDefinition) use($context, &$complexity) { $complexity = $this->fieldComplexity($operationDefinition, $complexity); if ($complexity > $this->getMaxQueryComplexity()) { $context->reportError(new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity))); } }]]); }
public function __invoke(ValidationContext $context) { return [Node::FIELD => ['leave' => function (Field $fieldAST) use($context) { $fieldDef = $context->getFieldDef(); if (!$fieldDef) { return Visitor::skipNode(); } $errors = []; $argASTs = $fieldAST->arguments ?: []; $argASTMap = []; foreach ($argASTs as $argAST) { $argASTMap[$argAST->name->value] = $argASTs; } foreach ($fieldDef->args as $argDef) { $argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null; if (!$argAST && $argDef->getType() instanceof NonNull) { $errors[] = new Error(self::missingFieldArgMessage($fieldAST->name->value, $argDef->name, $argDef->getType()), [$fieldAST]); } } if (!empty($errors)) { return $errors; } }], Node::DIRECTIVE => ['leave' => function (Directive $directiveAST) use($context) { $directiveDef = $context->getDirective(); if (!$directiveDef) { return Visitor::skipNode(); } $errors = []; $argASTs = $directiveAST->arguments ?: []; $argASTMap = []; foreach ($argASTs as $argAST) { $argASTMap[$argAST->name->value] = $argASTs; } foreach ($directiveDef->args as $argDef) { $argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null; if (!$argAST && $argDef->getType() instanceof NonNull) { $errors[] = new Error(self::missingDirectiveArgMessage($directiveAST->name->value, $argDef->name, $argDef->getType()), [$directiveAST]); } } if (!empty($errors)) { return $errors; } }]]; }
public function __invoke(ValidationContext $context) { $this->knownNames = []; $this->knownNameStack = []; return [NodeKind::OBJECT => ['enter' => function () { $this->knownNameStack[] = $this->knownNames; $this->knownNames = []; }, 'leave' => function () { $this->knownNames = array_pop($this->knownNameStack); }], NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use($context) { $fieldName = $node->name->value; if (!empty($this->knownNames[$fieldName])) { $context->reportError(new Error(self::duplicateInputFieldMessage($fieldName), [$this->knownNames[$fieldName], $node->name])); } else { $this->knownNames[$fieldName] = $node->name; } return Visitor::skipNode(); }]; }
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(); }]; }
public function __invoke(ValidationContext $context) { return [NodeKind::FIELD => ['leave' => function (FieldNode $fieldNode) use($context) { $fieldDef = $context->getFieldDef(); if (!$fieldDef) { return Visitor::skipNode(); } $argNodes = $fieldNode->arguments ?: []; $argNodeMap = []; foreach ($argNodes as $argNode) { $argNodeMap[$argNode->name->value] = $argNodes; } foreach ($fieldDef->args as $argDef) { $argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null; if (!$argNode && $argDef->getType() instanceof NonNull) { $context->reportError(new Error(self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()), [$fieldNode])); } } }], NodeKind::DIRECTIVE => ['leave' => function (DirectiveNode $directiveNode) use($context) { $directiveDef = $context->getDirective(); if (!$directiveDef) { return Visitor::skipNode(); } $argNodes = $directiveNode->arguments ?: []; $argNodeMap = []; foreach ($argNodes as $argNode) { $argNodeMap[$argNode->name->value] = $argNodes; } foreach ($directiveDef->args as $argDef) { $argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null; if (!$argNode && $argDef->getType() instanceof NonNull) { $context->reportError(new Error(self::missingDirectiveArgMessage($directiveNode->name->value, $argDef->name, $argDef->getType()), [$directiveNode])); } } }]]; }
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; $varDefs = self::wrap('(', self::join($node->variableDefinitions, ', '), ')'); $directives = self::join($node->directives, ' '); $selectionSet = $node->selectionSet; // Anonymous queries with no directives or variable definitions can use // the query short form. return !$name && !$directives && !$varDefs && $op === 'query' ? $selectionSet : self::join([$op, self::join([$name, $varDefs]), $directives, $selectionSet], ' '); }, Node::VARIABLE_DEFINITION => function (VariableDefinition $node) { return $node->variable . ': ' . $node->type . self::wrap(' = ', $node->defaultValue); }, Node::SELECTION_SET => function (SelectionSet $node) { return self::block($node->selections); }, Node::FIELD => function (Field $node) { return self::join([self::wrap('', $node->alias, ': ') . $node->name . self::wrap('(', self::join($node->arguments, ', '), ')'), self::join($node->directives, ' '), $node->selectionSet], ' '); }, Node::ARGUMENT => function (Argument $node) { return $node->name . ': ' . $node->value; }, Node::FRAGMENT_SPREAD => function (FragmentSpread $node) { return '...' . $node->name . self::wrap(' ', self::join($node->directives, ' ')); }, Node::INLINE_FRAGMENT => function (InlineFragment $node) { return self::join(["...", self::wrap('on ', $node->typeCondition), self::join($node->directives, ' '), $node->selectionSet], ' '); }, Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) { return "fragment {$node->name} on {$node->typeCondition} " . self::wrap('', 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::LST => function (ListValue $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 '@' . $node->name . self::wrap('(', self::join($node->arguments, ', '), ')'); }, Node::NAMED_TYPE => function (NamedType $node) { return $node->name; }, Node::LIST_TYPE => function (ListType $node) { return '[' . $node->type . ']'; }, Node::NON_NULL_TYPE => function (NonNullType $node) { return $node->type . '!'; }, Node::SCHEMA_DEFINITION => function (SchemaDefinition $def) { return self::join(['schema', self::join($def->directives, ' '), self::block($def->operationTypes)], ' '); }, Node::OPERATION_TYPE_DEFINITION => function (OperationTypeDefinition $def) { return $def->operation . ': ' . $def->type; }, Node::SCALAR_TYPE_DEFINITION => function (ScalarTypeDefinition $def) { return self::join(['scalar', $def->name, self::join($def->directives, ' ')], ' '); }, Node::OBJECT_TYPE_DEFINITION => function (ObjectTypeDefinition $def) { return self::join(['type', $def->name, self::wrap('implements ', self::join($def->interfaces, ', ')), self::join($def->directives, ' '), self::block($def->fields)], ' '); }, Node::FIELD_DEFINITION => function (FieldDefinition $def) { return $def->name . self::wrap('(', self::join($def->arguments, ', '), ')') . ': ' . $def->type . self::wrap(' ', self::join($def->directives, ' ')); }, Node::INPUT_VALUE_DEFINITION => function (InputValueDefinition $def) { return self::join([$def->name . ': ' . $def->type, self::wrap('= ', $def->defaultValue), self::join($def->directives, ' ')], ' '); }, Node::INTERFACE_TYPE_DEFINITION => function (InterfaceTypeDefinition $def) { return self::join(['interface', $def->name, self::join($def->directives, ' '), self::block($def->fields)], ' '); }, Node::UNION_TYPE_DEFINITION => function (UnionTypeDefinition $def) { return self::join(['union', $def->name, self::join($def->directives, ' '), '= ' . self::join($def->types, ' | ')], ' '); }, Node::ENUM_TYPE_DEFINITION => function (EnumTypeDefinition $def) { return self::join(['enum', $def->name, self::join($def->directives, ' '), self::block($def->values)], ' '); }, Node::ENUM_VALUE_DEFINITION => function (EnumValueDefinition $def) { return self::join([$def->name, self::join($def->directives, ' ')], ' '); }, Node::INPUT_OBJECT_TYPE_DEFINITION => function (InputObjectTypeDefinition $def) { return self::join(['input', $def->name, self::join($def->directives, ' '), self::block($def->fields)], ' '); }, Node::TYPE_EXTENSION_DEFINITION => function (TypeExtensionDefinition $def) { return "extend {$def->definition}"; }, Node::DIRECTIVE_DEFINITION => function (DirectiveDefinition $def) { return 'directive @' . $def->name . self::wrap('(', self::join($def->arguments, ', '), ')') . ' on ' . self::join($def->locations, ' | '); }))); }
public function testVisitsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $visited = []; Visitor::visit($ast, ['enter' => function (Node $node, $key, $parent) use(&$visited) { $r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; $visited[] = $r; }, 'leave' => function (Node $node, $key, $parent) use(&$visited) { $r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; $visited[] = $r; }]); $expected = [['enter', 'Document', null, null], ['enter', 'OperationDefinition', 0, null], ['enter', 'Name', 'name', 'OperationDefinition'], ['leave', 'Name', 'name', 'OperationDefinition'], ['enter', 'VariableDefinition', 0, null], ['enter', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type', 'VariableDefinition'], ['leave', 'VariableDefinition', 0, null], ['enter', 'VariableDefinition', 1, null], ['enter', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'EnumValue', 'defaultValue', 'VariableDefinition'], ['leave', 'EnumValue', 'defaultValue', 'VariableDefinition'], ['leave', 'VariableDefinition', 1, null], ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], ['enter', 'Field', 0, null], ['enter', 'Name', 'alias', 'Field'], ['leave', 'Name', 'alias', 'Field'], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'ListValue', 'value', 'Argument'], ['enter', 'IntValue', 0, null], ['leave', 'IntValue', 0, null], ['enter', 'IntValue', 1, null], ['leave', 'IntValue', 1, null], ['leave', 'ListValue', 'value', 'Argument'], ['leave', 'Argument', 0, null], ['enter', 'SelectionSet', 'selectionSet', 'Field'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, null], ['enter', 'InlineFragment', 1, null], ['enter', 'NamedType', 'typeCondition', 'InlineFragment'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'typeCondition', 'InlineFragment'], ['enter', 'Directive', 0, null], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['leave', 'Directive', 0, null], ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selectionSet', 'Field'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, null], ['enter', 'Field', 1, null], ['enter', 'Name', 'alias', 'Field'], ['leave', 'Name', 'alias', 'Field'], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'IntValue', 'value', 'Argument'], ['leave', 'IntValue', 'value', 'Argument'], ['leave', 'Argument', 0, null], ['enter', 'Argument', 1, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 1, null], ['enter', 'Directive', 0, null], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['enter', 'Argument', 0, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 0, null], ['leave', 'Directive', 0, null], ['enter', 'SelectionSet', 'selectionSet', 'Field'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, null], ['enter', 'FragmentSpread', 1, null], ['enter', 'Name', 'name', 'FragmentSpread'], ['leave', 'Name', 'name', 'FragmentSpread'], ['leave', 'FragmentSpread', 1, null], ['leave', 'SelectionSet', 'selectionSet', 'Field'], ['leave', 'Field', 1, null], ['leave', 'SelectionSet', 'selectionSet', 'Field'], ['leave', 'Field', 0, null], ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], ['leave', 'InlineFragment', 1, null], ['leave', 'SelectionSet', 'selectionSet', 'Field'], ['leave', 'Field', 0, null], ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], ['leave', 'OperationDefinition', 0, null], ['enter', 'OperationDefinition', 1, null], ['enter', 'Name', 'name', 'OperationDefinition'], ['leave', 'Name', 'name', 'OperationDefinition'], ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'IntValue', 'value', 'Argument'], ['leave', 'IntValue', 'value', 'Argument'], ['leave', 'Argument', 0, null], ['enter', 'Directive', 0, null], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['leave', 'Directive', 0, null], ['enter', 'SelectionSet', 'selectionSet', 'Field'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selectionSet', 'Field'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, null], ['leave', 'SelectionSet', 'selectionSet', 'Field'], ['leave', 'Field', 0, null], ['leave', 'SelectionSet', 'selectionSet', 'Field'], ['leave', 'Field', 0, null], ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], ['leave', 'OperationDefinition', 1, null], ['enter', 'FragmentDefinition', 2, null], ['enter', 'Name', 'name', 'FragmentDefinition'], ['leave', 'Name', 'name', 'FragmentDefinition'], ['enter', 'NamedType', 'typeCondition', 'FragmentDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'typeCondition', 'FragmentDefinition'], ['enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 0, null], ['enter', 'Argument', 1, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 1, null], ['enter', 'Argument', 2, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'ObjectValue', 'value', 'Argument'], ['enter', 'ObjectField', 0, null], ['enter', 'Name', 'name', 'ObjectField'], ['leave', 'Name', 'name', 'ObjectField'], ['enter', 'StringValue', 'value', 'ObjectField'], ['leave', 'StringValue', 'value', 'ObjectField'], ['leave', 'ObjectField', 0, null], ['leave', 'ObjectValue', 'value', 'Argument'], ['leave', 'Argument', 2, null], ['leave', 'Field', 0, null], ['leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], ['leave', 'FragmentDefinition', 2, null], ['enter', 'OperationDefinition', 3, null], ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], ['enter', 'Field', 0, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'BooleanValue', 'value', 'Argument'], ['leave', 'BooleanValue', 'value', 'Argument'], ['leave', 'Argument', 0, null], ['enter', 'Argument', 1, null], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'BooleanValue', 'value', 'Argument'], ['leave', 'BooleanValue', 'value', 'Argument'], ['leave', 'Argument', 1, null], ['leave', 'Field', 0, null], ['enter', 'Field', 1, null], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 1, null], ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], ['leave', 'OperationDefinition', 3, null], ['leave', 'Document', null, null]]; $this->assertEquals($expected, $visited); }
/** * 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; }
/** * @param HasSelectionSet $node * @return array List of ['node' => Variable, 'type' => ?InputObjectType] */ function getVariableUsages(HasSelectionSet $node) { $usages = isset($this->variableUsages[$node]) ? $this->variableUsages[$node] : null; if (!$usages) { $newUsages = []; $typeInfo = new TypeInfo($this->schema); Visitor::visit($node, Visitor::visitWithTypeInfo($typeInfo, [Node::VARIABLE_DEFINITION => function () { return false; }, Node::VARIABLE => function (Variable $variable) use(&$newUsages, $typeInfo) { $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()]; }])); $usages = $newUsages; $this->variableUsages[$node] = $usages; } return $usages; }
/** * This uses a specialized visitor which runs multiple visitors in parallel, * while maintaining the visitor skip and break API. * * @param Schema $schema * @param TypeInfo $typeInfo * @param DocumentNode $documentNode * @param array $rules * @return array */ public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules) { $context = new ValidationContext($schema, $documentNode, $typeInfo); $visitors = []; foreach ($rules as $rule) { $visitors[] = $rule($context); } Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors))); return $context->getErrors(); }
/** * @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); }