![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/webonyx/graphql-php/src/Utils/ |
<?php declare(strict_types=1); namespace GraphQL\Utils; use GraphQL\Error\InvariantViolation; use GraphQL\Error\SyntaxError; use GraphQL\Language\Parser; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputObjectField; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Type\TypeKind; /** * @phpstan-import-type UnnamedFieldDefinitionConfig from FieldDefinition * @phpstan-import-type UnnamedInputObjectFieldConfig from InputObjectField * * @phpstan-type Options array{ * assumeValid?: bool * } * * - assumeValid: * When building a schema from a GraphQL service's introspection result, it * might be safe to assume the schema is valid. Set to true to assume the * produced schema is valid. * * Default: false * * @see \GraphQL\Tests\Utils\BuildClientSchemaTest */ class BuildClientSchema { /** @var array<string, mixed> */ private array $introspection; /** * @var array<string, bool> * * @phpstan-var Options */ private array $options; /** @var array<string, NamedType&Type> */ private array $typeMap = []; /** * @param array<string, mixed> $introspectionQuery * @param array<string, bool> $options * * @phpstan-param Options $options */ public function __construct(array $introspectionQuery, array $options = []) { $this->introspection = $introspectionQuery; $this->options = $options; } /** * Build a schema for use by client tools. * * Given the result of a client running the introspection query, creates and * returns a \GraphQL\Type\Schema instance which can be then used with all graphql-php * tools, but cannot be used to execute a query, as introspection does not * represent the "resolver", "parse" or "serialize" functions or any other * server-internal mechanisms. * * This function expects a complete introspection result. Don't forget to check * the "errors" field of a server response before calling this function. * * @param array<string, mixed> $introspectionQuery * @param array<string, bool> $options * * @phpstan-param Options $options * * @api * * @throws InvariantViolation */ public static function build(array $introspectionQuery, array $options = []): Schema { return (new self($introspectionQuery, $options))->buildSchema(); } /** @throws InvariantViolation */ public function buildSchema(): Schema { if (! \array_key_exists('__schema', $this->introspection)) { $missingSchemaIntrospection = Utils::printSafeJson($this->introspection); throw new InvariantViolation("Invalid or incomplete introspection result. Ensure that you are passing \"data\" property of introspection response and no \"errors\" was returned alongside: {$missingSchemaIntrospection}."); } $schemaIntrospection = $this->introspection['__schema']; $builtInTypes = \array_merge( Type::getStandardTypes(), Introspection::getTypes() ); foreach ($schemaIntrospection['types'] as $typeIntrospection) { if (! isset($typeIntrospection['name'])) { throw self::invalidOrIncompleteIntrospectionResult($typeIntrospection); } $name = $typeIntrospection['name']; if (! is_string($name)) { throw self::invalidOrIncompleteIntrospectionResult($typeIntrospection); } // Use the built-in singleton types to avoid reconstruction $this->typeMap[$name] = $builtInTypes[$name] ?? $this->buildType($typeIntrospection); } $queryType = isset($schemaIntrospection['queryType']) ? $this->getObjectType($schemaIntrospection['queryType']) : null; $mutationType = isset($schemaIntrospection['mutationType']) ? $this->getObjectType($schemaIntrospection['mutationType']) : null; $subscriptionType = isset($schemaIntrospection['subscriptionType']) ? $this->getObjectType($schemaIntrospection['subscriptionType']) : null; $directives = isset($schemaIntrospection['directives']) ? \array_map( [$this, 'buildDirective'], $schemaIntrospection['directives'] ) : []; return new Schema( (new SchemaConfig()) ->setQuery($queryType) ->setMutation($mutationType) ->setSubscription($subscriptionType) ->setTypes($this->typeMap) ->setDirectives($directives) ->setAssumeValid($this->options['assumeValid'] ?? false) ); } /** * @param array<string, mixed> $typeRef * * @throws InvariantViolation */ private function getType(array $typeRef): Type { if (isset($typeRef['kind'])) { if ($typeRef['kind'] === TypeKind::LIST) { if (! isset($typeRef['ofType'])) { throw new InvariantViolation('Decorated type deeper than introspection query.'); } return new ListOfType($this->getType($typeRef['ofType'])); } if ($typeRef['kind'] === TypeKind::NON_NULL) { if (! isset($typeRef['ofType'])) { throw new InvariantViolation('Decorated type deeper than introspection query.'); } // @phpstan-ignore-next-line if the type is not a nullable type, schema validation will catch it return new NonNull($this->getType($typeRef['ofType'])); } } if (! isset($typeRef['name'])) { $unknownTypeRef = Utils::printSafeJson($typeRef); throw new InvariantViolation("Unknown type reference: {$unknownTypeRef}."); } return $this->getNamedType($typeRef['name']); } /** * @throws InvariantViolation * * @return NamedType&Type */ private function getNamedType(string $typeName): NamedType { if (! isset($this->typeMap[$typeName])) { throw new InvariantViolation("Invalid or incomplete schema, unknown type: {$typeName}. Ensure that a full introspection query is used in order to build a client schema."); } return $this->typeMap[$typeName]; } /** @param array<mixed> $type */ public static function invalidOrIncompleteIntrospectionResult(array $type): InvariantViolation { $incompleteType = Utils::printSafeJson($type); return new InvariantViolation("Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {$incompleteType}."); } /** * @param array<string, mixed> $typeRef * * @throws InvariantViolation * * @return Type&InputType */ private function getInputType(array $typeRef): InputType { $type = $this->getType($typeRef); if ($type instanceof InputType) { return $type; } $notInputType = Utils::printSafe($type); throw new InvariantViolation("Introspection must provide input type for arguments, but received: {$notInputType}."); } /** * @param array<string, mixed> $typeRef * * @throws InvariantViolation */ private function getOutputType(array $typeRef): OutputType { $type = $this->getType($typeRef); if ($type instanceof OutputType) { return $type; } $notInputType = Utils::printSafe($type); throw new InvariantViolation("Introspection must provide output type for fields, but received: {$notInputType}."); } /** * @param array<string, mixed> $typeRef * * @throws InvariantViolation */ private function getObjectType(array $typeRef): ObjectType { $type = $this->getType($typeRef); return ObjectType::assertObjectType($type); } /** * @param array<string, mixed> $typeRef * * @throws InvariantViolation */ public function getInterfaceType(array $typeRef): InterfaceType { $type = $this->getType($typeRef); return InterfaceType::assertInterfaceType($type); } /** * @param array<string, mixed> $type * * @throws InvariantViolation * * @return Type&NamedType */ private function buildType(array $type): NamedType { if (! \array_key_exists('kind', $type)) { throw self::invalidOrIncompleteIntrospectionResult($type); } switch ($type['kind']) { case TypeKind::SCALAR: return $this->buildScalarDef($type); case TypeKind::OBJECT: return $this->buildObjectDef($type); case TypeKind::INTERFACE: return $this->buildInterfaceDef($type); case TypeKind::UNION: return $this->buildUnionDef($type); case TypeKind::ENUM: return $this->buildEnumDef($type); case TypeKind::INPUT_OBJECT: return $this->buildInputObjectDef($type); default: $unknownKindType = Utils::printSafeJson($type); throw new InvariantViolation("Invalid or incomplete introspection result. Received type with unknown kind: {$unknownKindType}."); } } /** * @param array<string, string> $scalar * * @throws InvariantViolation */ private function buildScalarDef(array $scalar): ScalarType { return new CustomScalarType([ 'name' => $scalar['name'], 'description' => $scalar['description'], 'serialize' => static fn ($value): string => (string) $value, ]); } /** * @param array<string, mixed> $implementingIntrospection * * @throws InvariantViolation * * @return array<int, InterfaceType> */ private function buildImplementationsList(array $implementingIntrospection): array { // TODO: Temporary workaround until GraphQL ecosystem will fully support 'interfaces' on interface types. if ( \array_key_exists('interfaces', $implementingIntrospection) && $implementingIntrospection['interfaces'] === null && $implementingIntrospection['kind'] === TypeKind::INTERFACE ) { return []; } if (! \array_key_exists('interfaces', $implementingIntrospection)) { $safeIntrospection = Utils::printSafeJson($implementingIntrospection); throw new InvariantViolation("Introspection result missing interfaces: {$safeIntrospection}."); } return \array_map( [$this, 'getInterfaceType'], $implementingIntrospection['interfaces'] ); } /** * @param array<string, mixed> $object * * @throws InvariantViolation */ private function buildObjectDef(array $object): ObjectType { return new ObjectType([ 'name' => $object['name'], 'description' => $object['description'], 'interfaces' => fn (): array => $this->buildImplementationsList($object), 'fields' => fn (): array => $this->buildFieldDefMap($object), ]); } /** * @param array<string, mixed> $interface * * @throws InvariantViolation */ private function buildInterfaceDef(array $interface): InterfaceType { return new InterfaceType([ 'name' => $interface['name'], 'description' => $interface['description'], 'fields' => fn (): array => $this->buildFieldDefMap($interface), 'interfaces' => fn (): array => $this->buildImplementationsList($interface), ]); } /** * @param array<string, mixed> $union * * @throws InvariantViolation */ private function buildUnionDef(array $union): UnionType { if (! \array_key_exists('possibleTypes', $union)) { $safeUnion = Utils::printSafeJson($union); throw new InvariantViolation("Introspection result missing possibleTypes: {$safeUnion}."); } return new UnionType([ 'name' => $union['name'], 'description' => $union['description'], 'types' => fn (): array => \array_map( [$this, 'getObjectType'], $union['possibleTypes'] ), ]); } /** * @param array<string, mixed> $enum * * @throws InvariantViolation */ private function buildEnumDef(array $enum): EnumType { if (! \array_key_exists('enumValues', $enum)) { $safeEnum = Utils::printSafeJson($enum); throw new InvariantViolation("Introspection result missing enumValues: {$safeEnum}."); } $values = []; foreach ($enum['enumValues'] as $value) { $values[$value['name']] = [ 'description' => $value['description'], 'deprecationReason' => $value['deprecationReason'], ]; } return new EnumType([ 'name' => $enum['name'], 'description' => $enum['description'], 'values' => $values, ]); } /** * @param array<string, mixed> $inputObject * * @throws InvariantViolation */ private function buildInputObjectDef(array $inputObject): InputObjectType { if (! \array_key_exists('inputFields', $inputObject)) { $safeInputObject = Utils::printSafeJson($inputObject); throw new InvariantViolation("Introspection result missing inputFields: {$safeInputObject}."); } return new InputObjectType([ 'name' => $inputObject['name'], 'description' => $inputObject['description'], 'fields' => fn (): array => $this->buildInputValueDefMap($inputObject['inputFields']), ]); } /** * @param array<string, mixed> $typeIntrospection * * @throws \Exception * @throws InvariantViolation * * @return array<string, UnnamedFieldDefinitionConfig> */ private function buildFieldDefMap(array $typeIntrospection): array { if (! \array_key_exists('fields', $typeIntrospection)) { $safeType = Utils::printSafeJson($typeIntrospection); throw new InvariantViolation("Introspection result missing fields: {$safeType}."); } /** @var array<string, UnnamedFieldDefinitionConfig> $map */ $map = []; foreach ($typeIntrospection['fields'] as $field) { if (! \array_key_exists('args', $field)) { $safeField = Utils::printSafeJson($field); throw new InvariantViolation("Introspection result missing field args: {$safeField}."); } $map[$field['name']] = [ 'description' => $field['description'], 'deprecationReason' => $field['deprecationReason'], 'type' => $this->getOutputType($field['type']), 'args' => $this->buildInputValueDefMap($field['args']), ]; } // @phpstan-ignore-next-line unless the returned name was numeric, this works return $map; } /** * @param array<int, array<string, mixed>> $inputValueIntrospections * * @throws \Exception * * @return array<string, UnnamedInputObjectFieldConfig> */ private function buildInputValueDefMap(array $inputValueIntrospections): array { /** @var array<string, UnnamedInputObjectFieldConfig> $map */ $map = []; foreach ($inputValueIntrospections as $value) { $map[$value['name']] = $this->buildInputValue($value); } // @phpstan-ignore-next-line unless the returned name was numeric, this works return $map; } /** * @param array<string, mixed> $inputValueIntrospection * * @throws \Exception * @throws SyntaxError * * @return UnnamedInputObjectFieldConfig */ public function buildInputValue(array $inputValueIntrospection): array { $type = $this->getInputType($inputValueIntrospection['type']); $inputValue = [ 'description' => $inputValueIntrospection['description'], 'type' => $type, ]; if (isset($inputValueIntrospection['defaultValue'])) { $inputValue['defaultValue'] = AST::valueFromAST( Parser::parseValue($inputValueIntrospection['defaultValue']), $type ); } return $inputValue; } /** * @param array<string, mixed> $directive * * @throws \Exception * @throws InvariantViolation */ public function buildDirective(array $directive): Directive { if (! \array_key_exists('args', $directive)) { $safeDirective = Utils::printSafeJson($directive); throw new InvariantViolation("Introspection result missing directive args: {$safeDirective}."); } if (! \array_key_exists('locations', $directive)) { $safeDirective = Utils::printSafeJson($directive); throw new InvariantViolation("Introspection result missing directive locations: {$safeDirective}."); } return new Directive([ 'name' => $directive['name'], 'description' => $directive['description'], 'args' => $this->buildInputValueDefMap($directive['args']), 'isRepeatable' => $directive['isRepeatable'] ?? false, 'locations' => $directive['locations'], ]); } }