Version 4.6.0
This commit is contained in:
1 parent
f79dcf067a
commit
517a5e1f70
2036 files changed
+110041
-26162
No files matched your search
+7
-2
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
/**
|
||||
@@ -14,8 +17,9 @@ interface ClientAware
|
||||
/**
|
||||
* Returns true when exception message is safe to be displayed to a client.
|
||||
*
|
||||
* @api
|
||||
* @return bool
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isClientSafe();
|
||||
|
||||
@@ -24,8 +28,9 @@ interface ClientAware
|
||||
*
|
||||
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
|
||||
*
|
||||
* @api
|
||||
* @return string
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getCategory();
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
namespace GraphQL\Error;
|
||||
|
||||
/**
|
||||
* Collection of flags for [error debugging](error-handling.md#debugging-tools).
|
||||
*/
|
||||
class Debug
|
||||
{
|
||||
const INCLUDE_DEBUG_MESSAGE = 1;
|
||||
const INCLUDE_TRACE = 2;
|
||||
const RETHROW_INTERNAL_EXCEPTIONS = 4;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
/**
|
||||
* Collection of flags for [error debugging](error-handling.md#debugging-tools).
|
||||
*/
|
||||
final class DebugFlag
|
||||
{
|
||||
public const NONE = 0;
|
||||
public const INCLUDE_DEBUG_MESSAGE = 1;
|
||||
public const INCLUDE_TRACE = 2;
|
||||
public const RETHROW_INTERNAL_EXCEPTIONS = 4;
|
||||
public const RETHROW_UNSAFE_EXCEPTIONS = 8;
|
||||
}
|
||||
+194
-112
@@ -1,9 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Utils\Utils;
|
||||
use JsonSerializable;
|
||||
use Throwable;
|
||||
use Traversable;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function iterator_to_array;
|
||||
|
||||
/**
|
||||
* Describes an Error found during the parse, validate, or
|
||||
@@ -19,19 +33,14 @@ use GraphQL\Utils\Utils;
|
||||
* Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
|
||||
* are available in addition to those listed below.
|
||||
*/
|
||||
class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
class Error extends Exception implements JsonSerializable, ClientAware
|
||||
{
|
||||
const CATEGORY_GRAPHQL = 'graphql';
|
||||
const CATEGORY_GRAPHQL = 'graphql';
|
||||
const CATEGORY_INTERNAL = 'internal';
|
||||
|
||||
/**
|
||||
* A message describing the Error for debugging purposes.
|
||||
* Lazily initialized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* @var SourceLocation[]
|
||||
*/
|
||||
private $locations;
|
||||
@@ -40,135 +49,148 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
* An array describing the JSON-path into the execution response which
|
||||
* corresponds to this error. Only included for errors during execution.
|
||||
*
|
||||
* @var array
|
||||
* @var mixed[]|null
|
||||
*/
|
||||
public $path;
|
||||
|
||||
/**
|
||||
* An array of GraphQL AST Nodes corresponding to this error.
|
||||
*
|
||||
* @var array
|
||||
* @var Node[]|null
|
||||
*/
|
||||
public $nodes;
|
||||
|
||||
/**
|
||||
* The source GraphQL document corresponding to this error.
|
||||
* The source GraphQL document for the first location of this error.
|
||||
*
|
||||
* Note that if this Error represents more than one node, the source may not
|
||||
* represent nodes after the first node.
|
||||
*
|
||||
* @var Source|null
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var int[] */
|
||||
private $positions;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
/** @var bool */
|
||||
private $isClientSafe;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $category;
|
||||
|
||||
/** @var mixed[]|null */
|
||||
protected $extensions;
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Node|Node[]|Traversable|null $nodes
|
||||
* @param mixed[] $positions
|
||||
* @param mixed[]|null $path
|
||||
* @param Throwable $previous
|
||||
* @param mixed[] $extensions
|
||||
*/
|
||||
public function __construct(
|
||||
$message = '',
|
||||
$nodes = null,
|
||||
?Source $source = null,
|
||||
array $positions = [],
|
||||
$path = null,
|
||||
$previous = null,
|
||||
array $extensions = []
|
||||
) {
|
||||
parent::__construct($message, 0, $previous);
|
||||
|
||||
// Compute list of blame nodes.
|
||||
if ($nodes instanceof Traversable) {
|
||||
$nodes = iterator_to_array($nodes);
|
||||
} elseif ($nodes !== null && ! is_array($nodes)) {
|
||||
$nodes = [$nodes];
|
||||
}
|
||||
|
||||
$this->nodes = $nodes;
|
||||
$this->source = $source;
|
||||
$this->positions = $positions;
|
||||
$this->path = $path;
|
||||
$this->extensions = count($extensions) > 0 ? $extensions : (
|
||||
$previous instanceof self
|
||||
? $previous->extensions
|
||||
: []
|
||||
);
|
||||
|
||||
if ($previous instanceof ClientAware) {
|
||||
$this->isClientSafe = $previous->isClientSafe();
|
||||
$cat = $previous->getCategory();
|
||||
$this->category = $cat === '' || $cat === null ? self::CATEGORY_INTERNAL: $cat;
|
||||
} elseif ($previous !== null) {
|
||||
$this->isClientSafe = false;
|
||||
$this->category = self::CATEGORY_INTERNAL;
|
||||
} else {
|
||||
$this->isClientSafe = true;
|
||||
$this->category = self::CATEGORY_GRAPHQL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param mixed $error
|
||||
* @param Node[]|null $nodes
|
||||
* @param mixed[]|null $path
|
||||
*
|
||||
* @return Error
|
||||
*/
|
||||
public static function createLocatedError($error, $nodes = null, $path = null)
|
||||
{
|
||||
if ($error instanceof self) {
|
||||
if ($error->path && $error->nodes) {
|
||||
if ($error->path !== null && $error->nodes !== null && count($error->nodes) !== 0) {
|
||||
return $error;
|
||||
} else {
|
||||
$nodes = $nodes ?: $error->nodes;
|
||||
$path = $path ?: $error->path;
|
||||
}
|
||||
|
||||
$nodes = $nodes ?? $error->nodes;
|
||||
$path = $path ?? $error->path;
|
||||
}
|
||||
|
||||
$source = $positions = $originalError = null;
|
||||
$source = null;
|
||||
$originalError = null;
|
||||
$positions = [];
|
||||
$extensions = [];
|
||||
|
||||
if ($error instanceof self) {
|
||||
$message = $error->getMessage();
|
||||
$message = $error->getMessage();
|
||||
$originalError = $error;
|
||||
$nodes = $error->nodes ?: $nodes;
|
||||
$source = $error->source;
|
||||
$positions = $error->positions;
|
||||
} else if ($error instanceof \Exception || $error instanceof \Throwable) {
|
||||
$message = $error->getMessage();
|
||||
$nodes = $error->nodes ?? $nodes;
|
||||
$source = $error->source;
|
||||
$positions = $error->positions;
|
||||
$extensions = $error->extensions;
|
||||
} elseif ($error instanceof Throwable) {
|
||||
$message = $error->getMessage();
|
||||
$originalError = $error;
|
||||
} else {
|
||||
$message = (string) $error;
|
||||
}
|
||||
|
||||
return new static(
|
||||
$message ?: 'An unknown error occurred.',
|
||||
$message === '' || $message === null ? 'An unknown error occurred.' : $message,
|
||||
$nodes,
|
||||
$source,
|
||||
$positions,
|
||||
$path,
|
||||
$originalError
|
||||
$originalError,
|
||||
$extensions
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Error $error
|
||||
* @return array
|
||||
* @return mixed[]
|
||||
*/
|
||||
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 \Throwable $previous
|
||||
*/
|
||||
public function __construct(
|
||||
$message,
|
||||
$nodes = null,
|
||||
Source $source = null,
|
||||
$positions = null,
|
||||
$path = null,
|
||||
$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;
|
||||
|
||||
if ($previous instanceof ClientAware) {
|
||||
$this->isClientSafe = $previous->isClientSafe();
|
||||
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
|
||||
} else if ($previous) {
|
||||
$this->isClientSafe = false;
|
||||
$this->category = static::CATEGORY_INTERNAL;
|
||||
} else {
|
||||
$this->isClientSafe = true;
|
||||
$this->category = static::CATEGORY_GRAPHQL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -185,34 +207,40 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Source|null
|
||||
*/
|
||||
public function getSource()
|
||||
public function getSource() : ?Source
|
||||
{
|
||||
if (null === $this->source) {
|
||||
if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) {
|
||||
if ($this->source === null) {
|
||||
if (isset($this->nodes[0]) && $this->nodes[0]->loc !== null) {
|
||||
$this->source = $this->nodes[0]->loc->source;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return int[]
|
||||
*/
|
||||
public function getPositions()
|
||||
public function getPositions() : array
|
||||
{
|
||||
if (null === $this->positions) {
|
||||
if (!empty($this->nodes)) {
|
||||
$positions = array_map(function($node) {
|
||||
if (count($this->positions) === 0 && count($this->nodes ?? []) > 0) {
|
||||
$positions = array_map(
|
||||
static function ($node) : ?int {
|
||||
return isset($node->loc) ? $node->loc->start : null;
|
||||
}, $this->nodes);
|
||||
$this->positions = array_filter($positions, function($p) {
|
||||
},
|
||||
$this->nodes
|
||||
);
|
||||
|
||||
$positions = array_filter(
|
||||
$positions,
|
||||
static function ($p) : bool {
|
||||
return $p !== null;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->positions = array_values($positions);
|
||||
}
|
||||
|
||||
return $this->positions;
|
||||
}
|
||||
|
||||
@@ -227,19 +255,38 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
* point out to field mentioned in multiple fragments. Errors during execution include a
|
||||
* single location, the field which produced the error.
|
||||
*
|
||||
* @api
|
||||
* @return SourceLocation[]
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getLocations()
|
||||
public function getLocations() : array
|
||||
{
|
||||
if (null === $this->locations) {
|
||||
if (! isset($this->locations)) {
|
||||
$positions = $this->getPositions();
|
||||
$source = $this->getSource();
|
||||
$source = $this->getSource();
|
||||
$nodes = $this->nodes;
|
||||
|
||||
if ($positions && $source) {
|
||||
$this->locations = array_map(function ($pos) use ($source) {
|
||||
return $source->getLocation($pos);
|
||||
}, $positions);
|
||||
if ($source !== null && count($positions) !== 0) {
|
||||
$this->locations = array_map(
|
||||
static function ($pos) use ($source) : SourceLocation {
|
||||
return $source->getLocation($pos);
|
||||
},
|
||||
$positions
|
||||
);
|
||||
} elseif ($nodes !== null && count($nodes) !== 0) {
|
||||
$locations = array_filter(
|
||||
array_map(
|
||||
static function ($node) : ?SourceLocation {
|
||||
if (isset($node->loc->source)) {
|
||||
return $node->loc->source->getLocation($node->loc->start);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
$nodes
|
||||
)
|
||||
);
|
||||
$this->locations = array_values($locations);
|
||||
} else {
|
||||
$this->locations = [];
|
||||
}
|
||||
@@ -248,53 +295,88 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
return $this->locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node[]|null
|
||||
*/
|
||||
public function getNodes()
|
||||
{
|
||||
return $this->nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array describing the path from the root value to the field which produced this error.
|
||||
* Only included for execution errors.
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*
|
||||
* @api
|
||||
* @return array|null
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getExtensions()
|
||||
{
|
||||
return $this->extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array representation of error suitable for serialization
|
||||
*
|
||||
* @deprecated Use FormattedError::createFromException() instead
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function toSerializableArray()
|
||||
{
|
||||
$arr = [
|
||||
'message' => $this->getMessage()
|
||||
'message' => $this->getMessage(),
|
||||
];
|
||||
|
||||
$locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {
|
||||
return $loc->toSerializableArray();
|
||||
});
|
||||
$locations = Utils::map(
|
||||
$this->getLocations(),
|
||||
static function (SourceLocation $loc) : array {
|
||||
return $loc->toSerializableArray();
|
||||
}
|
||||
);
|
||||
|
||||
if (!empty($locations)) {
|
||||
if (count($locations) > 0) {
|
||||
$arr['locations'] = $locations;
|
||||
}
|
||||
if (!empty($this->path)) {
|
||||
if (count($this->path ?? []) > 0) {
|
||||
$arr['path'] = $this->path;
|
||||
}
|
||||
if (count($this->extensions ?? []) > 0) {
|
||||
$arr['extensions'] = $this->extensions;
|
||||
}
|
||||
|
||||
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 <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
function jsonSerialize()
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toSerializableArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return FormattedError::printError($this);
|
||||
}
|
||||
}
|
||||
+244
-104
@@ -1,10 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use Countable;
|
||||
use ErrorException;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\WrappingType;
|
||||
use GraphQL\Utils\Utils;
|
||||
use Throwable;
|
||||
use function addcslashes;
|
||||
use function array_filter;
|
||||
use function array_intersect_key;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function mb_strlen;
|
||||
use function preg_split;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* This class is used for [default error formatting](error-handling.md).
|
||||
@@ -13,20 +41,119 @@ use GraphQL\Utils\Utils;
|
||||
*/
|
||||
class FormattedError
|
||||
{
|
||||
/** @var string */
|
||||
private static $internalErrorMessage = 'Internal server error';
|
||||
|
||||
/**
|
||||
* Set default error message for internal errors formatted using createFormattedError().
|
||||
* This value can be overridden by passing 3rd argument to `createFormattedError()`.
|
||||
*
|
||||
* @api
|
||||
* @param string $msg
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function setInternalErrorMessage($msg)
|
||||
{
|
||||
self::$internalErrorMessage = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a GraphQLError to a string, representing useful location information
|
||||
* about the error's position in the source.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function printError(Error $error)
|
||||
{
|
||||
$printedLocations = [];
|
||||
if (count($error->nodes ?? []) !== 0) {
|
||||
/** @var Node $node */
|
||||
foreach ($error->nodes as $node) {
|
||||
if ($node->loc === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node->loc->source === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$printedLocations[] = self::highlightSourceAtLocation(
|
||||
$node->loc->source,
|
||||
$node->loc->source->getLocation($node->loc->start)
|
||||
);
|
||||
}
|
||||
} elseif ($error->getSource() !== null && count($error->getLocations()) !== 0) {
|
||||
$source = $error->getSource();
|
||||
foreach (($error->getLocations() ?? []) as $location) {
|
||||
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
|
||||
}
|
||||
}
|
||||
|
||||
return count($printedLocations) === 0
|
||||
? $error->getMessage()
|
||||
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a helpful description of the location of the error in the GraphQL
|
||||
* Source document.
|
||||
*
|
||||
* @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];
|
||||
|
||||
$outputLines = [
|
||||
sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
|
||||
$line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
|
||||
self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
|
||||
self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
|
||||
$line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
|
||||
];
|
||||
|
||||
return implode("\n", array_filter($outputLines));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
@@ -34,52 +161,57 @@ class FormattedError
|
||||
* This method only exposes exception message when exception implements ClientAware interface
|
||||
* (or when debug flags are passed).
|
||||
*
|
||||
* For a list of available debug flags see GraphQL\Error\Debug constants.
|
||||
* For a list of available debug flags @see \GraphQL\Error\DebugFlag constants.
|
||||
*
|
||||
* @param string $internalErrorMessage
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @throws Throwable
|
||||
*
|
||||
* @api
|
||||
* @param \Throwable $e
|
||||
* @param bool|int $debug
|
||||
* @param string $internalErrorMessage
|
||||
* @return array
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
|
||||
public static function createFromException(Throwable $exception, int $debug = DebugFlag::NONE, $internalErrorMessage = null) : array
|
||||
{
|
||||
Utils::invariant(
|
||||
$e instanceof \Exception || $e instanceof \Throwable,
|
||||
"Expected exception, got %s",
|
||||
Utils::getVariableType($e)
|
||||
);
|
||||
$internalErrorMessage = $internalErrorMessage ?? self::$internalErrorMessage;
|
||||
|
||||
$internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage;
|
||||
|
||||
if ($e instanceof ClientAware) {
|
||||
if ($exception instanceof ClientAware) {
|
||||
$formattedError = [
|
||||
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
|
||||
'category' => $e->getCategory()
|
||||
'message' => $exception->isClientSafe() ? $exception->getMessage() : $internalErrorMessage,
|
||||
'extensions' => [
|
||||
'category' => $exception->getCategory(),
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$formattedError = [
|
||||
'message' => $internalErrorMessage,
|
||||
'category' => Error::CATEGORY_INTERNAL
|
||||
'message' => $internalErrorMessage,
|
||||
'extensions' => [
|
||||
'category' => Error::CATEGORY_INTERNAL,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($e instanceof Error) {
|
||||
$locations = Utils::map($e->getLocations(), function(SourceLocation $loc) {
|
||||
return $loc->toSerializableArray();
|
||||
});
|
||||
|
||||
if (!empty($locations)) {
|
||||
if ($exception instanceof Error) {
|
||||
$locations = Utils::map(
|
||||
$exception->getLocations(),
|
||||
static function (SourceLocation $loc) : array {
|
||||
return $loc->toSerializableArray();
|
||||
}
|
||||
);
|
||||
if (count($locations) > 0) {
|
||||
$formattedError['locations'] = $locations;
|
||||
}
|
||||
if (!empty($e->path)) {
|
||||
$formattedError['path'] = $e->path;
|
||||
|
||||
if (count($exception->path ?? []) > 0) {
|
||||
$formattedError['path'] = $exception->path;
|
||||
}
|
||||
if (count($exception->getExtensions() ?? []) > 0) {
|
||||
$formattedError['extensions'] = $exception->getExtensions() + $formattedError['extensions'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
$formattedError = self::addDebugEntries($formattedError, $e, $debug);
|
||||
if ($debug !== DebugFlag::NONE) {
|
||||
$formattedError = self::addDebugEntries($formattedError, $exception, $debug);
|
||||
}
|
||||
|
||||
return $formattedError;
|
||||
@@ -87,126 +219,125 @@ class FormattedError
|
||||
|
||||
/**
|
||||
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
|
||||
* (see GraphQL\Error\Debug for available flags)
|
||||
* (@see \GraphQL\Error\DebugFlag for available flags)
|
||||
*
|
||||
* @param array $formattedError
|
||||
* @param \Throwable $e
|
||||
* @param bool $debug
|
||||
* @return array
|
||||
* @throws \Throwable
|
||||
* @param mixed[] $formattedError
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function addDebugEntries(array $formattedError, $e, $debug)
|
||||
public static function addDebugEntries(array $formattedError, Throwable $e, int $debugFlag) : array
|
||||
{
|
||||
if (!$debug) {
|
||||
if ($debugFlag === DebugFlag::NONE) {
|
||||
return $formattedError;
|
||||
}
|
||||
|
||||
Utils::invariant(
|
||||
$e instanceof \Exception || $e instanceof \Throwable,
|
||||
"Expected exception, got %s",
|
||||
Utils::getVariableType($e)
|
||||
);
|
||||
|
||||
$debug = (int) $debug;
|
||||
|
||||
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
|
||||
if (!$e instanceof Error) {
|
||||
if (( $debugFlag & DebugFlag::RETHROW_INTERNAL_EXCEPTIONS) !== 0) {
|
||||
if (! $e instanceof Error) {
|
||||
throw $e;
|
||||
} else if ($e->getPrevious()) {
|
||||
}
|
||||
|
||||
if ($e->getPrevious() !== null) {
|
||||
throw $e->getPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
$isInternal = !$e instanceof ClientAware || !$e->isClientSafe();
|
||||
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
|
||||
|
||||
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
|
||||
if (($debugFlag & DebugFlag::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) {
|
||||
throw $e->getPrevious();
|
||||
}
|
||||
|
||||
if (($debugFlag & DebugFlag::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) {
|
||||
// Displaying debugMessage as a first entry:
|
||||
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
|
||||
}
|
||||
|
||||
if ($debug & Debug::INCLUDE_TRACE) {
|
||||
if ($e instanceof \ErrorException || $e instanceof \Error) {
|
||||
if (($debugFlag & DebugFlag::INCLUDE_TRACE) !== 0) {
|
||||
if ($e instanceof ErrorException || $e instanceof \Error) {
|
||||
$formattedError += [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
];
|
||||
}
|
||||
|
||||
$isTrivial = $e instanceof Error && !$e->getPrevious();
|
||||
$isTrivial = $e instanceof Error && $e->getPrevious() === null;
|
||||
|
||||
if (!$isTrivial) {
|
||||
$debugging = $e->getPrevious() ?: $e;
|
||||
if (! $isTrivial) {
|
||||
$debugging = $e->getPrevious() ?? $e;
|
||||
$formattedError['trace'] = static::toSafeTrace($debugging);
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares final error formatter taking in account $debug flags.
|
||||
* If initial formatter is not set, FormattedError::createFromException is used
|
||||
*
|
||||
* @param callable|null $formatter
|
||||
* @param $debug
|
||||
* @return callable|\Closure
|
||||
*/
|
||||
public static function prepareFormatter(callable $formatter = null, $debug)
|
||||
public static function prepareFormatter(?callable $formatter, int $debug) : callable
|
||||
{
|
||||
$formatter = $formatter ?: function($e) {
|
||||
$formatter = $formatter ?? static function ($e) : array {
|
||||
return FormattedError::createFromException($e);
|
||||
};
|
||||
if ($debug) {
|
||||
$formatter = function($e) use ($formatter, $debug) {
|
||||
if ($debug !== DebugFlag::NONE) {
|
||||
$formatter = static function ($e) use ($formatter, $debug) : array {
|
||||
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
|
||||
};
|
||||
}
|
||||
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error trace as serializable array
|
||||
*
|
||||
* @param Throwable $error
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @api
|
||||
* @param \Throwable $error
|
||||
* @return array
|
||||
*/
|
||||
public static function toSafeTrace($error)
|
||||
{
|
||||
$trace = $error->getTrace();
|
||||
|
||||
// Remove invariant entries as they don't provide much value:
|
||||
if (
|
||||
isset($trace[0]['function']) && isset($trace[0]['class']) &&
|
||||
('GraphQL\Utils\Utils::invariant' === $trace[0]['class'].'::'.$trace[0]['function'])) {
|
||||
if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
|
||||
// Remove invariant entries as they don't provide much value:
|
||||
($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
|
||||
array_shift($trace);
|
||||
} elseif (! isset($trace[0]['file'])) {
|
||||
// Remove root call as it's likely error handler trace:
|
||||
array_shift($trace);
|
||||
}
|
||||
|
||||
// Remove root call as it's likely error handler trace:
|
||||
else if (!isset($trace[0]['file'])) {
|
||||
array_shift($trace);
|
||||
}
|
||||
return array_map(
|
||||
static function ($err) : array {
|
||||
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
|
||||
|
||||
return array_map(function($err) {
|
||||
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
|
||||
if (isset($err['function'])) {
|
||||
$func = $err['function'];
|
||||
$args = array_map([self::class, 'printVar'], $err['args'] ?? []);
|
||||
$funcStr = $func . '(' . implode(', ', $args) . ')';
|
||||
|
||||
if (isset($err['function'])) {
|
||||
$func = $err['function'];
|
||||
$args = !empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
|
||||
$funcStr = $func . '(' . implode(", ", $args) . ')';
|
||||
|
||||
if (isset($err['class'])) {
|
||||
$safeErr['call'] = $err['class'] . '::' . $funcStr;
|
||||
} else {
|
||||
$safeErr['function'] = $funcStr;
|
||||
if (isset($err['class'])) {
|
||||
$safeErr['call'] = $err['class'] . '::' . $funcStr;
|
||||
} else {
|
||||
$safeErr['function'] = $funcStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $safeErr;
|
||||
}, $trace);
|
||||
return $safeErr;
|
||||
},
|
||||
$trace
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $var
|
||||
* @param mixed $var
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function printVar($var)
|
||||
@@ -216,16 +347,17 @@ class FormattedError
|
||||
if ($var instanceof WrappingType) {
|
||||
$var = $var->getWrappedType(true);
|
||||
}
|
||||
|
||||
return 'GraphQLType: ' . $var->name;
|
||||
}
|
||||
|
||||
if (is_object($var)) {
|
||||
return 'instance of ' . get_class($var) . ($var instanceof \Countable ? '(' . count($var) . ')' : '');
|
||||
return 'instance of ' . get_class($var) . ($var instanceof Countable ? '(' . count($var) . ')' : '');
|
||||
}
|
||||
if (is_array($var)) {
|
||||
return 'array(' . count($var) . ')';
|
||||
}
|
||||
if ('' === $var) {
|
||||
if ($var === '') {
|
||||
return '(empty string)';
|
||||
}
|
||||
if (is_string($var)) {
|
||||
@@ -237,42 +369,50 @@ class FormattedError
|
||||
if (is_scalar($var)) {
|
||||
return $var;
|
||||
}
|
||||
if (null === $var) {
|
||||
if ($var === null) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
return gettype($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated as of v0.8.0
|
||||
* @param $error
|
||||
*
|
||||
* @param string $error
|
||||
* @param SourceLocation[] $locations
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function create($error, array $locations = [])
|
||||
{
|
||||
$formatted = [
|
||||
'message' => $error
|
||||
];
|
||||
$formatted = ['message' => $error];
|
||||
|
||||
if (!empty($locations)) {
|
||||
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
|
||||
if (count($locations) > 0) {
|
||||
$formatted['locations'] = array_map(
|
||||
static function ($loc) : array {
|
||||
return $loc->toArray();
|
||||
},
|
||||
$locations
|
||||
);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ErrorException $e
|
||||
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function createFromPHPError(\ErrorException $e)
|
||||
public static function createFromPHPError(ErrorException $e)
|
||||
{
|
||||
return [
|
||||
'message' => $e->getMessage(),
|
||||
'message' => $e->getMessage(),
|
||||
'severity' => $e->getSeverity(),
|
||||
'trace' => self::toSafeTrace($e)
|
||||
'trace' => self::toSafeTrace($e),
|
||||
];
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -1,15 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Class InvariantVoilation
|
||||
*
|
||||
* Note:
|
||||
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
||||
* user-land code
|
||||
*
|
||||
* @package GraphQL\Error
|
||||
*/
|
||||
class InvariantViolation extends \LogicException
|
||||
class InvariantViolation extends LogicException
|
||||
{
|
||||
public static function shouldNotHappen() : self
|
||||
{
|
||||
return new self('This should not have happened');
|
||||
}
|
||||
}
|
||||
+11
-57
@@ -1,71 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use function sprintf;
|
||||
|
||||
class SyntaxError extends Error
|
||||
{
|
||||
/**
|
||||
* @param Source $source
|
||||
* @param int $position
|
||||
* @param int $position
|
||||
* @param string $description
|
||||
*/
|
||||
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(
|
||||
sprintf('Syntax Error: %s', $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;
|
||||
}
|
||||
|
||||
}
|
||||
+6
-5
@@ -1,14 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class UserError
|
||||
*
|
||||
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
|
||||
*
|
||||
* @package GraphQL\Error
|
||||
*/
|
||||
class UserError extends \RuntimeException implements ClientAware
|
||||
class UserError extends RuntimeException implements ClientAware
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
|
||||
+50
-33
@@ -1,6 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Exception\InvalidArgument;
|
||||
use function is_int;
|
||||
use function trigger_error;
|
||||
use const E_USER_WARNING;
|
||||
|
||||
/**
|
||||
* Encapsulates warnings produced by the library.
|
||||
*
|
||||
@@ -9,28 +17,29 @@ namespace GraphQL\Error;
|
||||
*/
|
||||
final class Warning
|
||||
{
|
||||
const WARNING_NAME = 1;
|
||||
const WARNING_ASSIGN = 2;
|
||||
const WARNING_CONFIG = 4;
|
||||
const WARNING_FULL_SCHEMA_SCAN = 8;
|
||||
const WARNING_CONFIG_DEPRECATION = 16;
|
||||
const WARNING_NOT_A_TYPE = 32;
|
||||
const ALL = 63;
|
||||
public const WARNING_ASSIGN = 2;
|
||||
public const WARNING_CONFIG = 4;
|
||||
public const WARNING_FULL_SCHEMA_SCAN = 8;
|
||||
public const WARNING_CONFIG_DEPRECATION = 16;
|
||||
public const WARNING_NOT_A_TYPE = 32;
|
||||
public const ALL = 63;
|
||||
|
||||
static $enableWarnings = self::ALL;
|
||||
/** @var int */
|
||||
private static $enableWarnings = self::ALL;
|
||||
|
||||
static $warned = [];
|
||||
/** @var mixed[] */
|
||||
private static $warned = [];
|
||||
|
||||
static private $warningHandler;
|
||||
/** @var callable|null */
|
||||
private static $warningHandler;
|
||||
|
||||
/**
|
||||
* Sets warning handler which can intercept all system warnings.
|
||||
* When not set, trigger_error() is used to notify about warnings.
|
||||
*
|
||||
* @api
|
||||
* @param callable|null $warningHandler
|
||||
*/
|
||||
public static function setWarningHandler(callable $warningHandler = null)
|
||||
public static function setWarningHandler(?callable $warningHandler = null)
|
||||
{
|
||||
self::$warningHandler = $warningHandler;
|
||||
}
|
||||
@@ -43,18 +52,20 @@ final class Warning
|
||||
*
|
||||
* When passing true - suppresses all warnings.
|
||||
*
|
||||
* @api
|
||||
* @param bool|int $suppress
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
static function suppress($suppress = true)
|
||||
public static function suppress($suppress = true)
|
||||
{
|
||||
if (true === $suppress) {
|
||||
if ($suppress === true) {
|
||||
self::$enableWarnings = 0;
|
||||
} else if (false === $suppress) {
|
||||
} elseif ($suppress === false) {
|
||||
self::$enableWarnings = self::ALL;
|
||||
} else {
|
||||
$suppress = (int) $suppress;
|
||||
} elseif (is_int($suppress)) {
|
||||
self::$enableWarnings &= ~$suppress;
|
||||
} else {
|
||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,39 +77,45 @@ final class Warning
|
||||
*
|
||||
* When passing true - re-enables all warnings.
|
||||
*
|
||||
* @api
|
||||
* @param bool|int $enable
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function enable($enable = true)
|
||||
{
|
||||
if (true === $enable) {
|
||||
if ($enable === true) {
|
||||
self::$enableWarnings = self::ALL;
|
||||
} else if (false === $enable) {
|
||||
} elseif ($enable === false) {
|
||||
self::$enableWarnings = 0;
|
||||
} else {
|
||||
$enable = (int) $enable;
|
||||
} elseif (is_int($enable)) {
|
||||
self::$enableWarnings |= $enable;
|
||||
} else {
|
||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
|
||||
}
|
||||
}
|
||||
|
||||
static function warnOnce($errorMessage, $warningId, $messageLevel = null)
|
||||
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null)
|
||||
{
|
||||
if (self::$warningHandler) {
|
||||
$messageLevel = $messageLevel ?? E_USER_WARNING;
|
||||
|
||||
if (self::$warningHandler !== null) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
|
||||
$fn($errorMessage, $warningId, $messageLevel);
|
||||
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
|
||||
self::$warned[$warningId] = true;
|
||||
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
|
||||
trigger_error($errorMessage, $messageLevel);
|
||||
}
|
||||
}
|
||||
|
||||
static function warn($errorMessage, $warningId, $messageLevel = null)
|
||||
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null)
|
||||
{
|
||||
if (self::$warningHandler) {
|
||||
$messageLevel = $messageLevel ?? E_USER_WARNING;
|
||||
|
||||
if (self::$warningHandler !== null) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} else if ((self::$enableWarnings & $warningId) > 0) {
|
||||
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
|
||||
$fn($errorMessage, $warningId, $messageLevel);
|
||||
} elseif ((self::$enableWarnings & $warningId) > 0) {
|
||||
trigger_error($errorMessage, $messageLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user