Automattic\WooCommerce\Vendor\GraphQL\Validator

DocumentValidator{}WC 1.0

Implements the "Validation" section of the spec.

Validation runs synchronously, returning an array of encountered errors, or an empty array if no errors were encountered and the document is valid.

A list of specific validation rules may be provided. If not provided, the default list of rules defined by the Automattic\WooCommerce\Vendor\GraphQL specification will be used.

Each validation rule is an instance of Automattic\WooCommerce\Vendor\GraphQL\Validator\Rules\ValidationRule which returns a visitor (see the Automattic\WooCommerce\Vendor\GraphQL\Language\Visitor API).

Visitor methods are expected to return an instance of Automattic\WooCommerce\Vendor\GraphQL\Error\Error, or array of such instances when invalid.

Optionally a custom TypeInfo instance may be provided. If not provided, one will be created from the provided schema.

No Hooks.

Usage

$DocumentValidator = new DocumentValidator();
// use class methods

Methods

  1. public static addRule(ValidationRule $rule)
  2. public static allRules()
  3. public static assertValidSDL(DocumentNode $documentAST)
  4. public static assertValidSDLExtension(DocumentNode $documentAST, Schema $schema)
  5. public static defaultRules()
  6. public static getRule(string $name)
  7. public static removeRule(ValidationRule $rule)
  8. public static sdlRules()
  9. public static securityRules()
  10. public static validate(
  11. public static validateSDL(
  12. private static combineErrorMessages(array $errors)

DocumentValidator{} code WC 10.9.1

class DocumentValidator
{
    /** @var array<string, ValidationRule> */
    private static array $rules = [];

    /** @var array<class-string<ValidationRule>, ValidationRule> */
    private static array $defaultRules;

    /** @var array<class-string<QuerySecurityRule>, QuerySecurityRule> */
    private static array $securityRules;

    /** @var array<class-string<ValidationRule>, ValidationRule> */
    private static array $sdlRules;

    private static bool $initRules = false;

    /**
     * Validate a Automattic\WooCommerce\Vendor\GraphQL query against a schema.
     *
     * @param array<ValidationRule>|null $rules Defaults to using all available rules
     *
     * @throws \Exception
     *
     * @return list<Error>
     *
     * @api
     */
    public static function validate(
        Schema $schema,
        DocumentNode $ast,
        ?array $rules = null,
        ?TypeInfo $typeInfo = null
    ): array {
        $rules ??= static::allRules();

        if ($rules === []) {
            return [];
        }

        $typeInfo ??= new TypeInfo($schema);

        $context = new QueryValidationContext($schema, $ast, $typeInfo);

        $visitors = [];
        foreach ($rules as $rule) {
            $visitors[] = $rule->getVisitor($context);
        }

        Visitor::visit(
            $ast,
            Visitor::visitWithTypeInfo(
                $typeInfo,
                Visitor::visitInParallel($visitors)
            )
        );

        return $context->getErrors();
    }

    /**
     * Returns all global validation rules.
     *
     * @throws \InvalidArgumentException
     *
     * @return array<string, ValidationRule>
     *
     * @api
     */
    public static function allRules(): array
    {
        if (! self::$initRules) {
            self::$rules = array_merge(
                static::defaultRules(),
                self::securityRules(),
                self::$rules
            );
            self::$initRules = true;
        }

        return self::$rules;
    }

    /** @return array<class-string<ValidationRule>, ValidationRule> */
    public static function defaultRules(): array
    {
        return self::$defaultRules ??= [
            ExecutableDefinitions::class => new ExecutableDefinitions(),
            UniqueOperationNames::class => new UniqueOperationNames(),
            LoneAnonymousOperation::class => new LoneAnonymousOperation(),
            SingleFieldSubscription::class => new SingleFieldSubscription(),
            KnownTypeNames::class => new KnownTypeNames(),
            FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
            VariablesAreInputTypes::class => new VariablesAreInputTypes(),
            ScalarLeafs::class => new ScalarLeafs(),
            FieldsOnCorrectType::class => new FieldsOnCorrectType(),
            UniqueFragmentNames::class => new UniqueFragmentNames(),
            KnownFragmentNames::class => new KnownFragmentNames(),
            NoUnusedFragments::class => new NoUnusedFragments(),
            PossibleFragmentSpreads::class => new PossibleFragmentSpreads(),
            NoFragmentCycles::class => new NoFragmentCycles(),
            UniqueVariableNames::class => new UniqueVariableNames(),
            NoUndefinedVariables::class => new NoUndefinedVariables(),
            NoUnusedVariables::class => new NoUnusedVariables(),
            KnownDirectives::class => new KnownDirectives(),
            UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
            KnownArgumentNames::class => new KnownArgumentNames(),
            UniqueArgumentNames::class => new UniqueArgumentNames(),
            ValuesOfCorrectType::class => new ValuesOfCorrectType(),
            ProvidedRequiredArguments::class => new ProvidedRequiredArguments(),
            VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
            OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
            UniqueInputFieldNames::class => new UniqueInputFieldNames(),
            OneOfInputObjectsRule::class => new OneOfInputObjectsRule(),
        ];
    }

    /**
     * @deprecated just add rules via @see DocumentValidator::addRule()
     *
     * @throws \InvalidArgumentException
     *
     * @return array<class-string<QuerySecurityRule>, QuerySecurityRule>
     */
    public static function securityRules(): array
    {
        return self::$securityRules ??= [
            DisableIntrospection::class => new DisableIntrospection(DisableIntrospection::DISABLED),
            QueryDepth::class => new QueryDepth(QueryDepth::DISABLED),
            QueryComplexity::class => new QueryComplexity(QueryComplexity::DISABLED),
        ];
    }

    /** @return array<class-string<ValidationRule>, ValidationRule> */
    public static function sdlRules(): array
    {
        return self::$sdlRules ??= [
            LoneSchemaDefinition::class => new LoneSchemaDefinition(),
            UniqueOperationTypes::class => new UniqueOperationTypes(),
            UniqueTypeNames::class => new UniqueTypeNames(),
            UniqueEnumValueNames::class => new UniqueEnumValueNames(),
            UniqueFieldDefinitionNames::class => new UniqueFieldDefinitionNames(),
            UniqueArgumentDefinitionNames::class => new UniqueArgumentDefinitionNames(),
            UniqueDirectiveNames::class => new UniqueDirectiveNames(),
            KnownTypeNames::class => new KnownTypeNames(),
            KnownDirectives::class => new KnownDirectives(),
            UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
            PossibleTypeExtensions::class => new PossibleTypeExtensions(),
            KnownArgumentNamesOnDirectives::class => new KnownArgumentNamesOnDirectives(),
            UniqueArgumentNames::class => new UniqueArgumentNames(),
            UniqueInputFieldNames::class => new UniqueInputFieldNames(),
            ProvidedRequiredArgumentsOnDirectives::class => new ProvidedRequiredArgumentsOnDirectives(),
        ];
    }

    /**
     * Returns global validation rule by name.
     *
     * Standard rules are named by class name, so example usage for such rules:
     *
     * @example DocumentValidator::getRule(Automattic\WooCommerce\Vendor\GraphQL\Validator\Rules\QueryComplexity::class);
     *
     * @api
     *
     * @throws \InvalidArgumentException
     */
    public static function getRule(string $name): ?ValidationRule
    {
        return static::allRules()[$name] ?? null;
    }

    /**
     * Add rule to list of global validation rules.
     *
     * @api
     */
    public static function addRule(ValidationRule $rule): void
    {
        self::$rules[$rule->getName()] = $rule;
    }

    /**
     * Remove rule from list of global validation rules.
     *
     * @api
     */
    public static function removeRule(ValidationRule $rule): void
    {
        unset(self::$rules[$rule->getName()]);
    }

    /**
     * Validate a Automattic\WooCommerce\Vendor\GraphQL document defined through schema definition language.
     *
     * @param array<ValidationRule>|null $rules
     *
     * @throws \Exception
     *
     * @return list<Error>
     */
    public static function validateSDL(
        DocumentNode $documentAST,
        ?Schema $schemaToExtend = null,
        ?array $rules = null
    ): array {
        $rules ??= self::sdlRules();

        if ($rules === []) {
            return [];
        }

        $context = new SDLValidationContext($documentAST, $schemaToExtend);

        $visitors = [];
        foreach ($rules as $rule) {
            $visitors[] = $rule->getSDLVisitor($context);
        }

        Visitor::visit(
            $documentAST,
            Visitor::visitInParallel($visitors)
        );

        return $context->getErrors();
    }

    /**
     * @throws \Exception
     * @throws Error
     */
    public static function assertValidSDL(DocumentNode $documentAST): void
    {
        $errors = self::validateSDL($documentAST);
        if ($errors !== []) {
            throw new Error(self::combineErrorMessages($errors));
        }
    }

    /**
     * @throws \Exception
     * @throws Error
     */
    public static function assertValidSDLExtension(DocumentNode $documentAST, Schema $schema): void
    {
        $errors = self::validateSDL($documentAST, $schema);
        if ($errors !== []) {
            throw new Error(self::combineErrorMessages($errors));
        }
    }

    /** @param array<Error> $errors */
    private static function combineErrorMessages(array $errors): string
    {
        $messages = [];
        foreach ($errors as $error) {
            $messages[] = $error->getMessage();
        }

        return implode("\n\n", $messages);
    }
}