<?php

declare(strict_types=1);

namespace GraphQL\Utils;

use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;

class TypeComparators
{
    /**
     * Provided two types, return true if the types are equal (invariant).
     *
     * @return bool
     */
    public static function isEqualType(Type $typeA, Type $typeB)
    {
        // Equivalent types are equal.
        if ($typeA === $typeB) {
            return true;
        }

        // If either type is non-null, the other must also be non-null.
        if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
            return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
        }

        // If either type is a list, the other must also be a list.
        if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
            return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
        }

        // Otherwise the types are not equal.
        return false;
    }

    /**
     * Provided a type and a super type, return true if the first type is either
     * equal or a subset of the second super type (covariant).
     *
     * @param AbstractType $maybeSubType
     * @param AbstractType $superType
     *
     * @return bool
     */
    public static function isTypeSubTypeOf(Schema $schema, $maybeSubType, $superType)
    {
        // Equivalent type is a valid subtype
        if ($maybeSubType === $superType) {
            return true;
        }

        // If superType is non-null, maybeSubType must also be nullable.
        if ($superType instanceof NonNull) {
            if ($maybeSubType instanceof NonNull) {
                return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
            }

            return false;
        }

        if ($maybeSubType instanceof NonNull) {
            // If superType is nullable, maybeSubType may be non-null.
            return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType);
        }

        // If superType type is a list, maybeSubType type must also be a list.
        if ($superType instanceof ListOfType) {
            if ($maybeSubType instanceof ListOfType) {
                return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
            }

            return false;
        }

        if ($maybeSubType instanceof ListOfType) {
            // If superType is not a list, maybeSubType must also be not a list.
            return false;
        }

        // If superType type is an abstract type, maybeSubType type may be a currently
        // possible object type.
        return Type::isAbstractType($superType) &&
            $maybeSubType instanceof ObjectType &&
            $schema->isPossibleType(
                $superType,
                $maybeSubType
            );
    }

    /**
     * Provided two composite types, determine if they "overlap". Two composite
     * types overlap when the Sets of possible concrete types for each intersect.
     *
     * This is often used to determine if a fragment of a given type could possibly
     * be visited in a context of another type.
     *
     * This function is commutative.
     *
     * @return bool
     */
    public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
    {
        // Equivalent types overlap
        if ($typeA === $typeB) {
            return true;
        }

        if ($typeA instanceof AbstractType) {
            if ($typeB instanceof AbstractType) {
                // If both types are abstract, then determine if there is any intersection
                // between possible concrete types of each.
                foreach ($schema->getPossibleTypes($typeA) as $type) {
                    if ($schema->isPossibleType($typeB, $type)) {
                        return true;
                    }
                }

                return false;
            }

            // Determine if the latter type is a possible concrete type of the former.
            return $schema->isPossibleType($typeA, $typeB);
        }

        if ($typeB instanceof AbstractType) {
            // Determine if the former type is a possible concrete type of the latter.
            return $schema->isPossibleType($typeB, $typeA);
        }

        // Otherwise the types do not overlap.
        return false;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Language\AST;

use ArrayAccess;
use Countable;
use Generator;
use GraphQL\Utils\AST;
use IteratorAggregate;
use function array_merge;
use function array_splice;
use function count;
use function is_array;

class NodeList implements ArrayAccess, IteratorAggregate, Countable
{
    /** @var Node[]|mixed[] */
    private $nodes;

    /**
     * @param Node[]|mixed[] $nodes
     *
     * @return static
     */
    public static function create(array $nodes)
    {
        return new static($nodes);
    }

    /**
     * @param Node[]|mixed[] $nodes
     */
    public function __construct(array $nodes)
    {
        $this->nodes = $nodes;
    }

    /**
     * @param mixed $offset
     *
     * @return bool
     */
    public function offsetExists($offset)
    {
        return isset($this->nodes[$offset]);
    }

    /**
     * @param mixed $offset
     *
     * @return mixed
     */
    public function offsetGet($offset)
    {
        $item = $this->nodes[$offset];

        if (is_array($item) && isset($item['kind'])) {
            $this->nodes[$offset] = $item = AST::fromArray($item);
        }

        return $item;
    }

    /**
     * @param mixed $offset
     * @param mixed $value
     */
    public function offsetSet($offset, $value)
    {
        if (is_array($value) && isset($value['kind'])) {
            $value = AST::fromArray($value);
        }
        $this->nodes[$offset] = $value;
    }

    /**
     * @param mixed $offset
     */
    public function offsetUnset($offset)
    {
        unset($this->nodes[$offset]);
    }

    /**
     * @param int   $offset
     * @param int   $length
     * @param mixed $replacement
     *
     * @return NodeList
     */
    public function splice($offset, $length, $replacement = null)
    {
        return new NodeList(array_splice($this->nodes, $offset, $length, $replacement));
    }

    /**
     * @param NodeList|Node[] $list
     *
     * @return NodeList
     */
    public function merge($list)
    {
        if ($list instanceof self) {
            $list = $list->nodes;
        }

        return new NodeList(array_merge($this->nodes, $list));
    }

    /**
     * @return Generator
     */
    public function getIterator()
    {
        $count = count($this->nodes);
        for ($i = 0; $i < $count; $i++) {
            yield $this->offsetGet($i);
        }
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->nodes);
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Language;

use ArrayObject;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Utils\TypeInfo;
use SplFixedArray;
use stdClass;
use function array_pop;
use function array_splice;
use function call_user_func;
use function call_user_func_array;
use function count;
use function func_get_args;
use function is_array;
use function is_callable;
use function json_encode;

/**
 * Utility for efficient AST traversal and modification.
 *
 * `visit()` will walk through an AST using a depth first traversal, calling
 * the visitor's enter function at each node in the traversal, and calling the
 * leave function after visiting that node and all of it's child nodes.
 *
 * By returning different values from the enter and leave functions, the
 * behavior of the visitor can be altered, including skipping over a sub-tree of
 * the AST (by returning false), editing the AST by returning a value or null
 * to remove the value, or to stop the whole traversal by returning BREAK.
 *
 * When using `visit()` to edit an AST, the original AST will not be modified, and
 * a new version of the AST with the changes applied will be returned from the
 * visit function.
 *
 *     $editedAST = Visitor::visit($ast, [
 *       'enter' => function ($node, $key, $parent, $path, $ancestors) {
 *         // return
 *         //   null: no action
 *         //   Visitor::skipNode(): skip visiting this node
 *         //   Visitor::stop(): stop visiting altogether
 *         //   Visitor::removeNode(): delete this node
 *         //   any value: replace this node with the returned value
 *       },
 *       'leave' => function ($node, $key, $parent, $path, $ancestors) {
 *         // return
 *         //   null: no action
 *         //   Visitor::stop(): stop visiting altogether
 *         //   Visitor::removeNode(): delete this node
 *         //   any value: replace this node with the returned value
 *       }
 *     ]);
 *
 * Alternatively to providing enter() and leave() functions, a visitor can
 * instead provide functions named the same as the [kinds of AST nodes](reference.md#graphqllanguageastnodekind),
 * or enter/leave visitors at a named key, leading to four permutations of
 * visitor API:
 *
 * 1) Named visitors triggered when entering a node a specific kind.
 *
 *     Visitor::visit($ast, [
 *       'Kind' => function ($node) {
 *         // enter the "Kind" node
 *       }
 *     ]);
 *
 * 2) Named visitors that trigger upon entering and leaving a node of
 *    a specific kind.
 *
 *     Visitor::visit($ast, [
 *       'Kind' => [
 *         'enter' => function ($node) {
 *           // enter the "Kind" node
 *         }
 *         'leave' => function ($node) {
 *           // leave the "Kind" node
 *         }
 *       ]
 *     ]);
 *
 * 3) Generic visitors that trigger upon entering and leaving any node.
 *
 *     Visitor::visit($ast, [
 *       'enter' => function ($node) {
 *         // enter any node
 *       },
 *       'leave' => function ($node) {
 *         // leave any node
 *       }
 *     ]);
 *
 * 4) Parallel visitors for entering and leaving nodes of a specific kind.
 *
 *     Visitor::visit($ast, [
 *       'enter' => [
 *         'Kind' => function($node) {
 *           // enter the "Kind" node
 *         }
 *       },
 *       'leave' => [
 *         'Kind' => function ($node) {
 *           // leave the "Kind" node
 *         }
 *       ]
 *     ]);
 */
class Visitor
{
    /** @var string[][] */
    public static $visitorKeys = [
        NodeKind::NAME                 => [],
        NodeKind::DOCUMENT             => ['definitions'],
        NodeKind::OPERATION_DEFINITION => ['name', 'variableDefinitions', 'directives', 'selectionSet'],
        NodeKind::VARIABLE_DEFINITION  => ['variable', 'type', 'defaultValue'],
        NodeKind::VARIABLE             => ['name'],
        NodeKind::SELECTION_SET        => ['selections'],
        NodeKind::FIELD                => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
        NodeKind::ARGUMENT             => ['name', 'value'],
        NodeKind::FRAGMENT_SPREAD      => ['name', 'directives'],
        NodeKind::INLINE_FRAGMENT      => ['typeCondition', 'directives', 'selectionSet'],
        NodeKind::FRAGMENT_DEFINITION  => [
            'name',
            // Note: fragment variable definitions are experimental and may be changed
            // or removed in the future.
            'variableDefinitions',
            'typeCondition',
            'directives',
            'selectionSet',
        ],

        NodeKind::INT           => [],
        NodeKind::FLOAT         => [],
        NodeKind::STRING        => [],
        NodeKind::BOOLEAN       => [],
        NodeKind::NULL          => [],
        NodeKind::ENUM          => [],
        NodeKind::LST           => ['values'],
        NodeKind::OBJECT        => ['fields'],
        NodeKind::OBJECT_FIELD  => ['name', 'value'],
        NodeKind::DIRECTIVE     => ['name', 'arguments'],
        NodeKind::NAMED_TYPE    => ['name'],
        NodeKind::LIST_TYPE     => ['type'],
        NodeKind::NON_NULL_TYPE => ['type'],

        NodeKind::SCHEMA_DEFINITION            => ['directives', 'operationTypes'],
        NodeKind::OPERATION_TYPE_DEFINITION    => ['type'],
        NodeKind::SCALAR_TYPE_DEFINITION       => ['description', 'name', 'directives'],
        NodeKind::OBJECT_TYPE_DEFINITION       => ['description', 'name', 'interfaces', 'directives', 'fields'],
        NodeKind::FIELD_DEFINITION             => ['description', 'name', 'arguments', 'type', 'directives'],
        NodeKind::INPUT_VALUE_DEFINITION       => ['description', 'name', 'type', 'defaultValue', 'directives'],
        NodeKind::INTERFACE_TYPE_DEFINITION    => ['description', 'name', 'directives', 'fields'],
        NodeKind::UNION_TYPE_DEFINITION        => ['description', 'name', 'directives', 'types'],
        NodeKind::ENUM_TYPE_DEFINITION         => ['description', 'name', 'directives', 'values'],
        NodeKind::ENUM_VALUE_DEFINITION        => ['description', 'name', 'directives'],
        NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],

        NodeKind::SCALAR_TYPE_EXTENSION       => ['name', 'directives'],
        NodeKind::OBJECT_TYPE_EXTENSION       => ['name', 'interfaces', 'directives', 'fields'],
        NodeKind::INTERFACE_TYPE_EXTENSION    => ['name', 'directives', 'fields'],
        NodeKind::UNION_TYPE_EXTENSION        => ['name', 'directives', 'types'],
        NodeKind::ENUM_TYPE_EXTENSION         => ['name', 'directives', 'values'],
        NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'],

        NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations'],

        NodeKind::SCHEMA_EXTENSION => ['directives', 'operationTypes'],
    ];

    /**
     * Visit the AST (see class description for details)
     *
     * @param Node|ArrayObject|stdClass $root
     * @param callable[]                $visitor
     * @param mixed[]|null              $keyMap
     *
     * @return Node|mixed
     *
     * @throws Exception
     *
     * @api
     */
    public static function visit($root, $visitor, $keyMap = null)
    {
        $visitorKeys = $keyMap ?: self::$visitorKeys;

        $stack     = null;
        $inArray   = $root instanceof NodeList || is_array($root);
        $keys      = [$root];
        $index     = -1;
        $edits     = [];
        $parent    = null;
        $path      = [];
        $ancestors = [];
        $newRoot   = $root;

        $UNDEFINED = null;

        do {
            $index++;
            $isLeaving = $index === count($keys);
            $key       = null;
            $node      = null;
            $isEdited  = $isLeaving && count($edits) !== 0;

            if ($isLeaving) {
                $key    = ! $ancestors ? $UNDEFINED : $path[count($path) - 1];
                $node   = $parent;
                $parent = array_pop($ancestors);

                if ($isEdited) {
                    if ($inArray) {
                        // $node = $node; // arrays are value types in PHP
                        if ($node instanceof NodeList) {
                            $node = clone $node;
                        }
                    } else {
                        $node = clone $node;
                    }
                    $editOffset = 0;
                    for ($ii = 0; $ii < count($edits); $ii++) {
                        $editKey   = $edits[$ii][0];
                        $editValue = $edits[$ii][1];

                        if ($inArray) {
                            $editKey -= $editOffset;
                        }
                        if ($inArray && $editValue === null) {
                            if ($node instanceof NodeList) {
                                $node->splice($editKey, 1);
                            } else {
                                array_splice($node, $editKey, 1);
                            }
                            $editOffset++;
                        } else {
                            if ($node instanceof NodeList || is_array($node)) {
                                $node[$editKey] = $editValue;
                            } else {
                                $node->{$editKey} = $editValue;
                            }
                        }
                    }
                }
                $index   = $stack['index'];
                $keys    = $stack['keys'];
                $edits   = $stack['edits'];
                $inArray = $stack['inArray'];
                $stack   = $stack['prev'];
            } else {
                $key  = $parent !== null ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
                $node = $parent !== null ? ($parent instanceof NodeList || is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot;
                if ($node === null || $node === $UNDEFINED) {
                    continue;
                }
                if ($parent !== null) {
                    $path[] = $key;
                }
            }

            $result = null;
            if (! $node instanceof NodeList && ! is_array($node)) {
                if (! ($node instanceof Node)) {
                    throw new Exception('Invalid AST Node: ' . json_encode($node));
                }

                $visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving);

                if ($visitFn) {
                    $result    = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
                    $editValue = null;

                    if ($result !== null) {
                        if ($result instanceof VisitorOperation) {
                            if ($result->doBreak) {
                                break;
                            }
                            if (! $isLeaving && $result->doContinue) {
                                array_pop($path);
                                continue;
                            }
                            if ($result->removeNode) {
                                $editValue = null;
                            }
                        } else {
                            $editValue = $result;
                        }

                        $edits[] = [$key, $editValue];
                        if (! $isLeaving) {
                            if (! ($editValue instanceof Node)) {
                                array_pop($path);
                                continue;
                            }

                            $node = $editValue;
                        }
                    }
                }
            }

            if ($result === null && $isEdited) {
                $edits[] = [$key, $node];
            }

            if ($isLeaving) {
                array_pop($path);
            } else {
                $stack   = [
                    'inArray' => $inArray,
                    'index'   => $index,
                    'keys'    => $keys,
                    'edits'   => $edits,
                    'prev'    => $stack,
                ];
                $inArray = $node instanceof NodeList || is_array($node);

                $keys  = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
                $index = -1;
                $edits = [];
                if ($parent !== null) {
                    $ancestors[] = $parent;
                }
                $parent = $node;
            }
        } while ($stack);

        if (count($edits) !== 0) {
            $newRoot = $edits[0][1];
        }

        return $newRoot;
    }

    /**
     * Returns marker for visitor break
     *
     * @return VisitorOperation
     *
     * @api
     */
    public static function stop()
    {
        $r          = new VisitorOperation();
        $r->doBreak = true;

        return $r;
    }

    /**
     * Returns marker for skipping current node
     *
     * @return VisitorOperation
     *
     * @api
     */
    public static function skipNode()
    {
        $r             = new VisitorOperation();
        $r->doContinue = true;

        return $r;
    }

    /**
     * Returns marker for removing a node
     *
     * @return VisitorOperation
     *
     * @api
     */
    public static function removeNode()
    {
        $r             = new VisitorOperation();
        $r->removeNode = true;

        return $r;
    }

    /**
     * @param callable[][] $visitors
     *
     * @return callable[][]
     */
    public static function visitInParallel($visitors)
    {
        $visitorsCount = count($visitors);
        $skipping      = new SplFixedArray($visitorsCount);

        return [
            'enter' => static function (Node $node) use ($visitors, $skipping, $visitorsCount) {
                for ($i = 0; $i < $visitorsCount; $i++) {
                    if (! empty($skipping[$i])) {
                        continue;
                    }

                    $fn = self::getVisitFn(
                        $visitors[$i],
                        $node->kind, /* isLeaving */
                        false
                    );

                    if (! $fn) {
                        continue;
                    }

                    $result = call_user_func_array($fn, func_get_args());

                    if ($result instanceof VisitorOperation) {
                        if ($result->doContinue) {
                            $skipping[$i] = $node;
                        } elseif ($result->doBreak) {
                            $skipping[$i] = $result;
                        } elseif ($result->removeNode) {
                            return $result;
                        }
                    } elseif ($result !== null) {
                        return $result;
                    }
                }
            },
            'leave' => static function (Node $node) use ($visitors, $skipping, $visitorsCount) {
                for ($i = 0; $i < $visitorsCount; $i++) {
                    if (empty($skipping[$i])) {
                        $fn = self::getVisitFn(
                            $visitors[$i],
                            $node->kind, /* isLeaving */
                            true
                        );

                        if ($fn) {
                            $result = call_user_func_array($fn, func_get_args());
                            if ($result instanceof VisitorOperation) {
                                if ($result->doBreak) {
                                    $skipping[$i] = $result;
                                } elseif ($result->removeNode) {
                                    return $result;
                                }
                            } elseif ($result !== null) {
                                return $result;
                            }
                        }
                    } elseif ($skipping[$i] === $node) {
                        $skipping[$i] = null;
                    }
                }
            },
        ];
    }

    /**
     * Creates a new visitor instance which maintains a provided TypeInfo instance
     * along with visiting visitor.
     */
    public static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor)
    {
        return [
            'enter' => static function (Node $node) use ($typeInfo, $visitor) {
                $typeInfo->enter($node);
                $fn = self::getVisitFn($visitor, $node->kind, false);

                if ($fn) {
                    $result = call_user_func_array($fn, func_get_args());
                    if ($result !== null) {
                        $typeInfo->leave($node);
                        if ($result instanceof Node) {
                            $typeInfo->enter($result);
                        }
                    }

                    return $result;
                }

                return null;
            },
            'leave' => static function (Node $node) use ($typeInfo, $visitor) {
                $fn     = self::getVisitFn($visitor, $node->kind, true);
                $result = $fn ? call_user_func_array($fn, func_get_args()) : null;
                $typeInfo->leave($node);

                return $result;
            },
        ];
    }

    /**
     * @param callable[]|null $visitor
     * @param string          $kind
     * @param bool            $isLeaving
     *
     * @return callable|null
     */
    public static function getVisitFn($visitor, $kind, $isLeaving)
    {
        if ($visitor === null) {
            return null;
        }

        $kindVisitor = $visitor[$kind] ?? null;

        if (! $isLeaving && is_callable($kindVisitor)) {
            // { Kind() {} }
            return $kindVisitor;
        }

        if (is_array($kindVisitor)) {
            if ($isLeaving) {
                $kindSpecificVisitor = $kindVisitor['leave'] ?? null;
            } else {
                $kindSpecificVisitor = $kindVisitor['enter'] ?? null;
            }

            if ($kindSpecificVisitor && is_callable($kindSpecificVisitor)) {
                // { Kind: { enter() {}, leave() {} } }
                return $kindSpecificVisitor;
            }

            return null;
        }

        $visitor += ['leave' => null, 'enter' => null];

        $specificVisitor = $isLeaving ? $visitor['leave'] : $visitor['enter'];

        if ($specificVisitor) {
            if (is_callable($specificVisitor)) {
                // { enter() {}, leave() {} }
                return $specificVisitor;
            }
            $specificKindVisitor = $specificVisitor[$kind] ?? null;

            if (is_callable($specificKindVisitor)) {
                // { enter: { Kind() {} }, leave: { Kind() {} } }
                return $specificKindVisitor;
            }
        }

        return null;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Server;

use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\GraphQL;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
use JsonSerializable;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use function file_get_contents;
use function header;
use function is_array;
use function is_callable;
use function is_string;
use function json_decode;
use function json_encode;
use function json_last_error;
use function json_last_error_msg;
use function sprintf;
use function stripos;

/**
 * Contains functionality that could be re-used by various server implementations
 */
class Helper
{
    /**
     * Parses HTTP request using PHP globals and returns GraphQL OperationParams
     * contained in this request. For batched requests it returns an array of OperationParams.
     *
     * This function does not check validity of these params
     * (validation is performed separately in validateOperationParams() method).
     *
     * If $readRawBodyFn argument is not provided - will attempt to read raw request body
     * from `php://input` stream.
     *
     * Internally it normalizes input to $method, $bodyParams and $queryParams and
     * calls `parseRequestParams()` to produce actual return value.
     *
     * For PSR-7 request parsing use `parsePsrRequest()` instead.
     *
     * @return OperationParams|OperationParams[]
     *
     * @throws RequestError
     *
     * @api
     */
    public function parseHttpRequest(?callable $readRawBodyFn = null)
    {
        $method     = $_SERVER['REQUEST_METHOD'] ?? null;
        $bodyParams = [];
        $urlParams  = $_GET;

        if ($method === 'POST') {
            $contentType = $_SERVER['CONTENT_TYPE'] ?? null;

            if ($contentType === null) {
                throw new RequestError('Missing "Content-Type" header');
            }

            if (stripos($contentType, 'application/graphql') !== false) {
                $rawBody    = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
                $bodyParams = ['query' => $rawBody ?: ''];
            } elseif (stripos($contentType, 'application/json') !== false) {
                $rawBody    = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
                $bodyParams = json_decode($rawBody ?: '', true);

                if (json_last_error()) {
                    throw new RequestError('Could not parse JSON: ' . json_last_error_msg());
                }

                if (! is_array($bodyParams)) {
                    throw new RequestError(
                        'GraphQL Server expects JSON object or array, but got ' .
                        Utils::printSafeJson($bodyParams)
                    );
                }
            } elseif (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
                $bodyParams = $_POST;
            } elseif (stripos($contentType, 'multipart/form-data') !== false) {
                $bodyParams = $_POST;
            } else {
                throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType));
            }
        }

        return $this->parseRequestParams($method, $bodyParams, $urlParams);
    }

    /**
     * Parses normalized request params and returns instance of OperationParams
     * or array of OperationParams in case of batch operation.
     *
     * Returned value is a suitable input for `executeOperation` or `executeBatch` (if array)
     *
     * @param string  $method
     * @param mixed[] $bodyParams
     * @param mixed[] $queryParams
     *
     * @return OperationParams|OperationParams[]
     *
     * @throws RequestError
     *
     * @api
     */
    public function parseRequestParams($method, array $bodyParams, array $queryParams)
    {
        if ($method === 'GET') {
            $result = OperationParams::create($queryParams, true);
        } elseif ($method === 'POST') {
            if (isset($bodyParams[0])) {
                $result = [];
                foreach ($bodyParams as $index => $entry) {
                    $op       = OperationParams::create($entry);
                    $result[] = $op;
                }
            } else {
                $result = OperationParams::create($bodyParams);
            }
        } else {
            throw new RequestError('HTTP Method "' . $method . '" is not supported');
        }

        return $result;
    }

    /**
     * Checks validity of OperationParams extracted from HTTP request and returns an array of errors
     * if params are invalid (or empty array when params are valid)
     *
     * @return Error[]
     *
     * @api
     */
    public function validateOperationParams(OperationParams $params)
    {
        $errors = [];
        if (! $params->query && ! $params->queryId) {
            $errors[] = new RequestError('GraphQL Request must include at least one of those two parameters: "query" or "queryId"');
        }

        if ($params->query && $params->queryId) {
            $errors[] = new RequestError('GraphQL Request parameters "query" and "queryId" are mutually exclusive');
        }

        if ($params->query !== null && (! is_string($params->query) || empty($params->query))) {
            $errors[] = new RequestError(
                'GraphQL Request parameter "query" must be string, but got ' .
                Utils::printSafeJson($params->query)
            );
        }

        if ($params->queryId !== null && (! is_string($params->queryId) || empty($params->queryId))) {
            $errors[] = new RequestError(
                'GraphQL Request parameter "queryId" must be string, but got ' .
                Utils::printSafeJson($params->queryId)
            );
        }

        if ($params->operation !== null && (! is_string($params->operation) || empty($params->operation))) {
            $errors[] = new RequestError(
                'GraphQL Request parameter "operation" must be string, but got ' .
                Utils::printSafeJson($params->operation)
            );
        }

        if ($params->variables !== null && (! is_array($params->variables) || isset($params->variables[0]))) {
            $errors[] = new RequestError(
                'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got ' .
                Utils::printSafeJson($params->getOriginalInput('variables'))
            );
        }

        return $errors;
    }

    /**
     * Executes GraphQL operation with given server configuration and returns execution result
     * (or promise when promise adapter is different from SyncPromiseAdapter)
     *
     * @return ExecutionResult|Promise
     *
     * @api
     */
    public function executeOperation(ServerConfig $config, OperationParams $op)
    {
        $promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter();
        $result         = $this->promiseToExecuteOperation($promiseAdapter, $config, $op);

        if ($promiseAdapter instanceof SyncPromiseAdapter) {
            $result = $promiseAdapter->wait($result);
        }

        return $result;
    }

    /**
     * Executes batched GraphQL operations with shared promise queue
     * (thus, effectively batching deferreds|promises of all queries at once)
     *
     * @param OperationParams[] $operations
     *
     * @return ExecutionResult|ExecutionResult[]|Promise
     *
     * @api
     */
    public function executeBatch(ServerConfig $config, array $operations)
    {
        $promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter();
        $result         = [];

        foreach ($operations as $operation) {
            $result[] = $this->promiseToExecuteOperation($promiseAdapter, $config, $operation, true);
        }

        $result = $promiseAdapter->all($result);

        // Wait for promised results when using sync promises
        if ($promiseAdapter instanceof SyncPromiseAdapter) {
            $result = $promiseAdapter->wait($result);
        }

        return $result;
    }

    /**
     * @param bool $isBatch
     *
     * @return Promise
     */
    private function promiseToExecuteOperation(
        PromiseAdapter $promiseAdapter,
        ServerConfig $config,
        OperationParams $op,
        $isBatch = false
    ) {
        try {
            if (! $config->getSchema()) {
                throw new InvariantViolation('Schema is required for the server');
            }

            if ($isBatch && ! $config->getQueryBatching()) {
                throw new RequestError('Batched queries are not supported by this server');
            }

            $errors = $this->validateOperationParams($op);

            if (! empty($errors)) {
                $errors = Utils::map(
                    $errors,
                    static function (RequestError $err) {
                        return Error::createLocatedError($err, null, null);
                    }
                );

                return $promiseAdapter->createFulfilled(
                    new ExecutionResult(null, $errors)
                );
            }

            $doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query;

            if (! $doc instanceof DocumentNode) {
                $doc = Parser::parse($doc);
            }

            $operationType = AST::getOperation($doc, $op->operation);
            if ($operationType !== 'query' && $op->isReadOnly()) {
                throw new RequestError('GET supports only query operation');
            }

            $result = GraphQL::promiseToExecute(
                $promiseAdapter,
                $config->getSchema(),
                $doc,
                $this->resolveRootValue($config, $op, $doc, $operationType),
                $this->resolveContextValue($config, $op, $doc, $operationType),
                $op->variables,
                $op->operation,
                $config->getFieldResolver(),
                $this->resolveValidationRules($config, $op, $doc, $operationType)
            );
        } catch (RequestError $e) {
            $result = $promiseAdapter->createFulfilled(
                new ExecutionResult(null, [Error::createLocatedError($e)])
            );
        } catch (Error $e) {
            $result = $promiseAdapter->createFulfilled(
                new ExecutionResult(null, [$e])
            );
        }

        $applyErrorHandling = static function (ExecutionResult $result) use ($config) {
            if ($config->getErrorsHandler()) {
                $result->setErrorsHandler($config->getErrorsHandler());
            }
            if ($config->getErrorFormatter() || $config->getDebug()) {
                $result->setErrorFormatter(
                    FormattedError::prepareFormatter(
                        $config->getErrorFormatter(),
                        $config->getDebug()
                    )
                );
            }

            return $result;
        };

        return $result->then($applyErrorHandling);
    }

    /**
     * @return mixed
     *
     * @throws RequestError
     */
    private function loadPersistedQuery(ServerConfig $config, OperationParams $operationParams)
    {
        // Load query if we got persisted query id:
        $loader = $config->getPersistentQueryLoader();

        if (! $loader) {
            throw new RequestError('Persisted queries are not supported by this server');
        }

        $source = $loader($operationParams->queryId, $operationParams);

        if (! is_string($source) && ! $source instanceof DocumentNode) {
            throw new InvariantViolation(sprintf(
                'Persistent query loader must return query string or instance of %s but got: %s',
                DocumentNode::class,
                Utils::printSafe($source)
            ));
        }

        return $source;
    }

    /**
     * @param string $operationType
     *
     * @return mixed[]|null
     */
    private function resolveValidationRules(
        ServerConfig $config,
        OperationParams $params,
        DocumentNode $doc,
        $operationType
    ) {
        // Allow customizing validation rules per operation:
        $validationRules = $config->getValidationRules();

        if (is_callable($validationRules)) {
            $validationRules = $validationRules($params, $doc, $operationType);

            if (! is_array($validationRules)) {
                throw new InvariantViolation(sprintf(
                    'Expecting validation rules to be array or callable returning array, but got: %s',
                    Utils::printSafe($validationRules)
                ));
            }
        }

        return $validationRules;
    }

    /**
     * @param string $operationType
     *
     * @return mixed
     */
    private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType)
    {
        $root = $config->getRootValue();

        if (is_callable($root)) {
            $root = $root($params, $doc, $operationType);
        }

        return $root;
    }

    /**
     * @param string $operationType
     *
     * @return mixed
     */
    private function resolveContextValue(
        ServerConfig $config,
        OperationParams $params,
        DocumentNode $doc,
        $operationType
    ) {
        $context = $config->getContext();

        if (is_callable($context)) {
            $context = $context($params, $doc, $operationType);
        }

        return $context;
    }

    /**
     * Send response using standard PHP `header()` and `echo`.
     *
     * @param Promise|ExecutionResult|ExecutionResult[] $result
     * @param bool                                      $exitWhenDone
     *
     * @api
     */
    public function sendResponse($result, $exitWhenDone = false)
    {
        if ($result instanceof Promise) {
            $result->then(function ($actualResult) use ($exitWhenDone) {
                $this->doSendResponse($actualResult, $exitWhenDone);
            });
        } else {
            $this->doSendResponse($result, $exitWhenDone);
        }
    }

    private function doSendResponse($result, $exitWhenDone)
    {
        $httpStatus = $this->resolveHttpStatus($result);
        $this->emitResponse($result, $httpStatus, $exitWhenDone);
    }

    /**
     * @param mixed[]|JsonSerializable $jsonSerializable
     * @param int                      $httpStatus
     * @param bool                     $exitWhenDone
     */
    public function emitResponse($jsonSerializable, $httpStatus, $exitWhenDone)
    {
        $body = json_encode($jsonSerializable);
        header('Content-Type: application/json', true, $httpStatus);
        echo $body;

        if ($exitWhenDone) {
            exit;
        }
    }

    /**
     * @return bool|string
     */
    private function readRawBody()
    {
        return file_get_contents('php://input');
    }

    /**
     * @param ExecutionResult|mixed[] $result
     *
     * @return int
     */
    private function resolveHttpStatus($result)
    {
        if (is_array($result) && isset($result[0])) {
            Utils::each(
                $result,
                static function ($executionResult, $index) {
                    if (! $executionResult instanceof ExecutionResult) {
                        throw new InvariantViolation(sprintf(
                            'Expecting every entry of batched query result to be instance of %s but entry at position %d is %s',
                            ExecutionResult::class,
                            $index,
                            Utils::printSafe($executionResult)
                        ));
                    }
                }
            );
            $httpStatus = 200;
        } else {
            if (! $result instanceof ExecutionResult) {
                throw new InvariantViolation(sprintf(
                    'Expecting query result to be instance of %s but got %s',
                    ExecutionResult::class,
                    Utils::printSafe($result)
                ));
            }
            if ($result->data === null && ! empty($result->errors)) {
                $httpStatus = 400;
            } else {
                $httpStatus = 200;
            }
        }

        return $httpStatus;
    }

    /**
     * Converts PSR-7 request to OperationParams[]
     *
     * @return OperationParams[]|OperationParams
     *
     * @throws RequestError
     *
     * @api
     */
    public function parsePsrRequest(ServerRequestInterface $request)
    {
        if ($request->getMethod() === 'GET') {
            $bodyParams = [];
        } else {
            $contentType = $request->getHeader('content-type');

            if (! isset($contentType[0])) {
                throw new RequestError('Missing "Content-Type" header');
            }

            if (stripos($contentType[0], 'application/graphql') !== false) {
                $bodyParams = ['query' => $request->getBody()->getContents()];
            } elseif (stripos($contentType[0], 'application/json') !== false) {
                $bodyParams = $request->getParsedBody();

                if ($bodyParams === null) {
                    throw new InvariantViolation(
                        'PSR-7 request is expected to provide parsed body for "application/json" requests but got null'
                    );
                }

                if (! is_array($bodyParams)) {
                    throw new RequestError(
                        'GraphQL Server expects JSON object or array, but got ' .
                        Utils::printSafeJson($bodyParams)
                    );
                }
            } else {
                $bodyParams = $request->getParsedBody();

                if (! is_array($bodyParams)) {
                    throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType[0]));
                }
            }
        }

        return $this->parseRequestParams(
            $request->getMethod(),
            $bodyParams,
            $request->getQueryParams()
        );
    }

    /**
     * Converts query execution result to PSR-7 response
     *
     * @param Promise|ExecutionResult|ExecutionResult[] $result
     *
     * @return Promise|ResponseInterface
     *
     * @api
     */
    public function toPsrResponse($result, ResponseInterface $response, StreamInterface $writableBodyStream)
    {
        if ($result instanceof Promise) {
            return $result->then(function ($actualResult) use ($response, $writableBodyStream) {
                return $this->doConvertToPsrResponse($actualResult, $response, $writableBodyStream);
            });
        }

        return $this->doConvertToPsrResponse($result, $response, $writableBodyStream);
    }

    private function doConvertToPsrResponse($result, ResponseInterface $response, StreamInterface $writableBodyStream)
    {
        $httpStatus = $this->resolveHttpStatus($result);

        $result = json_encode($result);
        $writableBodyStream->write($result);

        return $response
            ->withStatus($httpStatus)
            ->withHeader('Content-Type', 'application/json')
            ->withBody($writableBodyStream);
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Language\AST;

use GraphQL\Utils\Utils;
use function get_object_vars;
use function is_array;
use function is_scalar;
use function json_encode;

/**
 * type Node = NameNode
 * | DocumentNode
 * | OperationDefinitionNode
 * | VariableDefinitionNode
 * | VariableNode
 * | SelectionSetNode
 * | FieldNode
 * | ArgumentNode
 * | FragmentSpreadNode
 * | InlineFragmentNode
 * | FragmentDefinitionNode
 * | IntValueNode
 * | FloatValueNode
 * | StringValueNode
 * | BooleanValueNode
 * | EnumValueNode
 * | ListValueNode
 * | ObjectValueNode
 * | ObjectFieldNode
 * | DirectiveNode
 * | ListTypeNode
 * | NonNullTypeNode
 */
abstract class Node
{
    /** @var Location */
    public $loc;

    /**
     * @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
     */
    public function __construct(array $vars)
    {
        if (empty($vars)) {
            return;
        }

        Utils::assign($this, $vars);
    }

    /**
     * @return self
     */
    public function cloneDeep()
    {
        return $this->cloneValue($this);
    }

    /**
     * @param string|NodeList|Location|Node|(Node|NodeList|Location)[] $value
     *
     * @return string|NodeList|Location|Node
     */
    private function cloneValue($value)
    {
        if (is_array($value)) {
            $cloned = [];
            foreach ($value as $key => $arrValue) {
                $cloned[$key] = $this->cloneValue($arrValue);
            }
        } elseif ($value instanceof self) {
            $cloned = clone $value;
            foreach (get_object_vars($cloned) as $prop => $propValue) {
                $cloned->{$prop} = $this->cloneValue($propValue);
            }
        } else {
            $cloned = $value;
        }

        return $cloned;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $tmp = $this->toArray(true);

        return (string) json_encode($tmp);
    }

    /**
     * @param bool $recursive
     *
     * @return mixed[]
     */
    public function toArray($recursive = false)
    {
        if ($recursive) {
            return $this->recursiveToArray($this);
        }

        $tmp = (array) $this;

        if ($this->loc) {
            $tmp['loc'] = [
                'start' => $this->loc->start,
                'end'   => $this->loc->end,
            ];
        }

        return $tmp;
    }

    /**
     * @return mixed[]
     */
    private function recursiveToArray(Node $node)
    {
        $result = [
            'kind' => $node->kind,
        ];

        if ($node->loc) {
            $result['loc'] = [
                'start' => $node->loc->start,
                'end'   => $node->loc->end,
            ];
        }

        foreach (get_object_vars($node) as $prop => $propValue) {
            if (isset($result[$prop])) {
                continue;
            }

            if ($propValue === null) {
                continue;
            }

            if (is_array($propValue) || $propValue instanceof NodeList) {
                $tmp = [];
                foreach ($propValue as $tmp1) {
                    $tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
                }
            } elseif ($propValue instanceof Node) {
                $tmp = $this->recursiveToArray($propValue);
            } elseif (is_scalar($propValue) || $propValue === null) {
                $tmp = $propValue;
            } else {
                $tmp = null;
            }

            $result[$prop] = $tmp;
        }

        return $result;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils;
use function is_numeric;

class FloatType extends ScalarType
{
    /** @var string */
    public $name = Type::FLOAT;

    /** @var string */
    public $description =
        'The `Float` scalar type represents signed double-precision fractional
values as specified by
[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ';

    /**
     * @param mixed $value
     *
     * @return float|null
     *
     * @throws Error
     */
    public function serialize($value)
    {
        return $this->coerceFloat($value);
    }

    private function coerceFloat($value)
    {
        if ($value === '') {
            throw new Error(
                'Float cannot represent non numeric value: (empty string)'
            );
        }

        if (! is_numeric($value) && $value !== true && $value !== false) {
            throw new Error(
                'Float cannot represent non numeric value: ' .
                Utils::printSafe($value)
            );
        }

        return (float) $value;
    }

    /**
     * @param mixed $value
     *
     * @return float|null
     *
     * @throws Error
     */
    public function parseValue($value)
    {
        return $this->coerceFloat($value);
    }

    /**
     * @param Node         $valueNode
     * @param mixed[]|null $variables
     *
     * @return float|null
     *
     * @throws Exception
     */
    public function parseLiteral($valueNode, ?array $variables = null)
    {
        if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) {
            return (float) $valueNode->value;
        }

        // Intentionally without message, as all information already in wrapped Exception
        throw new Exception();
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;

class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
{
    /** @var UnionTypeDefinitionNode */
    public $astNode;

    /** @var ObjectType[] */
    private $types;

    /** @var ObjectType[] */
    private $possibleTypeNames;

    /** @var UnionTypeExtensionNode[] */
    public $extensionASTNodes;

    public function __construct($config)
    {
        if (! isset($config['name'])) {
            $config['name'] = $this->tryInferName();
        }

        Utils::invariant(is_string($config['name']), 'Must provide name.');

        /**
         * Optionally provide a custom type resolver function. If one is not provided,
         * the default implemenation will call `isTypeOf` on each implementing
         * Object type.
         */
        $this->name              = $config['name'];
        $this->description       = $config['description'] ?? null;
        $this->astNode           = $config['astNode'] ?? null;
        $this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
        $this->config            = $config;
    }

    public function isPossibleType(Type $type) : bool
    {
        if (! $type instanceof ObjectType) {
            return false;
        }

        if ($this->possibleTypeNames === null) {
            $this->possibleTypeNames = [];
            foreach ($this->getTypes() as $possibleType) {
                $this->possibleTypeNames[$possibleType->name] = true;
            }
        }

        return isset($this->possibleTypeNames[$type->name]);
    }

    /**
     * @return ObjectType[]
     */
    public function getTypes()
    {
        if ($this->types === null) {
            if (! isset($this->config['types'])) {
                $types = null;
            } elseif (is_callable($this->config['types'])) {
                $types = call_user_func($this->config['types']);
            } else {
                $types = $this->config['types'];
            }

            if (! is_array($types)) {
                throw new InvariantViolation(
                    sprintf(
                        'Must provide Array of types or a callable which returns such an array for Union %s',
                        $this->name
                    )
                );
            }

            $this->types = $types;
        }

        return $this->types;
    }

    /**
     * Resolves concrete ObjectType for given object value
     *
     * @param object $objectValue
     * @param mixed  $context
     *
     * @return callable|null
     */
    public function resolveType($objectValue, $context, ResolveInfo $info)
    {
        if (isset($this->config['resolveType'])) {
            $fn = $this->config['resolveType'];

            return $fn($objectValue, $context, $info);
        }

        return null;
    }

    /**
     * @throws InvariantViolation
     */
    public function assertValid()
    {
        parent::assertValid();

        if (! isset($this->config['resolveType'])) {
            return;
        }

        Utils::invariant(
            is_callable($this->config['resolveType']),
            sprintf(
                '%s must provide "resolveType" as a function, but got: %s',
                $this->name,
                Utils::printSafe($this->config['resolveType'])
            )
        );
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Utils;

use ErrorException;
use Exception;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\WrappingType;
use InvalidArgumentException;
use LogicException;
use stdClass;
use Traversable;
use function array_keys;
use function array_map;
use function array_reduce;
use function array_shift;
use function array_slice;
use function array_values;
use function asort;
use function chr;
use function count;
use function dechex;
use function func_get_args;
use function func_num_args;
use function get_class;
use function gettype;
use function is_array;
use function is_int;
use function is_object;
use function is_scalar;
use function is_string;
use function json_encode;
use function levenshtein;
use function max;
use function mb_convert_encoding;
use function mb_strlen;
use function mb_substr;
use function method_exists;
use function ord;
use function pack;
use function preg_match;
use function property_exists;
use function range;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function strtolower;
use function unpack;

class Utils
{
    public static function undefined()
    {
        static $undefined;

        return $undefined ?: $undefined = new stdClass();
    }

    /**
     * Check if the value is invalid
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function isInvalid($value)
    {
        return self::undefined() === $value;
    }

    /**
     * @param object   $obj
     * @param mixed[]  $vars
     * @param string[] $requiredKeys
     *
     * @return object
     */
    public static function assign($obj, array $vars, array $requiredKeys = [])
    {
        foreach ($requiredKeys as $key) {
            if (! isset($vars[$key])) {
                throw new InvalidArgumentException(sprintf('Key %s is expected to be set and not to be null', $key));
            }
        }

        foreach ($vars as $key => $value) {
            if (! property_exists($obj, $key)) {
                $cls = get_class($obj);
                Warning::warn(
                    sprintf("Trying to set non-existing property '%s' on class '%s'", $key, $cls),
                    Warning::WARNING_ASSIGN
                );
            }
            $obj->{$key} = $value;
        }

        return $obj;
    }

    /**
     * @param mixed|Traversable $traversable
     *
     * @return mixed|null
     */
    public static function find($traversable, callable $predicate)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        foreach ($traversable as $key => $value) {
            if ($predicate($value, $key)) {
                return $value;
            }
        }

        return null;
    }

    /**
     * @param mixed|Traversable $traversable
     *
     * @return mixed[]
     *
     * @throws Exception
     */
    public static function filter($traversable, callable $predicate)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        $result = [];
        $assoc  = false;
        foreach ($traversable as $key => $value) {
            if (! $assoc && ! is_int($key)) {
                $assoc = true;
            }
            if (! $predicate($value, $key)) {
                continue;
            }

            $result[$key] = $value;
        }

        return $assoc ? $result : array_values($result);
    }

    /**
     * @param mixed|Traversable $traversable
     *
     * @return mixed[]
     *
     * @throws Exception
     */
    public static function map($traversable, callable $fn)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        $map = [];
        foreach ($traversable as $key => $value) {
            $map[$key] = $fn($value, $key);
        }

        return $map;
    }

    /**
     * @param mixed|Traversable $traversable
     *
     * @return mixed[]
     *
     * @throws Exception
     */
    public static function mapKeyValue($traversable, callable $fn)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        $map = [];
        foreach ($traversable as $key => $value) {
            [$newKey, $newValue] = $fn($value, $key);
            $map[$newKey]        = $newValue;
        }

        return $map;
    }

    /**
     * @param mixed|Traversable $traversable
     *
     * @return mixed[]
     *
     * @throws Exception
     */
    public static function keyMap($traversable, callable $keyFn)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        $map = [];
        foreach ($traversable as $key => $value) {
            $newKey = $keyFn($value, $key);
            if (! is_scalar($newKey)) {
                continue;
            }

            $map[$newKey] = $value;
        }

        return $map;
    }

    public static function each($traversable, callable $fn)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        foreach ($traversable as $key => $item) {
            $fn($item, $key);
        }
    }

    /**
     * Splits original traversable to several arrays with keys equal to $keyFn return
     *
     * E.g. Utils::groupBy([1, 2, 3, 4, 5], function($value) {return $value % 3}) will output:
     * [
     *    1 => [1, 4],
     *    2 => [2, 5],
     *    0 => [3],
     * ]
     *
     * $keyFn is also allowed to return array of keys. Then value will be added to all arrays with given keys
     *
     * @param mixed[]|Traversable $traversable
     *
     * @return mixed[]
     */
    public static function groupBy($traversable, callable $keyFn)
    {
        self::invariant(
            is_array($traversable) || $traversable instanceof Traversable,
            __METHOD__ . ' expects array or Traversable'
        );

        $grouped = [];
        foreach ($traversable as $key => $value) {
            $newKeys = (array) $keyFn($value, $key);
            foreach ($newKeys as $newKey) {
                $grouped[$newKey][] = $value;
            }
        }

        return $grouped;
    }

    /**
     * @param mixed[]|Traversable $traversable
     *
     * @return mixed[][]
     */
    public static function keyValMap($traversable, callable $keyFn, callable $valFn)
    {
        $map = [];
        foreach ($traversable as $item) {
            $map[$keyFn($item)] = $valFn($item);
        }

        return $map;
    }

    /**
     * @param mixed[] $traversable
     *
     * @return bool
     */
    public static function every($traversable, callable $predicate)
    {
        foreach ($traversable as $key => $value) {
            if (! $predicate($value, $key)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param bool   $test
     * @param string $message
     */
    public static function invariant($test, $message = '')
    {
        if (! $test) {
            if (func_num_args() > 2) {
                $args = func_get_args();
                array_shift($args);
                $message = sprintf(...$args);
            }
            // TODO switch to Error here
            throw new InvariantViolation($message);
        }
    }

    /**
     * @param Type|mixed $var
     *
     * @return string
     */
    public static function getVariableType($var)
    {
        if ($var instanceof Type) {
            // FIXME: Replace with schema printer call
            if ($var instanceof WrappingType) {
                $var = $var->getWrappedType(true);
            }

            return $var->name;
        }

        return is_object($var) ? get_class($var) : gettype($var);
    }

    /**
     * @param mixed $var
     *
     * @return string
     */
    public static function printSafeJson($var)
    {
        if ($var instanceof stdClass) {
            $var = (array) $var;
        }
        if (is_array($var)) {
            return json_encode($var);
        }
        if ($var === '') {
            return '(empty string)';
        }
        if ($var === null) {
            return 'null';
        }
        if ($var === false) {
            return 'false';
        }
        if ($var === true) {
            return 'true';
        }
        if (is_string($var)) {
            return sprintf('"%s"', $var);
        }
        if (is_scalar($var)) {
            return (string) $var;
        }

        return gettype($var);
    }

    /**
     * @param Type|mixed $var
     *
     * @return string
     */
    public static function printSafe($var)
    {
        if ($var instanceof Type) {
            return $var->toString();
        }
        if (is_object($var)) {
            if (method_exists($var, '__toString')) {
                return (string) $var;
            }

            return 'instance of ' . get_class($var);
        }
        if (is_array($var)) {
            return json_encode($var);
        }
        if ($var === '') {
            return '(empty string)';
        }
        if ($var === null) {
            return 'null';
        }
        if ($var === false) {
            return 'false';
        }
        if ($var === true) {
            return 'true';
        }
        if (is_string($var)) {
            return $var;
        }
        if (is_scalar($var)) {
            return (string) $var;
        }

        return gettype($var);
    }

    /**
     * UTF-8 compatible chr()
     *
     * @param string $ord
     * @param string $encoding
     *
     * @return string
     */
    public static function chr($ord, $encoding = 'UTF-8')
    {
        if ($ord <= 255) {
            return chr($ord);
        }
        if ($encoding === 'UCS-4BE') {
            return pack('N', $ord);
        }

        return mb_convert_encoding(self::chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE');
    }

    /**
     * UTF-8 compatible ord()
     *
     * @param string $char
     * @param string $encoding
     *
     * @return mixed
     */
    public static function ord($char, $encoding = 'UTF-8')
    {
        if (! $char && $char !== '0') {
            return 0;
        }
        if (! isset($char[1])) {
            return ord($char);
        }
        if ($encoding !== 'UCS-4BE') {
            $char = mb_convert_encoding($char, 'UCS-4BE', $encoding);
        }

        return unpack('N', $char)[1];
    }

    /**
     * Returns UTF-8 char code at given $positing of the $string
     *
     * @param string $string
     * @param int    $position
     *
     * @return mixed
     */
    public static function charCodeAt($string, $position)
    {
        $char = mb_substr($string, $position, 1, 'UTF-8');

        return self::ord($char);
    }

    /**
     * @param int|null $code
     *
     * @return string
     */
    public static function printCharCode($code)
    {
        if ($code === null) {
            return '<EOF>';
        }

        return $code < 0x007F
            // Trust JSON for ASCII.
            ? json_encode(self::chr($code))
            // Otherwise print the escaped form.
            : '"\\u' . dechex($code) . '"';
    }

    /**
     * Upholds the spec rules about naming.
     *
     * @param string $name
     *
     * @throws Error
     */
    public static function assertValidName($name)
    {
        $error = self::isValidNameError($name);
        if ($error) {
            throw $error;
        }
    }

    /**
     * Returns an Error if a name is invalid.
     *
     * @param string    $name
     * @param Node|null $node
     *
     * @return Error|null
     */
    public static function isValidNameError($name, $node = null)
    {
        self::invariant(is_string($name), 'Expected string');

        if (isset($name[1]) && $name[0] === '_' && $name[1] === '_') {
            return new Error(
                sprintf('Name "%s" must not begin with "__", which is reserved by ', $name) .
                'GraphQL introspection.',
                $node
            );
        }

        if (! preg_match('/^[_a-zA-Z][_a-zA-Z0-9]*$/', $name)) {
            return new Error(
                sprintf('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%s" does not.', $name),
                $node
            );
        }

        return null;
    }

    /**
     * Wraps original callable with PHP error handling (using set_error_handler).
     * Resulting callable will collect all PHP errors that occur during the call in $errors array.
     *
     * @param ErrorException[] $errors
     *
     * @return callable
     */
    public static function withErrorHandling(callable $fn, array &$errors)
    {
        return static function () use ($fn, &$errors) {
            // Catch custom errors (to report them in query results)
            set_error_handler(static function ($severity, $message, $file, $line) use (&$errors) {
                $errors[] = new ErrorException($message, 0, $severity, $file, $line);
            });

            try {
                return $fn();
            } finally {
                restore_error_handler();
            }
        };
    }

    /**
     * @param string[] $items
     *
     * @return string
     */
    public static function quotedOrList(array $items)
    {
        $items = array_map(
            static function ($item) {
                return sprintf('"%s"', $item);
            },
            $items
        );

        return self::orList($items);
    }

    /**
     * @param string[] $items
     *
     * @return string
     */
    public static function orList(array $items)
    {
        if (count($items) === 0) {
            throw new LogicException('items must not need to be empty.');
        }
        $selected       = array_slice($items, 0, 5);
        $selectedLength = count($selected);
        $firstSelected  = $selected[0];

        if ($selectedLength === 1) {
            return $firstSelected;
        }

        return array_reduce(
            range(1, $selectedLength - 1),
            static function ($list, $index) use ($selected, $selectedLength) {
                return $list .
                    ($selectedLength > 2 ? ', ' : ' ') .
                    ($index === $selectedLength - 1 ? 'or ' : '') .
                    $selected[$index];
            },
            $firstSelected
        );
    }

    /**
     * Given an invalid input string and a list of valid options, returns a filtered
     * list of valid options sorted based on their similarity with the input.
     *
     * Includes a custom alteration from Damerau-Levenshtein to treat case changes
     * as a single edit which helps identify mis-cased values with an edit distance
     * of 1
     *
     * @param string   $input
     * @param string[] $options
     *
     * @return string[]
     */
    public static function suggestionList($input, array $options)
    {
        $optionsByDistance = [];
        $inputThreshold    = mb_strlen($input) / 2;
        foreach ($options as $option) {
            if ($input === $option) {
                $distance = 0;
            } else {
                $distance = (strtolower($input) === strtolower($option)
                    ? 1
                    : levenshtein($input, $option));
            }
            $threshold = max($inputThreshold, mb_strlen($option) / 2, 1);
            if ($distance > $threshold) {
                continue;
            }

            $optionsByDistance[$option] = $distance;
        }

        asort($optionsByDistance);

        return array_keys($optionsByDistance);
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Executor;

use ArrayAccess;
use Closure;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Schema;
use function is_array;
use function is_object;

/**
 * Implements the "Evaluating requests" section of the GraphQL specification.
 */
class Executor
{
    /** @var callable|string[] */
    private static $defaultFieldResolver = [self::class, 'defaultFieldResolver'];

    /** @var PromiseAdapter */
    private static $defaultPromiseAdapter;

    /** @var callable */
    private static $implementationFactory = [ReferenceExecutor::class, 'create'];

    public static function getDefaultFieldResolver() : callable
    {
        return self::$defaultFieldResolver;
    }

    /**
     * Custom default resolve function.
     */
    public static function setDefaultFieldResolver(callable $fieldResolver)
    {
        self::$defaultFieldResolver = $fieldResolver;
    }

    public static function getPromiseAdapter() : PromiseAdapter
    {
        return self::$defaultPromiseAdapter ?: (self::$defaultPromiseAdapter = new SyncPromiseAdapter());
    }

    public static function setPromiseAdapter(?PromiseAdapter $defaultPromiseAdapter = null)
    {
        self::$defaultPromiseAdapter = $defaultPromiseAdapter;
    }

    public static function getImplementationFactory() : callable
    {
        return self::$implementationFactory;
    }

    /**
     * Custom executor implementation factory.
     *
     * Will be called with as
     */
    public static function setImplementationFactory(callable $implementationFactory)
    {
        self::$implementationFactory = $implementationFactory;
    }

    /**
     * Executes DocumentNode against given $schema.
     *
     * Always returns ExecutionResult and never throws. All errors which occur during operation
     * execution are collected in `$result->errors`.
     *
     * @param mixed|null               $rootValue
     * @param mixed|null               $contextValue
     * @param mixed[]|ArrayAccess|null $variableValues
     * @param string|null              $operationName
     *
     * @return ExecutionResult|Promise
     *
     * @api
     */
    public static function execute(
        Schema $schema,
        DocumentNode $documentNode,
        $rootValue = null,
        $contextValue = null,
        $variableValues = null,
        $operationName = null,
        ?callable $fieldResolver = null
    ) {
        // TODO: deprecate (just always use SyncAdapter here) and have `promiseToExecute()` for other cases

        $promiseAdapter = static::getPromiseAdapter();

        $result = static::promiseToExecute(
            $promiseAdapter,
            $schema,
            $documentNode,
            $rootValue,
            $contextValue,
            $variableValues,
            $operationName,
            $fieldResolver
        );

        if ($promiseAdapter instanceof SyncPromiseAdapter) {
            $result = $promiseAdapter->wait($result);
        }

        return $result;
    }

    /**
     * Same as execute(), but requires promise adapter and returns a promise which is always
     * fulfilled with an instance of ExecutionResult and never rejected.
     *
     * Useful for async PHP platforms.
     *
     * @param mixed|null   $rootValue
     * @param mixed|null   $contextValue
     * @param mixed[]|null $variableValues
     * @param string|null  $operationName
     *
     * @return Promise
     *
     * @api
     */
    public static function promiseToExecute(
        PromiseAdapter $promiseAdapter,
        Schema $schema,
        DocumentNode $documentNode,
        $rootValue = null,
        $contextValue = null,
        $variableValues = null,
        $operationName = null,
        ?callable $fieldResolver = null
    ) {
        $factory = self::$implementationFactory;

        /** @var ExecutorImplementation $executor */
        $executor = $factory(
            $promiseAdapter,
            $schema,
            $documentNode,
            $rootValue,
            $contextValue,
            $variableValues,
            $operationName,
            $fieldResolver ?: self::$defaultFieldResolver
        );

        return $executor->doExecute();
    }

    /**
     * If a resolve function is not given, then a default resolve behavior is used
     * which takes the property of the source object of the same name as the field
     * and returns it as the result, or if it's a function, returns the result
     * of calling that function while passing along args and context.
     *
     * @param mixed      $source
     * @param mixed[]    $args
     * @param mixed|null $context
     *
     * @return mixed|null
     */
    public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
    {
        $fieldName = $info->fieldName;
        $property  = null;

        if (is_array($source) || $source instanceof ArrayAccess) {
            if (isset($source[$fieldName])) {
                $property = $source[$fieldName];
            }
        } elseif (is_object($source)) {
            if (isset($source->{$fieldName})) {
                $property = $source->{$fieldName};
            }
        }

        return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
    }
}