From 2675b650955b7a0b6cac5db7c48439a6864f22c8 Mon Sep 17 00:00:00 2001 From: vladar Date: Fri, 21 Oct 2016 16:39:57 +0700 Subject: [PATCH] Moved all error-related classes to separate namespace; fixed related broken tests --- src/Error.php | 201 +---------------- src/Error/Error.php | 208 ++++++++++++++++++ src/Error/FormattedError.php | 26 +++ src/Error/SyntaxError.php | 54 +++++ src/Executor/ExecutionContext.php | 2 +- src/Executor/ExecutionResult.php | 4 +- src/Executor/Executor.php | 2 +- src/Executor/Values.php | 2 +- src/FormattedError.php | 30 +-- src/GraphQL.php | 1 + src/Language/Lexer.php | 2 +- src/Language/Parser.php | 2 +- src/SyntaxError.php | 49 +---- src/Type/Definition/Config.php | 4 +- src/Type/SchemaValidator.php | 2 +- src/Validator/DocumentValidator.php | 2 +- .../Rules/ArgumentsOfCorrectType.php | 2 +- .../Rules/DefaultValuesOfCorrectType.php | 2 +- src/Validator/Rules/FieldsOnCorrectType.php | 2 +- .../Rules/FragmentsOnCompositeTypes.php | 2 +- src/Validator/Rules/KnownArgumentNames.php | 2 +- src/Validator/Rules/KnownDirectives.php | 2 +- src/Validator/Rules/KnownFragmentNames.php | 2 +- src/Validator/Rules/KnownTypeNames.php | 2 +- .../Rules/LoneAnonymousOperation.php | 2 +- src/Validator/Rules/NoFragmentCycles.php | 2 +- src/Validator/Rules/NoUndefinedVariables.php | 2 +- src/Validator/Rules/NoUnusedFragments.php | 2 +- src/Validator/Rules/NoUnusedVariables.php | 2 +- .../Rules/OverlappingFieldsCanBeMerged.php | 2 +- .../Rules/PossibleFragmentSpreads.php | 2 +- .../Rules/ProvidedNonNullArguments.php | 2 +- src/Validator/Rules/QueryComplexity.php | 2 +- src/Validator/Rules/QueryDepth.php | 2 +- src/Validator/Rules/ScalarLeafs.php | 2 +- src/Validator/Rules/UniqueArgumentNames.php | 2 +- src/Validator/Rules/UniqueFragmentNames.php | 2 +- src/Validator/Rules/UniqueInputFieldNames.php | 2 +- src/Validator/Rules/UniqueOperationNames.php | 2 +- src/Validator/Rules/UniqueVariableNames.php | 2 +- .../Rules/VariablesAreInputTypes.php | 2 +- .../Rules/VariablesInAllowedPosition.php | 2 +- src/Validator/ValidationContext.php | 2 +- tests/ErrorTest.php | 2 +- tests/Executor/AbstractTest.php | 2 +- tests/Executor/ExecutorTest.php | 4 +- tests/Executor/ListsTest.php | 4 +- tests/Executor/MutationsTest.php | 2 +- tests/Executor/NonNullTest.php | 4 +- tests/Executor/VariablesTest.php | 4 +- tests/Language/LexerTest.php | 2 +- tests/Language/ParserTest.php | 8 +- tests/Language/SchemaParserTest.php | 2 +- tests/Type/EnumTypeTest.php | 2 +- tests/Type/IntrospectionTest.php | 2 +- tests/Validator/AbstractQuerySecurityTest.php | 4 +- .../Validator/ArgumentsOfCorrectTypeTest.php | 2 +- .../DefaultValuesOfCorrectTypeTest.php | 2 +- tests/Validator/FieldsOnCorrectTypeTest.php | 2 +- .../FragmentsOnCompositeTypesTest.php | 2 +- tests/Validator/KnownArgumentNamesTest.php | 2 +- tests/Validator/KnownDirectivesTest.php | 2 +- tests/Validator/KnownFragmentNamesTest.php | 2 +- tests/Validator/KnownTypeNamesTest.php | 2 +- .../Validator/LoneAnonymousOperationTest.php | 2 +- tests/Validator/NoFragmentCyclesTest.php | 2 +- tests/Validator/NoUndefinedVariablesTest.php | 2 +- tests/Validator/NoUnusedFragmentsTest.php | 2 +- tests/Validator/NoUnusedVariablesTest.php | 2 +- .../OverlappingFieldsCanBeMergedTest.php | 2 +- .../Validator/PossibleFragmentSpreadsTest.php | 2 +- .../ProvidedNonNullArgumentsTest.php | 2 +- tests/Validator/ScalarLeafsTest.php | 2 +- tests/Validator/TestCase.php | 2 +- tests/Validator/UniqueArgumentNamesTest.php | 2 +- tests/Validator/UniqueFragmentNamesTest.php | 2 +- tests/Validator/UniqueInputFieldNamesTest.php | 2 +- tests/Validator/UniqueOperationNamesTest.php | 2 +- tests/Validator/UniqueVariableNamesTest.php | 2 +- .../Validator/VariablesAreInputTypesTest.php | 2 +- .../VariablesInAllowedPositionTest.php | 2 +- 81 files changed, 399 insertions(+), 338 deletions(-) create mode 100644 src/Error/Error.php create mode 100644 src/Error/FormattedError.php create mode 100644 src/Error/SyntaxError.php diff --git a/src/Error.php b/src/Error.php index 6b46c3b..28a46a6 100644 --- a/src/Error.php +++ b/src/Error.php @@ -1,207 +1,18 @@ x, column => y] locations within the source GraphQL document - * which correspond to this error. - * - * Errors during validation often contain multiple locations, for example to - * point out two things with the same name. Errors during execution include a - * single location, the field which produced the error. - * - * @var SourceLocation[] - */ - private $locations; - - /** - * An array describing the JSON-path into the execution response which - * corresponds to this error. Only included for errors during execution. - * - * @var array - */ - public $path; - - /** - * An array of GraphQL AST Nodes corresponding to this error. - * - * @var array - */ - public $nodes; - - /** - * The source GraphQL document corresponding to this error. - * - * @var Source|null - */ - private $source; - - /** - * @var array - */ - private $positions; - - /** - * Given an arbitrary Error, presumably thrown while attempting to execute a - * GraphQL operation, produce a new GraphQLError aware of the location in the - * document responsible for the original Error. - * - * @param $error - * @param array|null $nodes - * @param array|null $path - * @return Error - */ - public static function createLocatedError($error, $nodes = null, $path = null) - { - if ($error instanceof self) { - return $error; - } - - if ($error instanceof \Exception) { - $message = $error->getMessage(); - $previous = $error; - } else { - $message = (string) $error; - $previous = null; - } - - return new Error($message, $nodes, null, null, $path, $previous); - } - - /** - * @param Error $error - * @return array - */ - public static function formatError(Error $error) - { - return $error->toSerializableArray(); - } - - /** - * @param string $message - * @param array|null $nodes - * @param Source $source - * @param array|null $positions - * @param array|null $path - * @param \Exception $previous - */ - public function __construct($message, $nodes = null, Source $source = null, $positions = null, $path = null, \Exception $previous = null) - { - parent::__construct($message, 0, $previous); - - if ($nodes instanceof \Traversable) { - $nodes = iterator_to_array($nodes); - } - - $this->nodes = $nodes; - $this->source = $source; - $this->positions = $positions; - $this->path = $path; - } - - /** - * @return Source|null - */ - public function getSource() - { - if (null === $this->source) { - if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) { - $this->source = $this->nodes[0]->loc->source; - } - } - return $this->source; - } - - /** - * @return array - */ - public function getPositions() - { - if (null === $this->positions) { - if (!empty($this->nodes)) { - $positions = array_map(function($node) { return isset($node->loc) ? $node->loc->start : null; }, $this->nodes); - $this->positions = array_filter($positions, function($p) {return $p !== null;}); - } - } - return $this->positions; - } - - /** - * @return SourceLocation[] - */ - public function getLocations() - { - if (null === $this->locations) { - $positions = $this->getPositions(); - $source = $this->getSource(); - - if ($positions && $source) { - $this->locations = array_map(function ($pos) use ($source) { - return $source->getLocation($pos); - }, $positions); - } else { - $this->locations = []; - } - } - - return $this->locations; - } - - /** - * Returns array representation of error suitable for serialization - * - * @return array - */ - public function toSerializableArray() - { - $arr = [ - 'message' => $this->getMessage(), - ]; - - $locations = Utils::map($this->getLocations(), function(SourceLocation $loc) { - return $loc->toSerializableArray(); - }); - - if (!empty($locations)) { - $arr['locations'] = $locations; - } - if (!empty($this->path)) { - $arr['path'] = $this->path; - } - - return $arr; - } - - /** - * Specify data which should be serialized to JSON - * @link http://php.net/manual/en/jsonserializable.jsonserialize.php - * @return mixed data which can be serialized by json_encode, - * which is a value of any type other than a resource. - * @since 5.4.0 - */ - function jsonSerialize() - { - return $this->toSerializableArray(); - } } diff --git a/src/Error/Error.php b/src/Error/Error.php new file mode 100644 index 0000000..5976e1c --- /dev/null +++ b/src/Error/Error.php @@ -0,0 +1,208 @@ + x, column => y] locations within the source GraphQL document + * which correspond to this error. + * + * Errors during validation often contain multiple locations, for example to + * point out two things with the same name. Errors during execution include a + * single location, the field which produced the error. + * + * @var SourceLocation[] + */ + private $locations; + + /** + * An array describing the JSON-path into the execution response which + * corresponds to this error. Only included for errors during execution. + * + * @var array + */ + public $path; + + /** + * An array of GraphQL AST Nodes corresponding to this error. + * + * @var array + */ + public $nodes; + + /** + * The source GraphQL document corresponding to this error. + * + * @var Source|null + */ + private $source; + + /** + * @var array + */ + private $positions; + + /** + * Given an arbitrary Error, presumably thrown while attempting to execute a + * GraphQL operation, produce a new GraphQLError aware of the location in the + * document responsible for the original Error. + * + * @param $error + * @param array|null $nodes + * @param array|null $path + * @return Error + */ + public static function createLocatedError($error, $nodes = null, $path = null) + { + if ($error instanceof self) { + return $error; + } + + if ($error instanceof \Exception) { + $message = $error->getMessage(); + $previous = $error; + } else { + $message = (string) $error; + $previous = null; + } + + return new static($message, $nodes, null, null, $path, $previous); + } + + /** + * @param Error $error + * @return array + */ + public static function formatError(Error $error) + { + return $error->toSerializableArray(); + } + + /** + * @param string $message + * @param array|null $nodes + * @param Source $source + * @param array|null $positions + * @param array|null $path + * @param \Exception $previous + */ + public function __construct($message, $nodes = null, Source $source = null, $positions = null, $path = null, \Exception $previous = null) + { + parent::__construct($message, 0, $previous); + + if ($nodes instanceof \Traversable) { + $nodes = iterator_to_array($nodes); + } + + $this->nodes = $nodes; + $this->source = $source; + $this->positions = $positions; + $this->path = $path; + } + + /** + * @return Source|null + */ + public function getSource() + { + if (null === $this->source) { + if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) { + $this->source = $this->nodes[0]->loc->source; + } + } + return $this->source; + } + + /** + * @return array + */ + public function getPositions() + { + if (null === $this->positions) { + if (!empty($this->nodes)) { + $positions = array_map(function($node) { return isset($node->loc) ? $node->loc->start : null; }, $this->nodes); + $this->positions = array_filter($positions, function($p) {return $p !== null;}); + } + } + return $this->positions; + } + + /** + * @return SourceLocation[] + */ + public function getLocations() + { + if (null === $this->locations) { + $positions = $this->getPositions(); + $source = $this->getSource(); + + if ($positions && $source) { + $this->locations = array_map(function ($pos) use ($source) { + return $source->getLocation($pos); + }, $positions); + } else { + $this->locations = []; + } + } + + return $this->locations; + } + + /** + * Returns array representation of error suitable for serialization + * + * @return array + */ + public function toSerializableArray() + { + $arr = [ + 'message' => $this->getMessage(), + ]; + + $locations = Utils::map($this->getLocations(), function(SourceLocation $loc) { + return $loc->toSerializableArray(); + }); + + if (!empty($locations)) { + $arr['locations'] = $locations; + } + if (!empty($this->path)) { + $arr['path'] = $this->path; + } + + return $arr; + } + + /** + * Specify data which should be serialized to JSON + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource. + * @since 5.4.0 + */ + function jsonSerialize() + { + return $this->toSerializableArray(); + } +} diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php new file mode 100644 index 0000000..7e35fa3 --- /dev/null +++ b/src/Error/FormattedError.php @@ -0,0 +1,26 @@ + $error + ]; + + if (!empty($locations)) { + $formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations); + } + + return $formatted; + } +} diff --git a/src/Error/SyntaxError.php b/src/Error/SyntaxError.php new file mode 100644 index 0000000..9fc2820 --- /dev/null +++ b/src/Error/SyntaxError.php @@ -0,0 +1,54 @@ +getLocation($position); + $syntaxError = + "Syntax Error {$source->name} ({$location->line}:{$location->column}) $description\n\n" . + self::highlightSourceAtLocation($source, $location); + + parent::__construct($syntaxError, null, $source, [$position]); + } + + /** + * @param Source $source + * @param SourceLocation $location + * @return string + */ + public static function highlightSourceAtLocation(Source $source, SourceLocation $location) + { + $line = $location->line; + $prevLineNum = (string)($line - 1); + $lineNum = (string)$line; + $nextLineNum = (string)($line + 1); + $padLen = mb_strlen($nextLineNum, 'UTF-8'); + + $unicodeChars = json_decode('"\u2028\u2029"'); // Quick hack to get js-compatible representation of these chars + $lines = preg_split('/\r\n|[\n\r' . $unicodeChars . ']/su', $source->body); + + $lpad = function($len, $str) { + return str_pad($str, $len - mb_strlen($str, 'UTF-8') + 1, ' ', STR_PAD_LEFT); + }; + + return + ($line >= 2 ? $lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2] . "\n" : '') . + ($lpad($padLen, $lineNum) . ': ' . $lines[$line - 1] . "\n") . + (str_repeat(' ', 1 + $padLen + $location->column) . "^\n") . + ($line < count($lines) ? $lpad($padLen, $nextLineNum) . ': ' . $lines[$line] . "\n" : ''); + } +} diff --git a/src/Executor/ExecutionContext.php b/src/Executor/ExecutionContext.php index 0566582..7aa3a0d 100644 --- a/src/Executor/ExecutionContext.php +++ b/src/Executor/ExecutionContext.php @@ -1,7 +1,7 @@ errors)) { - $result['errors'] = array_map(['GraphQL\Error', 'formatError'], $this->errors); + $result['errors'] = array_map(['GraphQL\Error\Error', 'formatError'], $this->errors); } if (!empty($this->extensions)) { diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 48632af..e6236c0 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -1,7 +1,7 @@ $error - ]; - - if (!empty($locations)) { - $formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations); - } - - return $formatted; - } } diff --git a/src/GraphQL.php b/src/GraphQL.php index ea2e291..5ae8733 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -1,6 +1,7 @@ getLocation($position); - $syntaxError = - "Syntax Error {$source->name} ({$location->line}:{$location->column}) $description\n\n" . - self::highlightSourceAtLocation($source, $location); - - parent::__construct($syntaxError, null, $source, [$position]); - } - - public static function highlightSourceAtLocation(Source $source, SourceLocation $location) - { - $line = $location->line; - $prevLineNum = (string)($line - 1); - $lineNum = (string)$line; - $nextLineNum = (string)($line + 1); - $padLen = mb_strlen($nextLineNum, 'UTF-8'); - - $unicodeChars = json_decode('"\u2028\u2029"'); // Quick hack to get js-compatible representation of these chars - $lines = preg_split('/\r\n|[\n\r' . $unicodeChars . ']/su', $source->body); - - $lpad = function($len, $str) { - return str_pad($str, $len - mb_strlen($str, 'UTF-8') + 1, ' ', STR_PAD_LEFT); - }; - - return - ($line >= 2 ? $lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2] . "\n" : '') . - ($lpad($padLen, $lineNum) . ': ' . $lines[$line - 1] . "\n") . - (str_repeat(' ', 1 + $padLen + $location->column) . "^\n") . - ($line < count($lines) ? $lpad($padLen, $nextLineNum) . ': ' . $lines[$line] . "\n" : ''); - } } diff --git a/src/Type/Definition/Config.php b/src/Type/Definition/Config.php index 5dfa56b..dde911e 100644 --- a/src/Type/Definition/Config.php +++ b/src/Type/Definition/Config.php @@ -178,9 +178,9 @@ class Config if ($def->flags & self::KEY_AS_NAME) { $arrValue += ['name' => $arrKey]; } - self::validateMap($typeName, $arrValue, $def->definition, "$pathStr: $arrKey"); + self::validateMap($typeName, $arrValue, $def->definition, "$pathStr:$arrKey"); } else { - self::validateEntry($typeName, $arrKey, $arrValue, $def->definition, "$pathStr: $arrKey"); + self::validateEntry($typeName, $arrKey, $arrValue, $def->definition, "$pathStr:$arrKey"); } } } else { diff --git a/src/Type/SchemaValidator.php b/src/Type/SchemaValidator.php index a1a45a1..af118b2 100644 --- a/src/Type/SchemaValidator.php +++ b/src/Type/SchemaValidator.php @@ -1,7 +1,7 @@ setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:10) Unexpected Name "on"'); + $this->setExpectedException('GraphQL\Error\SyntaxError', 'Syntax Error GraphQL (1:10) Unexpected Name "on"'); Parser::parse('fragment on on on { on }'); } @@ -105,7 +105,7 @@ fragment MissingOn Type */ public function testDoesNotAcceptFragmentSpreadOfOn() { - $this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:9) Expected Name, found }'); + $this->setExpectedException('GraphQL\Error\SyntaxError', 'Syntax Error GraphQL (1:9) Expected Name, found }'); Parser::parse('{ ...on }'); } @@ -114,7 +114,7 @@ fragment MissingOn Type */ public function testDoesNotAllowNullAsValue() { - $this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:39) Unexpected Name "null"'); + $this->setExpectedException('GraphQL\Error\SyntaxError', 'Syntax Error GraphQL (1:39) Unexpected Name "null"'); Parser::parse('{ fieldWithNullableStringInput(input: null) }'); } diff --git a/tests/Language/SchemaParserTest.php b/tests/Language/SchemaParserTest.php index 4a5b7d1..2f64594 100644 --- a/tests/Language/SchemaParserTest.php +++ b/tests/Language/SchemaParserTest.php @@ -593,7 +593,7 @@ input Hello { input Hello { world(foo: Int): String }'; - $this->setExpectedException('GraphQL\SyntaxError'); + $this->setExpectedException('GraphQL\Error\SyntaxError'); Parser::parse($body); } diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index ef32c1c..587f195 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -1,7 +1,7 @@ getRule($max)] ); - $this->assertEquals($expectedErrors, array_map(['GraphQL\Error', 'formatError'], $errors), $queryString); + $this->assertEquals($expectedErrors, array_map(['GraphQL\Error\Error', 'formatError'], $errors), $queryString); return $errors; } diff --git a/tests/Validator/ArgumentsOfCorrectTypeTest.php b/tests/Validator/ArgumentsOfCorrectTypeTest.php index f53d14a..e180d71 100644 --- a/tests/Validator/ArgumentsOfCorrectTypeTest.php +++ b/tests/Validator/ArgumentsOfCorrectTypeTest.php @@ -1,7 +1,7 @@ assertNotEmpty($errors, 'GraphQL should not validate'); - $this->assertEquals($expectedErrors, array_map(['GraphQL\Error', 'formatError'], $errors)); + $this->assertEquals($expectedErrors, array_map(['GraphQL\Error\Error', 'formatError'], $errors)); return $errors; } diff --git a/tests/Validator/UniqueArgumentNamesTest.php b/tests/Validator/UniqueArgumentNamesTest.php index 75fc607..bf13064 100644 --- a/tests/Validator/UniqueArgumentNamesTest.php +++ b/tests/Validator/UniqueArgumentNamesTest.php @@ -1,7 +1,7 @@