From fc77d4a9f5116259bfe17ab241af676664c15c30 Mon Sep 17 00:00:00 2001 From: cvernerie Date: Mon, 15 May 2023 11:49:11 +0200 Subject: [PATCH] Consider empty requestBody as valid if not required --- src/PSR7/Exception/Validation/InvalidBody.php | 8 ++ src/PSR7/SpecFinder.php | 17 +--- .../BodyValidator/BodyValidator.php | 16 ++++ .../FromCommunity/OptionalRequestBodyTest.php | 88 +++++++++++++++++++ 4 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 tests/FromCommunity/OptionalRequestBodyTest.php diff --git a/src/PSR7/Exception/Validation/InvalidBody.php b/src/PSR7/Exception/Validation/InvalidBody.php index 1cdd6f15..6d5412e7 100644 --- a/src/PSR7/Exception/Validation/InvalidBody.php +++ b/src/PSR7/Exception/Validation/InvalidBody.php @@ -46,4 +46,12 @@ public static function becauseBodyIsNotValidJson(string $error, OperationAddress return $exception; } + + public static function becauseOfMissingRequiredBody(OperationAddress $addr): self + { + $exception = static::fromAddr($addr); + $exception->message = sprintf('Required body is missing for %s', $addr); + + return $exception; + } } diff --git a/src/PSR7/SpecFinder.php b/src/PSR7/SpecFinder.php index 07326b7d..14ebaa06 100644 --- a/src/PSR7/SpecFinder.php +++ b/src/PSR7/SpecFinder.php @@ -7,15 +7,14 @@ use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\spec\Callback; use cebe\openapi\spec\Header as HeaderSpec; -use cebe\openapi\spec\MediaType; use cebe\openapi\spec\OpenApi; use cebe\openapi\spec\Operation; use cebe\openapi\spec\Parameter; use cebe\openapi\spec\PathItem; -use cebe\openapi\spec\Reference; use cebe\openapi\spec\Response as ResponseSpec; use cebe\openapi\spec\SecurityRequirement; use cebe\openapi\spec\SecurityScheme; +use cebe\openapi\SpecBaseObject; use League\OpenAPIValidation\PSR7\Exception\NoCallback; use League\OpenAPIValidation\PSR7\Exception\NoOperation; use League\OpenAPIValidation\PSR7\Exception\NoPath; @@ -171,23 +170,15 @@ public function findSecuritySchemesSpecs(): array } /** - * @return MediaType[]|Reference[] - * * @throws NoPath */ - public function findBodySpec(OperationAddress $addr): array + public function findBodySpec(OperationAddress $addr): ?SpecBaseObject { if ($addr instanceof ResponseAddress || $addr instanceof CallbackResponseAddress) { - return $this->findResponseSpec($addr)->content; - } - - $requestBody = $this->findOperationSpec($addr)->requestBody; - - if (! $requestBody) { - return []; + return $this->findResponseSpec($addr); } - return $requestBody->content; + return $this->findOperationSpec($addr)->requestBody; } /** diff --git a/src/PSR7/Validators/BodyValidator/BodyValidator.php b/src/PSR7/Validators/BodyValidator/BodyValidator.php index e1db4385..945aaa68 100644 --- a/src/PSR7/Validators/BodyValidator/BodyValidator.php +++ b/src/PSR7/Validators/BodyValidator/BodyValidator.php @@ -6,6 +6,8 @@ use cebe\openapi\spec\MediaType; use cebe\openapi\spec\Reference; +use cebe\openapi\spec\RequestBody; +use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody; use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders; use League\OpenAPIValidation\PSR7\MessageValidator; use League\OpenAPIValidation\PSR7\OperationAddress; @@ -39,6 +41,20 @@ public function validate(OperationAddress $addr, MessageInterface $message): voi { $mediaTypeSpecs = $this->finder->findBodySpec($addr); + if ($mediaTypeSpecs === null) { + return; + } + + if ($mediaTypeSpecs instanceof RequestBody && $message->getBody()->getSize() === 0) { + if ($mediaTypeSpecs->required) { + throw InvalidBody::becauseOfMissingRequiredBody($addr); + } + + return; + } + + $mediaTypeSpecs = $mediaTypeSpecs->content; + if (empty($mediaTypeSpecs)) { // edge case: if "content" keyword is not set (body can be anything as no expectations set) return; diff --git a/tests/FromCommunity/OptionalRequestBodyTest.php b/tests/FromCommunity/OptionalRequestBodyTest.php new file mode 100644 index 00000000..fba569e2 --- /dev/null +++ b/tests/FromCommunity/OptionalRequestBodyTest.php @@ -0,0 +1,88 @@ +fromYaml($yaml)->getServerRequestValidator(); + + $validator->validate( + new ServerRequest( + 'POST', + '/api' + ) + ); + + $this->addToAssertionCount(1); + } + + public function testRequiredEmptyBodyThrowsException(): void + { + $yaml = /** @lang yaml */ + <<<'YAML' +openapi: 3.0.0 +info: + title: Test API + version: '1.0' +servers: + - url: 'http://localhost:8000' +paths: + /api: + post: + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + responses: + '204': + description: No content +YAML; + + $validator = (new ValidatorBuilder())->fromYaml($yaml)->getServerRequestValidator(); + + $this->expectException(InvalidBody::class); + $this->expectExceptionMessage('Required body is missing for Request [post /api]'); + $validator->validate( + new ServerRequest( + 'POST', + '/api' + ) + ); + } +}