From 5cf0a8f10f8d384423fcfcc953418c5c0ea3ee0e Mon Sep 17 00:00:00 2001 From: Joe Saunderson Date: Tue, 14 May 2024 18:30:43 +0100 Subject: [PATCH 1/2] Add the concept of "validation_mode" --- docs/validation/index.md | 15 +++++++++++++++ src/DependencyInjection/Configuration.php | 16 ++++++++++++++++ .../OverblogGraphQLExtension.php | 6 ++++++ src/Resources/config/services.yaml | 1 + src/Validator/InputValidator.php | 7 +++++-- src/Validator/InputValidatorFactory.php | 8 ++++++-- tests/Functional/App/config/validator/config.yml | 1 + tests/Validator/InputValidatorTest.php | 2 +- 8 files changed, 51 insertions(+), 5 deletions(-) diff --git a/docs/validation/index.md b/docs/validation/index.md index 570f44ba5..282963919 100644 --- a/docs/validation/index.md +++ b/docs/validation/index.md @@ -7,6 +7,7 @@ to validate user input data. It currently supports only GraphQL schemas defined ### Contents: - [Overview](#overview) +- [Mode](#mode) - [How does it work?](#how-does-it-work) - [Applying of validation constraints](#applying-of-validation-constraints) - [Listing constraints directly](#listing-constraints-directly) @@ -109,6 +110,20 @@ The `birthdate` field is of type `input-object` and is marked as `cascade` so it - **month** is between 1 and 12 - **year** is between 1900 and 2019 +# Mode + +There are two modes for validation, `full` (default) and `partial`. + +`full` mode will always validate _all_ arguments/parameters of the type, even if they are not passed (where the types are optional). + +`partial` mode will only validate the arguments/parameters that are passed. This can be useful if you want to validate only the data that is actually sent by the client. + +To change the mode, you can set the `validation_mode` option in the main configuration: + +```yaml +overblog_graphql: + validation_mode: full | partial +``` ## How does it work? diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index a8c4c707e..e709a4daf 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -51,6 +51,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() ->append($this->batchingMethodSection()) + ->append($this->validationModeSection()) ->append($this->definitionsSection()) ->append($this->errorsHandlerSection()) ->append($this->servicesSection()) @@ -77,6 +78,21 @@ private function batchingMethodSection(): EnumNodeDefinition return $node; } + private function validationModeSection(): EnumNodeDefinition + { + $builder = new TreeBuilder('validation_mode', "enum"); + + /** @var EnumNodeDefinition $node */ + $node = $builder->getRootNode(); + + $node + ->values(['full', 'partial']) + ->defaultValue('full') + ->end(); + + return $node; + } + private function errorsHandlerSection(): ArrayNodeDefinition { $builder = new TreeBuilder('errors_handler'); diff --git a/src/DependencyInjection/OverblogGraphQLExtension.php b/src/DependencyInjection/OverblogGraphQLExtension.php index 257952eef..16180213b 100644 --- a/src/DependencyInjection/OverblogGraphQLExtension.php +++ b/src/DependencyInjection/OverblogGraphQLExtension.php @@ -43,6 +43,7 @@ public function load(array $configs, ContainerBuilder $container): void $config = $this->processConfiguration($configuration, $configs); $this->setBatchingMethod($config, $container); + $this->setValidationMode($config, $container); $this->setServicesAliases($config, $container); $this->setSchemaBuilderArguments($config, $container); $this->setSchemaArguments($config, $container); @@ -151,6 +152,11 @@ private function setBatchingMethod(array $config, ContainerBuilder $container): $container->setParameter($this->getAlias().'.batching_method', $config['batching_method']); } + private function setValidationMode(array $config, ContainerBuilder $container): void + { + $container->setParameter($this->getAlias().'.validation_mode', $config['validation_mode']); + } + private function setDebugListener(array $config, ContainerBuilder $container): void { if ($config['definitions']['show_debug_info']) { diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 72985d02c..2b23ff0f8 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -123,5 +123,6 @@ services: - "@?validator.validator_factory" - "@?validator" - "@?translator.default" + - "%overblog_graphql.validation_mode%" tags: - { name: overblog_graphql.service, alias: input_validator_factory, public: false } diff --git a/src/Validator/InputValidator.php b/src/Validator/InputValidator.php index 18846c393..6fa88fa09 100644 --- a/src/Validator/InputValidator.php +++ b/src/Validator/InputValidator.php @@ -42,6 +42,7 @@ final class InputValidator private ResolveInfo $info; private ConstraintValidatorFactoryInterface $constraintValidatorFactory; private ?TranslatorInterface $defaultTranslator; + private string $validationMode; /** @var ClassMetadataInterface[] */ private array $cachedMetadata = []; @@ -50,7 +51,8 @@ public function __construct( ResolverArgs $resolverArgs, ValidatorInterface $validator, ConstraintValidatorFactoryInterface $constraintValidatorFactory, - ?TranslatorInterface $translator + ?TranslatorInterface $translator, + string $validationMode ) { $this->resolverArgs = $resolverArgs; $this->info = $this->resolverArgs->info; @@ -58,6 +60,7 @@ public function __construct( $this->constraintValidatorFactory = $constraintValidatorFactory; $this->defaultTranslator = $translator; $this->metadataFactory = new MetadataFactory(); + $this->validationMode = $validationMode; } /** @@ -140,7 +143,7 @@ private function buildValidationTree(ValidationNode $rootObject, iterable $field $property = $arg['name'] ?? $name; $config = static::normalizeConfig($arg['validation'] ?? []); - if (!array_key_exists($property, $inputData)) { + if ($this->validationMode === "partial" && !array_key_exists($property, $inputData)) { // This field was not provided in the inputData. Do not attempt to validate it. continue; } diff --git a/src/Validator/InputValidatorFactory.php b/src/Validator/InputValidatorFactory.php index d0044ceda..e4dbd9420 100644 --- a/src/Validator/InputValidatorFactory.php +++ b/src/Validator/InputValidatorFactory.php @@ -15,6 +15,7 @@ final class InputValidatorFactory private ?ValidatorInterface $defaultValidator; private ?ConstraintValidatorFactoryInterface $constraintValidatorFactory; private ?TranslatorInterface $defaultTranslator; + private string $validationMode; /** * InputValidatorFactory constructor. @@ -22,11 +23,13 @@ final class InputValidatorFactory public function __construct( ?ConstraintValidatorFactoryInterface $constraintValidatorFactory, ?ValidatorInterface $validator, - ?TranslatorInterface $translator + ?TranslatorInterface $translator, + string $validationMode ) { $this->defaultValidator = $validator; $this->defaultTranslator = $translator; $this->constraintValidatorFactory = $constraintValidatorFactory; + $this->validationMode = $validationMode; } public function create(ResolverArgs $args): InputValidator @@ -39,7 +42,8 @@ public function create(ResolverArgs $args): InputValidator $args, $this->defaultValidator, $this->constraintValidatorFactory, - $this->defaultTranslator + $this->defaultTranslator, + $this->validationMode ); } } diff --git a/tests/Functional/App/config/validator/config.yml b/tests/Functional/App/config/validator/config.yml index fa4234e3e..7dcf40e64 100644 --- a/tests/Functional/App/config/validator/config.yml +++ b/tests/Functional/App/config/validator/config.yml @@ -6,6 +6,7 @@ framework: enabled: true overblog_graphql: + validation_mode: partial definitions: config_validation: false class_namespace: "Overblog\\GraphQLBundle\\Validator\\__DEFINITIONS__" diff --git a/tests/Validator/InputValidatorTest.php b/tests/Validator/InputValidatorTest.php index 9f3835df1..17e8ddded 100644 --- a/tests/Validator/InputValidatorTest.php +++ b/tests/Validator/InputValidatorTest.php @@ -29,7 +29,7 @@ public function testNoDefaultValidatorException(): void { $this->expectException(ServiceNotFoundException::class); - $factory = new InputValidatorFactory(null, null, null); + $factory = new InputValidatorFactory(null, null, null, 'full'); $factory->create(new ResolverArgs( true, From a2943adf5622873cbf504155624a76f004f8e3c1 Mon Sep 17 00:00:00 2001 From: Joe Saunderson Date: Tue, 14 May 2024 18:35:48 +0100 Subject: [PATCH 2/2] Linting --- src/DependencyInjection/Configuration.php | 2 +- src/Validator/InputValidator.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index e709a4daf..00400d34b 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -80,7 +80,7 @@ private function batchingMethodSection(): EnumNodeDefinition private function validationModeSection(): EnumNodeDefinition { - $builder = new TreeBuilder('validation_mode', "enum"); + $builder = new TreeBuilder('validation_mode', 'enum'); /** @var EnumNodeDefinition $node */ $node = $builder->getRootNode(); diff --git a/src/Validator/InputValidator.php b/src/Validator/InputValidator.php index 6fa88fa09..2977ae27b 100644 --- a/src/Validator/InputValidator.php +++ b/src/Validator/InputValidator.php @@ -143,7 +143,7 @@ private function buildValidationTree(ValidationNode $rootObject, iterable $field $property = $arg['name'] ?? $name; $config = static::normalizeConfig($arg['validation'] ?? []); - if ($this->validationMode === "partial" && !array_key_exists($property, $inputData)) { + if ("partial" === $this->validationMode && !array_key_exists($property, $inputData)) { // This field was not provided in the inputData. Do not attempt to validate it. continue; }