Skip to content

Commit

Permalink
Consider empty requestBody as valid if not required thephpleague#198
Browse files Browse the repository at this point in the history
…[source Camille Vernerie]
  • Loading branch information
Wilkins committed Dec 2, 2024
1 parent a665e22 commit 628a407
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 13 deletions.
8 changes: 8 additions & 0 deletions src/PSR7/Exception/Validation/InvalidBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
17 changes: 4 additions & 13 deletions src/PSR7/SpecFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/PSR7/Validators/BodyValidator/BodyValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
88 changes: 88 additions & 0 deletions tests/FromCommunity/OptionalRequestBodyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace League\OpenAPIValidation\Tests\FromCommunity;

use GuzzleHttp\Psr7\ServerRequest;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use PHPUnit\Framework\TestCase;

class OptionalRequestBodyTest extends TestCase
{
public function testOptionalEmptyBodyIsValid(): 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:
content:
application/json:
schema:
properties:
name:
type: string
responses:
'204':
description: No content
YAML;

$validator = (new ValidatorBuilder())->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'
)
);
}
}

0 comments on commit 628a407

Please sign in to comment.