From 7fa4b1393f7ce87a1abc6546ea99f58e7bd8d5d8 Mon Sep 17 00:00:00 2001 From: Laurens Bultynck Date: Mon, 5 Sep 2022 21:16:30 +0200 Subject: [PATCH] Validate required missing parameters in request (#12) * Catch exceptions in main validation method * Ensure missing required parameters are validated --- src/Exceptions/InvalidSchema.php | 9 +++++ src/Validators/LeagueSchemaValidator.php | 41 ++++++++++---------- tests/LeagueSchemaValidatorTest.php | 32 +++++++++++++++ tests/__fixtures__/specs/validation.v30.json | 33 ++++++++++++++++ 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/src/Exceptions/InvalidSchema.php b/src/Exceptions/InvalidSchema.php index bae5551..8e086de 100644 --- a/src/Exceptions/InvalidSchema.php +++ b/src/Exceptions/InvalidSchema.php @@ -14,6 +14,15 @@ public function __construct(Validator $validator, ?Response $response = null, st $this->status = config('schema-validation.response.status', $this->status); } + public static function becauseMissingRequiredKeyword(string $key, string $message): self + { + return self::withMessages([ + $key => [ + $message, + ], + ]); + } + public static function becauseInvalidKeyword(string $key, string $message): self { return self::withMessages([ diff --git a/src/Validators/LeagueSchemaValidator.php b/src/Validators/LeagueSchemaValidator.php index 8aaf83d..55f9830 100644 --- a/src/Validators/LeagueSchemaValidator.php +++ b/src/Validators/LeagueSchemaValidator.php @@ -6,7 +6,6 @@ use Beblife\SchemaValidation\Exceptions\UnableToValidateSchema; use Beblife\SchemaValidation\Schema; use Beblife\SchemaValidation\SchemaValidator; -use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\Reader; use cebe\openapi\spec\Schema as SpecSchema; use GuzzleHttp\Psr7\ServerRequest; @@ -15,6 +14,7 @@ use League\OpenAPIValidation\PSR7\Exception\NoPath; use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody; use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs; +use League\OpenAPIValidation\PSR7\Exception\Validation\RequiredParameterMissing; use League\OpenAPIValidation\PSR7\ServerRequestValidator; use League\OpenAPIValidation\PSR7\ValidatorBuilder; use League\OpenAPIValidation\Schema\Exception\KeywordMismatch; @@ -43,6 +43,9 @@ public function __construct(string $specPath) } } + /** + * @throws InvalidSchema + */ public function validate(Request $request, ?Schema $schema = null): Request { try { @@ -55,39 +58,35 @@ public function validate(Request $request, ?Schema $schema = null): Request } } catch(NoPath $exception) { throw UnableToValidateSchema::becauseNoSchemaForRequest($request); + } catch(InvalidQueryArgs $exception) { + if($exception->getPrevious() instanceof RequiredParameterMissing) { + throw InvalidSchema::becauseMissingRequiredKeyword( + $exception->getPrevious()->name(), + "Field '{$exception->getPrevious()->name()}' is required.", + ); + } + + throw $this->validationException($exception->getPrevious()->getPrevious()); + } catch(InvalidBody $exception) { + throw $this->validationException($exception->getPrevious()); + } catch (KeywordMismatch $keywordMismatch) { + throw $this->validationException($keywordMismatch); } return $request; } - /** - * @throws InvalidSchema - */ protected function validateFromSpec(Request $request): void { - try { - $this->validator->validate($this->toServerRequest($request)); - } catch(InvalidQueryArgs $exeception) { - throw $this->validationException($exeception->getPrevious()->getPrevious()); - } catch(InvalidBody $exception) { - throw $this->validationException($exception->getPrevious()); - } + $this->validator->validate($this->toServerRequest($request)); } - /** - * @throws InvalidSchema - * @throws TypeErrorException - */ protected function validateForSchema(Request $request, Schema $schema): void { $validator = new LeagueValidator(LeagueValidator::VALIDATE_AS_REQUEST); $specSchema = Reader::readFromJson(json_encode($schema->toArray()), SpecSchema::class); - try { - $validator->validate($request->all(), $specSchema); - } catch (KeywordMismatch $keywordMismatch) { - throw $this->validationException($keywordMismatch); - } + $validator->validate($request->all(), $specSchema); } protected function toServerRequest(Request $request): ServerRequestInterface @@ -139,6 +138,8 @@ protected function messageForInvalidKeyword(KeywordMismatch $keywordMismatch): s 'Size of an array' => 'Size', 'All array' => 'All', "The number of object's" => 'Object', + 'Required property ' => 'Field ', + ' must be present in the object' => ' is required', ] as $search => $replace) { $message = str_replace($search, $replace, $message); } diff --git a/tests/LeagueSchemaValidatorTest.php b/tests/LeagueSchemaValidatorTest.php index 0a86068..0b7e3eb 100644 --- a/tests/LeagueSchemaValidatorTest.php +++ b/tests/LeagueSchemaValidatorTest.php @@ -138,6 +138,38 @@ public function test_it_can_validate_the_body_on_a_request(string $param, $value $this->fail('A validation exception was not thrown for parameter: '. $param); } + public function test_it_validates_required_missing_query_parameters(): void + { + $validator = new LeagueSchemaValidator($this->specFixture('validation.v30.json')); + $request = $this->createRequest('GET', '/missing-parameters'); + + try { + $validator->validate($request); + } catch(InvalidSchema $exception) { + $this->assertFormattedValidationException($exception, 'missing', "Field 'missing' is required."); + + return; + } + + $this->fail('A validation exception was not thrown for a missing query parameter'); + } + + public function test_it_validates_required_missing_body_parameters(): void + { + $validator = new LeagueSchemaValidator($this->specFixture('validation.v30.json')); + $request = $this->createRequest('POST', '/missing-parameters'); + + try { + $validator->validate($request); + } catch(InvalidSchema $exception) { + $this->assertFormattedValidationException($exception, 'missing', "Field 'missing' is required."); + + return; + } + + $this->fail('A validation exception was not thrown for a missing body parameter'); + } + public function invalidData(): array { return [ diff --git a/tests/__fixtures__/specs/validation.v30.json b/tests/__fixtures__/specs/validation.v30.json index a7c38a4..43e7689 100644 --- a/tests/__fixtures__/specs/validation.v30.json +++ b/tests/__fixtures__/specs/validation.v30.json @@ -192,6 +192,39 @@ } } } + }, + "/missing-parameters": { + "get": { + "parameters": [ + { + "in": "query", + "name": "missing", + "required": true, + "schema": { + "type": "boolean" + } + } + ] + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "missing": { + "type": "boolean" + } + }, + "required": [ + "missing" + ] + } + } + } + } + } } } }