mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-06 07:49:24 +03:00
New: printError()
Lifted from / inspired by a similar change in graphql/graphql-js#722, this creates a new function `printError()` (and uses it as the implementation for `GraphQLError#toString()`) which prints location information in the context of an error. This is moved from the syntax error where it used to be hard-coded, so it may now be used to format validation errors, value coercion errors, or any other error which may be associated with a location. ref: graphql/graphql-js BREAKING CHANGE: The SyntaxError message does not contain the codeframe anymore and only the message, (string) $error will print the codeframe.
This commit is contained in:
parent
f661f38215
commit
15374a31dd
@ -324,4 +324,12 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
{
|
||||
return $this->toSerializableArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return FormattedError::printError($this);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\WrappingType;
|
||||
@ -27,6 +28,97 @@ class FormattedError
|
||||
self::$internalErrorMessage = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a GraphQLError to a string, representing useful location information
|
||||
* about the error's position in the source.
|
||||
*
|
||||
* @param Error $error
|
||||
* @return string
|
||||
*/
|
||||
public static function printError(Error $error)
|
||||
{
|
||||
$source = $error->getSource();
|
||||
$locations = $error->getLocations();
|
||||
|
||||
$message = $error->getMessage();
|
||||
|
||||
foreach($locations as $location) {
|
||||
$message .= $source
|
||||
? self::highlightSourceAtLocation($source, $location)
|
||||
: " ({$location->line}:{$location->column})";
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a helpful description of the location of the error in the GraphQL
|
||||
* Source document.
|
||||
*
|
||||
* @param Source $source
|
||||
* @param SourceLocation $location
|
||||
* @return string
|
||||
*/
|
||||
private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
|
||||
{
|
||||
$line = $location->line;
|
||||
$lineOffset = $source->locationOffset->line - 1;
|
||||
$columnOffset = self::getColumnOffset($source, $location);
|
||||
$contextLine = $line + $lineOffset;
|
||||
$contextColumn = $location->column + $columnOffset;
|
||||
$prevLineNum = (string) ($contextLine - 1);
|
||||
$lineNum = (string) $contextLine;
|
||||
$nextLineNum = (string) ($contextLine + 1);
|
||||
$padLen = strlen($nextLineNum);
|
||||
$lines = preg_split('/\r\n|[\n\r]/', $source->body);
|
||||
|
||||
$lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
|
||||
|
||||
return (
|
||||
"\n\n{$source->name} ($contextLine:$contextColumn)\n" .
|
||||
($line >= 2
|
||||
? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2] . "\n")
|
||||
: ''
|
||||
) .
|
||||
self::lpad($padLen, $lineNum) .
|
||||
': ' .
|
||||
$lines[$line - 1] .
|
||||
"\n" .
|
||||
self::whitespace(2 + $padLen + $contextColumn - 1) .
|
||||
"^\n" .
|
||||
($line < count($lines)
|
||||
? (self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] . "\n")
|
||||
: ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Source $source
|
||||
* @param SourceLocation $location
|
||||
* @return int
|
||||
*/
|
||||
private static function getColumnOffset(Source $source, SourceLocation $location)
|
||||
{
|
||||
return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $len
|
||||
* @return string
|
||||
*/
|
||||
private static function whitespace($len) {
|
||||
return str_repeat(' ', $len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $len
|
||||
* @return string
|
||||
*/
|
||||
private static function lpad($len, $str) {
|
||||
return self::whitespace($len - mb_strlen($str)) . $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard GraphQL error formatter. Converts any exception to array
|
||||
* conforming to GraphQL spec.
|
||||
|
@ -2,7 +2,6 @@
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
|
||||
class SyntaxError extends Error
|
||||
{
|
||||
@ -13,59 +12,11 @@ class SyntaxError extends Error
|
||||
*/
|
||||
public function __construct(Source $source, $position, $description)
|
||||
{
|
||||
$location = $source->getLocation($position);
|
||||
$line = $location->line + $source->locationOffset->line - 1;
|
||||
$columnOffset = self::getColumnOffset($source, $location);
|
||||
$column = $location->column + $columnOffset;
|
||||
|
||||
$syntaxError =
|
||||
"Syntax Error {$source->name} ({$line}:{$column}) $description\n" .
|
||||
"\n".
|
||||
self::highlightSourceAtLocation($source, $location);
|
||||
|
||||
parent::__construct($syntaxError, null, $source, [$position]);
|
||||
parent::__construct(
|
||||
"Syntax Error: $description",
|
||||
null,
|
||||
$source,
|
||||
[$position]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Source $source
|
||||
* @param SourceLocation $location
|
||||
* @return string
|
||||
*/
|
||||
public static function highlightSourceAtLocation(Source $source, SourceLocation $location)
|
||||
{
|
||||
$line = $location->line;
|
||||
$lineOffset = $source->locationOffset->line - 1;
|
||||
$columnOffset = self::getColumnOffset($source, $location);
|
||||
|
||||
$contextLine = $line + $lineOffset;
|
||||
$prevLineNum = (string) ($contextLine - 1);
|
||||
$lineNum = (string) $contextLine;
|
||||
$nextLineNum = (string) ($contextLine + 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);
|
||||
|
||||
$whitespace = function ($len) {
|
||||
return str_repeat(' ', $len);
|
||||
};
|
||||
|
||||
$lpad = function ($len, $str) {
|
||||
return str_pad($str, $len - mb_strlen($str, 'UTF-8') + 1, ' ', STR_PAD_LEFT);
|
||||
};
|
||||
|
||||
$lines[0] = $whitespace($source->locationOffset->column - 1) . $lines[0];
|
||||
|
||||
return
|
||||
($line >= 2 ? $lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2] . "\n" : '') .
|
||||
($lpad($padLen, $lineNum) . ': ' . $lines[$line - 1] . "\n") .
|
||||
($whitespace(2 + $padLen + $location->column - 1 + $columnOffset) . "^\n") .
|
||||
($line < count($lines) ? $lpad($padLen, $nextLineNum) . ': ' . $lines[$line] . "\n" : '');
|
||||
}
|
||||
|
||||
public static function getColumnOffset(Source $source, SourceLocation $location)
|
||||
{
|
||||
return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ class Source
|
||||
* be "Foo.graphql" and location to be `{ line: 40, column: 0 }`.
|
||||
* line and column in locationOffset are 1-indexed
|
||||
*
|
||||
* @param $body
|
||||
* @param null $name
|
||||
* @param string $body
|
||||
* @param string|null $name
|
||||
* @param SourceLocation|null $location
|
||||
*/
|
||||
public function __construct($body, $name = null, SourceLocation $location = null)
|
||||
@ -52,7 +52,7 @@ class Source
|
||||
|
||||
$this->body = $body;
|
||||
$this->length = mb_strlen($body, 'UTF-8');
|
||||
$this->name = $name ?: 'GraphQL';
|
||||
$this->name = $name ?: 'GraphQL request';
|
||||
$this->locationOffset = $location ?: new SourceLocation(1, 1);
|
||||
|
||||
Utils::invariant(
|
||||
|
@ -15,10 +15,11 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testDissallowsUncommonControlCharacters()
|
||||
{
|
||||
$char = Utils::chr(0x0007);
|
||||
|
||||
$this->setExpectedExceptionRegExp(SyntaxError::class, '/' . preg_quote('Syntax Error GraphQL (1:1) Cannot contain the invalid character "\u0007"', '/') . '/');
|
||||
$this->lexOne($char);
|
||||
$this->expectSyntaxError(
|
||||
Utils::chr(0x0007),
|
||||
'Cannot contain the invalid character "\u0007"',
|
||||
$this->loc(1, 1)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,14 +108,21 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
" ?\n" .
|
||||
"\n";
|
||||
|
||||
$this->setExpectedException(SyntaxError::class,
|
||||
'Syntax Error GraphQL (3:5) Cannot parse the unexpected character "?".' . "\n" .
|
||||
try {
|
||||
$this->lexOne($str);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals(
|
||||
'Syntax Error: Cannot parse the unexpected character "?".' . "\n" .
|
||||
"\n" .
|
||||
"GraphQL request (3:5)\n" .
|
||||
"2: \n" .
|
||||
"3: ?\n" .
|
||||
" ^\n" .
|
||||
"4: \n");
|
||||
$this->lexOne($str);
|
||||
"4: \n",
|
||||
(string) $error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,34 +137,42 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
"\n";
|
||||
$source = new Source($str, 'foo.js', new SourceLocation(11, 12));
|
||||
|
||||
$this->setExpectedException(
|
||||
SyntaxError::class,
|
||||
'Syntax Error foo.js (13:6) ' .
|
||||
'Cannot parse the unexpected character "?".' . "\n" .
|
||||
"\n" .
|
||||
'12: ' . "\n" .
|
||||
'13: ?' . "\n" .
|
||||
' ^' . "\n" .
|
||||
'14: ' . "\n"
|
||||
);
|
||||
try {
|
||||
$lexer = new Lexer($source);
|
||||
$lexer->advance();
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals(
|
||||
'Syntax Error: Cannot parse the unexpected character "?".' . "\n" .
|
||||
"\n" .
|
||||
"foo.js (13:6)\n" .
|
||||
"12: \n" .
|
||||
"13: ?\n" .
|
||||
" ^\n" .
|
||||
"14: \n",
|
||||
(string) $error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testUpdatesColumnNumbersInErrorForFileContext()
|
||||
{
|
||||
$source = new Source('?', 'foo.js', new SourceLocation(1, 5));
|
||||
|
||||
$this->setExpectedException(
|
||||
SyntaxError::class,
|
||||
'Syntax Error foo.js (1:5) ' .
|
||||
'Cannot parse the unexpected character "?".' . "\n" .
|
||||
"\n" .
|
||||
'1: ?' . "\n" .
|
||||
' ^' . "\n"
|
||||
);
|
||||
try {
|
||||
$lexer = new Lexer($source);
|
||||
$lexer->advance();
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals(
|
||||
'Syntax Error: Cannot parse the unexpected character "?".' . "\n" .
|
||||
"\n" .
|
||||
"foo.js (1:5)\n" .
|
||||
'1: ?' . "\n" .
|
||||
' ^' . "\n",
|
||||
(string) $error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -298,41 +314,22 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
\"\"\""));
|
||||
}
|
||||
|
||||
public function reportsUsefulBlockStringErrors() {
|
||||
return [
|
||||
['"""', "Syntax Error GraphQL (1:4) Unterminated string.\n\n1: \"\"\"\n ^\n"],
|
||||
['"""no end quote', "Syntax Error GraphQL (1:16) Unterminated string.\n\n1: \"\"\"no end quote\n ^\n"],
|
||||
['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Syntax Error GraphQL (1:23) Invalid character within String: \"\\u0007\""],
|
||||
['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Syntax Error GraphQL (1:21) Invalid character within String: \"\\u0000\""],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulBlockStringErrors
|
||||
* @it lex reports useful block string errors
|
||||
*/
|
||||
public function testReportsUsefulBlockStringErrors($str, $expectedMessage)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||
$this->lexOne($str);
|
||||
}
|
||||
|
||||
public function reportsUsefulStringErrors() {
|
||||
return [
|
||||
['"', "Syntax Error GraphQL (1:2) Unterminated string.\n\n1: \"\n ^\n"],
|
||||
['"no end quote', "Syntax Error GraphQL (1:14) Unterminated string.\n\n1: \"no end quote\n ^\n"],
|
||||
["'single quotes'", "Syntax Error GraphQL (1:1) Unexpected single quote character ('), did you mean to use a double quote (\")?\n\n1: 'single quotes'\n ^\n"],
|
||||
['"contains unescaped \u0007 control char"', "Syntax Error GraphQL (1:21) Invalid character within String: \"\\u0007\"\n\n1: \"contains unescaped \\u0007 control char\"\n ^\n"],
|
||||
['"null-byte is not \u0000 end of file"', 'Syntax Error GraphQL (1:19) Invalid character within String: "\\u0000"' . "\n\n1: \"null-byte is not \\u0000 end of file\"\n ^\n"],
|
||||
['"multi' . "\n" . 'line"', "Syntax Error GraphQL (1:7) Unterminated string.\n\n1: \"multi\n ^\n2: line\"\n"],
|
||||
['"multi' . "\r" . 'line"', "Syntax Error GraphQL (1:7) Unterminated string.\n\n1: \"multi\n ^\n2: line\"\n"],
|
||||
['"bad \\z esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\z\n\n1: \"bad \\z esc\"\n ^\n"],
|
||||
['"bad \\x esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\x\n\n1: \"bad \\x esc\"\n ^\n"],
|
||||
['"bad \\u1 esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\u1 es\n\n1: \"bad \\u1 esc\"\n ^\n"],
|
||||
['"bad \\u0XX1 esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\u0XX1\n\n1: \"bad \\u0XX1 esc\"\n ^\n"],
|
||||
['"bad \\uXXXX esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uXXXX\n\n1: \"bad \\uXXXX esc\"\n ^\n"],
|
||||
['"bad \\uFXXX esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uFXXX\n\n1: \"bad \\uFXXX esc\"\n ^\n"],
|
||||
['"bad \\uXXXF esc"', "Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uXXXF\n\n1: \"bad \\uXXXF esc\"\n ^\n"],
|
||||
['"', "Unterminated string.", $this->loc(1, 2)],
|
||||
['"no end quote', "Unterminated string.", $this->loc(1, 14)],
|
||||
["'single quotes'", "Unexpected single quote character ('), did you mean to use a double quote (\")?", $this->loc(1, 1)],
|
||||
['"contains unescaped \u0007 control char"', "Invalid character within String: \"\\u0007\"", $this->loc(1, 21)],
|
||||
['"null-byte is not \u0000 end of file"', 'Invalid character within String: "\\u0000"', $this->loc(1, 19)],
|
||||
['"multi' . "\n" . 'line"', "Unterminated string.", $this->loc(1, 7)],
|
||||
['"multi' . "\r" . 'line"', "Unterminated string.", $this->loc(1, 7)],
|
||||
['"bad \\z esc"', "Invalid character escape sequence: \\z", $this->loc(1, 7)],
|
||||
['"bad \\x esc"', "Invalid character escape sequence: \\x", $this->loc(1, 7)],
|
||||
['"bad \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)],
|
||||
['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)],
|
||||
['"bad \\uXXXX esc"', "Invalid character escape sequence: \\uXXXX", $this->loc(1, 7)],
|
||||
['"bad \\uFXXX esc"', "Invalid character escape sequence: \\uFXXX", $this->loc(1, 7)],
|
||||
['"bad \\uXXXF esc"', "Invalid character escape sequence: \\uXXXF", $this->loc(1, 7)],
|
||||
];
|
||||
}
|
||||
|
||||
@ -340,10 +337,27 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
* @dataProvider reportsUsefulStringErrors
|
||||
* @it lex reports useful string errors
|
||||
*/
|
||||
public function testLexReportsUsefulStringErrors($str, $expectedMessage)
|
||||
public function testLexReportsUsefulStringErrors($str, $expectedMessage, $location)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||
$this->lexOne($str);
|
||||
$this->expectSyntaxError($str, $expectedMessage, $location);
|
||||
}
|
||||
|
||||
public function reportsUsefulBlockStringErrors() {
|
||||
return [
|
||||
['"""', "Unterminated string.", $this->loc(1, 4)],
|
||||
['"""no end quote', "Unterminated string.", $this->loc(1, 16)],
|
||||
['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Invalid character within String: \"\\u0007\"", $this->loc(1, 23)],
|
||||
['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Invalid character within String: \"\\u0000\"", $this->loc(1, 21)],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulBlockStringErrors
|
||||
* @it lex reports useful block string errors
|
||||
*/
|
||||
public function testReportsUsefulBlockStringErrors($str, $expectedMessage, $location)
|
||||
{
|
||||
$this->expectSyntaxError($str, $expectedMessage, $location);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -420,15 +434,15 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
public function reportsUsefulNumberErrors()
|
||||
{
|
||||
return [
|
||||
[ '00', "Syntax Error GraphQL (1:2) Invalid number, unexpected digit after 0: \"0\"\n\n1: 00\n ^\n"],
|
||||
[ '+1', "Syntax Error GraphQL (1:1) Cannot parse the unexpected character \"+\".\n\n1: +1\n ^\n"],
|
||||
[ '1.', "Syntax Error GraphQL (1:3) Invalid number, expected digit but got: <EOF>\n\n1: 1.\n ^\n"],
|
||||
[ '1.e1', "Syntax Error GraphQL (1:3) Invalid number, expected digit but got: \"e\"\n\n1: 1.e1\n ^\n"],
|
||||
[ '.123', "Syntax Error GraphQL (1:1) Cannot parse the unexpected character \".\".\n\n1: .123\n ^\n"],
|
||||
[ '1.A', "Syntax Error GraphQL (1:3) Invalid number, expected digit but got: \"A\"\n\n1: 1.A\n ^\n"],
|
||||
[ '-A', "Syntax Error GraphQL (1:2) Invalid number, expected digit but got: \"A\"\n\n1: -A\n ^\n"],
|
||||
[ '1.0e', "Syntax Error GraphQL (1:5) Invalid number, expected digit but got: <EOF>\n\n1: 1.0e\n ^\n"],
|
||||
[ '1.0eA', "Syntax Error GraphQL (1:5) Invalid number, expected digit but got: \"A\"\n\n1: 1.0eA\n ^\n"],
|
||||
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)],
|
||||
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)],
|
||||
[ '1.', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 3)],
|
||||
[ '1.e1', "Invalid number, expected digit but got: \"e\"", $this->loc(1, 3)],
|
||||
[ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
|
||||
[ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)],
|
||||
[ '-A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 2)],
|
||||
[ '1.0e', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 5)],
|
||||
[ '1.0eA', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 5)],
|
||||
];
|
||||
}
|
||||
|
||||
@ -436,10 +450,9 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
* @dataProvider reportsUsefulNumberErrors
|
||||
* @it lex reports useful number errors
|
||||
*/
|
||||
public function testReportsUsefulNumberErrors($str, $expectedMessage)
|
||||
public function testReportsUsefulNumberErrors($str, $expectedMessage, $location)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||
$this->lexOne($str);
|
||||
$this->expectSyntaxError($str, $expectedMessage, $location);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -507,10 +520,10 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
$unicode2 = json_decode('"\u200b"');
|
||||
|
||||
return [
|
||||
['..', "Syntax Error GraphQL (1:1) Cannot parse the unexpected character \".\".\n\n1: ..\n ^\n"],
|
||||
['?', "Syntax Error GraphQL (1:1) Cannot parse the unexpected character \"?\".\n\n1: ?\n ^\n"],
|
||||
[$unicode1, "Syntax Error GraphQL (1:1) Cannot parse the unexpected character \"\\u203b\".\n\n1: $unicode1\n ^\n"],
|
||||
[$unicode2, "Syntax Error GraphQL (1:1) Cannot parse the unexpected character \"\\u200b\".\n\n1: $unicode2\n ^\n"],
|
||||
['..', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
|
||||
['?', "Cannot parse the unexpected character \"?\".", $this->loc(1, 1)],
|
||||
[$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)],
|
||||
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)],
|
||||
];
|
||||
}
|
||||
|
||||
@ -518,10 +531,9 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
* @dataProvider reportsUsefulUnknownCharErrors
|
||||
* @it lex reports useful unknown character error
|
||||
*/
|
||||
public function testReportsUsefulUnknownCharErrors($str, $expectedMessage)
|
||||
public function testReportsUsefulUnknownCharErrors($str, $expectedMessage, $location)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||
$this->lexOne($str);
|
||||
$this->expectSyntaxError($str, $expectedMessage, $location);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -533,8 +545,14 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
$lexer = new Lexer(new Source($q));
|
||||
$this->assertArraySubset(['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'], (array) $lexer->advance());
|
||||
|
||||
$this->setExpectedException(SyntaxError::class, 'Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "b"' . "\n\n1: a-b\n ^\n");
|
||||
$this->setExpectedException(SyntaxError::class, 'Syntax Error: Invalid number, expected digit but got: "b"');
|
||||
try {
|
||||
$lexer->advance();
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch(SyntaxError $error) {
|
||||
$this->assertEquals([$this->loc(1,3)], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -588,4 +606,20 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
$lexer = new Lexer(new Source($body));
|
||||
return $lexer->advance();
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $message);
|
||||
try {
|
||||
$this->lexOne($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,13 +39,13 @@ class ParserTest extends \PHPUnit_Framework_TestCase
|
||||
public function parseProvidesUsefulErrors()
|
||||
{
|
||||
return [
|
||||
['{', "Syntax Error GraphQL (1:2) Expected Name, found <EOF>\n\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]],
|
||||
['{', "Syntax Error: Expected Name, found <EOF>", "Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]],
|
||||
['{ ...MissingOn }
|
||||
fragment MissingOn Type
|
||||
', "Syntax Error GraphQL (2:20) Expected \"on\", found Name \"Type\"\n\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",],
|
||||
['{ field: {} }', "Syntax Error GraphQL (1:10) Expected Name, found {\n\n1: { field: {} }\n ^\n"],
|
||||
['notanoperation Foo { field }', "Syntax Error GraphQL (1:1) Unexpected Name \"notanoperation\"\n\n1: notanoperation Foo { field }\n ^\n"],
|
||||
['...', "Syntax Error GraphQL (1:1) Unexpected ...\n\n1: ...\n ^\n"],
|
||||
', "Syntax Error: Expected \"on\", found Name \"Type\"", "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",],
|
||||
['{ field: {} }', "Syntax Error: Expected Name, found {", "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"],
|
||||
['notanoperation Foo { field }', "Syntax Error: Unexpected Name \"notanoperation\"", "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"],
|
||||
['...', "Syntax Error: Unexpected ...", "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"],
|
||||
];
|
||||
}
|
||||
|
||||
@ -53,13 +53,14 @@ fragment MissingOn Type
|
||||
* @dataProvider parseProvidesUsefulErrors
|
||||
* @it parse provides useful errors
|
||||
*/
|
||||
public function testParseProvidesUsefulErrors($str, $expectedMessage, $expectedPositions = null, $expectedLocations = null)
|
||||
public function testParseProvidesUsefulErrors($str, $expectedMessage, $stringRepresentation, $expectedPositions = null, $expectedLocations = null)
|
||||
{
|
||||
try {
|
||||
Parser::parse($str);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $e) {
|
||||
$this->assertEquals($expectedMessage, $e->getMessage());
|
||||
$this->assertEquals($stringRepresentation, (string) $e);
|
||||
|
||||
if ($expectedPositions) {
|
||||
$this->assertEquals($expectedPositions, $e->getPositions());
|
||||
@ -76,8 +77,15 @@ fragment MissingOn Type
|
||||
*/
|
||||
public function testParseProvidesUsefulErrorWhenUsingSource()
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, "Syntax Error MyQuery.graphql (1:6) Expected {, found <EOF>\n\n1: query\n ^\n");
|
||||
try {
|
||||
Parser::parse(new Source('query', 'MyQuery.graphql'));
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals(
|
||||
"Syntax Error: Expected {, found <EOF>\n\nMyQuery.graphql (1:6)\n1: query\n ^\n",
|
||||
(string) $error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,8 +102,11 @@ fragment MissingOn Type
|
||||
*/
|
||||
public function testParsesConstantDefaultValues()
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, "Syntax Error GraphQL (1:37) Unexpected $\n\n" . '1: query Foo($x: Complex = { a: { b: [ $var ] } }) { field }' . "\n ^\n");
|
||||
Parser::parse('query Foo($x: Complex = { a: { b: [ $var ] } }) { field }');
|
||||
$this->expectSyntaxError(
|
||||
'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }',
|
||||
'Unexpected $',
|
||||
$this->loc(1,37)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,8 +114,11 @@ fragment MissingOn Type
|
||||
*/
|
||||
public function testDoesNotAcceptFragmentsNamedOn()
|
||||
{
|
||||
$this->setExpectedException('GraphQL\Error\SyntaxError', 'Syntax Error GraphQL (1:10) Unexpected Name "on"');
|
||||
Parser::parse('fragment on on on { on }');
|
||||
$this->expectSyntaxError(
|
||||
'fragment on on on { on }',
|
||||
'Unexpected Name "on"',
|
||||
$this->loc(1,10)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,8 +126,11 @@ fragment MissingOn Type
|
||||
*/
|
||||
public function testDoesNotAcceptFragmentSpreadOfOn()
|
||||
{
|
||||
$this->setExpectedException('GraphQL\Error\SyntaxError', 'Syntax Error GraphQL (1:9) Expected Name, found }');
|
||||
Parser::parse('{ ...on }');
|
||||
$this->expectSyntaxError(
|
||||
'{ ...on }',
|
||||
'Expected Name, found }',
|
||||
$this->loc(1,9)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -610,4 +627,20 @@ fragment $fragmentName on Type {
|
||||
{
|
||||
return TestUtils::nodeToArray($node);
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $message);
|
||||
try {
|
||||
Parser::parse($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace GraphQL\Tests\Language;
|
||||
use GraphQL\Error\SyntaxError;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
|
||||
class SchemaParserTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -199,31 +200,49 @@ extend type Hello {
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Extension do not include descriptions
|
||||
* @expectedException \GraphQL\Error\SyntaxError
|
||||
* @expectedExceptionMessage Syntax Error GraphQL (3:7)
|
||||
* @it Extension without anything throws
|
||||
*/
|
||||
public function testExtensionDoNotIncludeDescriptions() {
|
||||
public function testExtensionWithoutAnythingThrows()
|
||||
{
|
||||
$this->expectSyntaxError(
|
||||
'extend type Hello',
|
||||
'Unexpected <EOF>',
|
||||
$this->loc(1, 18)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Extension do not include descriptions
|
||||
*/
|
||||
public function testExtensionDoNotIncludeDescriptions()
|
||||
{
|
||||
$body = '
|
||||
"Description"
|
||||
extend type Hello {
|
||||
world: String
|
||||
}';
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
$body,
|
||||
'Unexpected Name "extend"',
|
||||
$this->loc(3, 7)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Extension do not include descriptions
|
||||
* @expectedException \GraphQL\Error\SyntaxError
|
||||
* @expectedExceptionMessage Syntax Error GraphQL (2:14)
|
||||
*/
|
||||
public function testExtensionDoNotIncludeDescriptions2() {
|
||||
public function testExtensionDoNotIncludeDescriptions2()
|
||||
{
|
||||
$body = '
|
||||
extend "Description" type Hello {
|
||||
world: String
|
||||
}
|
||||
}';
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
$body,
|
||||
'Unexpected String "Description"',
|
||||
$this->loc(2, 14)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -707,9 +726,11 @@ type Hello {
|
||||
*/
|
||||
public function testUnionFailsWithNoTypes()
|
||||
{
|
||||
$body = 'union Hello = |';
|
||||
$this->setExpectedExceptionRegExp(SyntaxError::class, '/' . preg_quote('Syntax Error GraphQL (1:16) Expected Name, found <EOF>', '/') . '/');
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
'union Hello = |',
|
||||
'Expected Name, found <EOF>',
|
||||
$this->loc(1, 16)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -717,9 +738,11 @@ type Hello {
|
||||
*/
|
||||
public function testUnionFailsWithLeadingDoublePipe()
|
||||
{
|
||||
$body = 'union Hello = || Wo | Rld';
|
||||
$this->setExpectedExceptionRegExp(SyntaxError::class, '/' . preg_quote('Syntax Error GraphQL (1:16) Expected Name, found |', '/') . '/');
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
'union Hello = || Wo | Rld',
|
||||
'Expected Name, found |',
|
||||
$this->loc(1, 16)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -727,9 +750,11 @@ type Hello {
|
||||
*/
|
||||
public function testUnionFailsWithDoublePipe()
|
||||
{
|
||||
$body = 'union Hello = Wo || Rld';
|
||||
$this->setExpectedExceptionRegExp(SyntaxError::class, '/' . preg_quote('Syntax Error GraphQL (1:19) Expected Name, found |', '/') . '/');
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
'union Hello = Wo || Rld',
|
||||
'Expected Name, found |',
|
||||
$this->loc(1, 19)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -737,9 +762,11 @@ type Hello {
|
||||
*/
|
||||
public function testUnionFailsWithTrailingPipe()
|
||||
{
|
||||
$body = 'union Hello = | Wo | Rld |';
|
||||
$this->setExpectedExceptionRegExp(SyntaxError::class, '/' . preg_quote('Syntax Error GraphQL (1:27) Expected Name, found <EOF>', '/') . '/');
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
'union Hello = | Wo | Rld |',
|
||||
'Expected Name, found <EOF>',
|
||||
$this->loc(1, 27)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -804,7 +831,6 @@ input Hello {
|
||||
|
||||
/**
|
||||
* @it Simple input object with args should fail
|
||||
* @expectedException \GraphQL\Error\SyntaxError
|
||||
*/
|
||||
public function testSimpleInputObjectWithArgsShouldFail()
|
||||
{
|
||||
@ -812,20 +838,26 @@ input Hello {
|
||||
input Hello {
|
||||
world(foo: Int): String
|
||||
}';
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
$body,
|
||||
'Expected :, found (',
|
||||
$this->loc(3, 14)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Directive with incorrect locations
|
||||
* @expectedException \GraphQL\Error\SyntaxError
|
||||
* @expectedExceptionMessage Syntax Error GraphQL (2:33) Unexpected Name "INCORRECT_LOCATION"
|
||||
*/
|
||||
public function testDirectiveWithIncorrectLocationShouldFail()
|
||||
{
|
||||
$body = '
|
||||
directive @foo on FIELD | INCORRECT_LOCATION
|
||||
';
|
||||
Parser::parse($body);
|
||||
$this->expectSyntaxError(
|
||||
$body,
|
||||
'Unexpected Name "INCORRECT_LOCATION"',
|
||||
$this->loc(2, 33)
|
||||
);
|
||||
}
|
||||
|
||||
private function typeNode($name, $loc)
|
||||
@ -887,4 +919,20 @@ input Hello {
|
||||
'description' => null
|
||||
];
|
||||
}
|
||||
|
||||
private function loc($line, $column)
|
||||
{
|
||||
return new SourceLocation($line, $column);
|
||||
}
|
||||
|
||||
private function expectSyntaxError($text, $message, $location)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $message);
|
||||
try {
|
||||
Parser::parse($text);
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertEquals([$location], $error->getLocations());
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class QueryExecutionTest extends TestCase
|
||||
$this->assertSame(null, $result->data);
|
||||
$this->assertCount(1, $result->errors);
|
||||
$this->assertContains(
|
||||
'Syntax Error GraphQL (1:4) Expected Name, found <EOF>',
|
||||
'Syntax Error: Expected Name, found <EOF>',
|
||||
$result->errors[0]->getMessage()
|
||||
);
|
||||
}
|
||||
|
@ -303,10 +303,18 @@ class ServerTest extends \PHPUnit_Framework_TestCase
|
||||
$server = Server::create();
|
||||
$ast = $server->parse('{q}');
|
||||
$this->assertInstanceOf('GraphQL\Language\AST\DocumentNode', $ast);
|
||||
}
|
||||
|
||||
$this->setExpectedExceptionRegExp(SyntaxError::class, '/' . preg_quote('{q', '/') . '/');
|
||||
public function testParseFailure()
|
||||
{
|
||||
$server = Server::create();
|
||||
try {
|
||||
$server->parse('{q');
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $error) {
|
||||
$this->assertContains('{q', (string) $error);
|
||||
$this->assertEquals('Syntax Error: Expected Name, found <EOF>', $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidate()
|
||||
|
Loading…
x
Reference in New Issue
Block a user