From ce70d9a173e60dd5d666cde78784fe6da88bc95a Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 25 Sep 2023 19:36:26 +0100 Subject: [PATCH] Validate parameters contain schema xor content --- src/Exception/InvalidOpenAPI.php | 8 +++++ src/Reader.php | 25 ++++++++++++-- tests/ReaderTest.php | 56 ++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/Exception/InvalidOpenAPI.php b/src/Exception/InvalidOpenAPI.php index 770d824..1053793 100644 --- a/src/Exception/InvalidOpenAPI.php +++ b/src/Exception/InvalidOpenAPI.php @@ -30,6 +30,14 @@ public static function duplicateOperationIds( return new self($message, self::INVALID_OPEN_API); } + public static function mustHaveSchemaXorContent(string $name): self + { + return new self( + sprintf('Parameter "%s" MUST have either a Schema or Content, but not both.', $name), + self::INVALID_OPEN_API + ); + } + public static function failedCebeValidation(string ...$errors): self { $message = sprintf("OpenAPI is invalid for the following reasons:\n\t- %s", implode("\n\t- ", $errors)); diff --git a/src/Reader.php b/src/Reader.php index 6e830ba..1d3a066 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -70,14 +70,16 @@ private function validate(CebeSpec\OpenApi $openAPI): void { $this->isVersionSupported($openAPI->openapi) ?: throw CannotSupport::unsupportedVersion($openAPI->openapi); - $openAPI->validate() ?: throw InvalidOpenAPI::failedCebeValidation(...$openAPI->getErrors()); - $existingOperationIds = []; // OpenAPI Version 3.1 does not require paths if (isset($openAPI->paths)) { foreach ($openAPI->paths as $pathUrl => $path) { + $this->parametersContainSchemaXorContent($path); + foreach ($path->getOperations() as $method => $operation) { + $this->parametersContainSchemaXorContent($operation); + Method::tryFrom($method) !== null ?: throw CannotSupport::unsupportedMethod($pathUrl, $method); isset($operation->operationId) ?: throw CannotSupport::missingOperationId($pathUrl, $method); @@ -95,10 +97,29 @@ private function validate(CebeSpec\OpenApi $openAPI): void } } } + + $openAPI->validate() ?: throw InvalidOpenAPI::failedCebeValidation(...$openAPI->getErrors()); } private function isVersionSupported(string $version): bool { return in_array(OpenAPIVersion::fromString($version), $this->supportedVersions, true); } + + private function parametersContainSchemaXorContent(CebeSpec\PathItem | CebeSpec\Operation $specObject): void + { + foreach ($specObject->parameters as $parameter) { + assert($parameter instanceof CebeSpec\Parameter); + + $result = isset($parameter->schema); + + if (!empty($parameter->content)) { + $result = !$result; + } + + if (!$result) { + throw InvalidOpenAPI::mustHaveSchemaXorContent($parameter->name); + } + } + } } diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index b0374a7..0230a49 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -206,6 +206,62 @@ public static function provideInvalidOpenAPIs(): Generator })(), InvalidOpenAPI::duplicateOperationIds('duplicate-id', '/firstpath', 'get', '/secondpath', 'get'), ]; + + yield 'path with parameter missing both schema and content' => [ + (function () use ($openAPI, $openAPIPath) { + $openAPIArray = $openAPI; + $path = $openAPIPath('get-first-path'); + $path['parameters'] = [['name' => 'param', 'in' => 'query']]; + $openAPIArray['paths'] = ['/firstpath' => ['get' => $path]]; + return json_encode($openAPIArray); + })(), + InvalidOpenAPI::mustHaveSchemaXorContent('param'), + ]; + + yield 'path with parameter both schema and content' => [ + (function () use ($openAPI, $openAPIPath) { + $openAPIArray = $openAPI; + $openAPIArray['paths'] = ['/firstpath' => [ + 'parameters' => [[ + 'name' => 'param', + 'in' => 'query', + 'schema' => ['type' => 'string'], + 'content' => ['application/json' => ['type' => 'string']] + ]], + 'get' => $openAPIPath('get-first-path') + ],]; + return json_encode($openAPIArray); + })(), + InvalidOpenAPI::mustHaveSchemaXorContent('param'), + ]; + + yield 'path with operation missing both schema and content' => [ + (function () use ($openAPI, $openAPIPath) { + $openAPIArray = $openAPI; + $openAPIArray['paths'] = ['/firstpath' => [ + 'parameters' => [['name' => 'param', 'in' => 'query']], + 'get' => $openAPIPath('get-first-path') + ]]; + return json_encode($openAPIArray); + })(), + InvalidOpenAPI::mustHaveSchemaXorContent('param'), + ]; + + yield 'path with operation both schema and content' => [ + (function () use ($openAPI, $openAPIPath) { + $openAPIArray = $openAPI; + $path = $openAPIPath('get-first-path'); + $path['parameters'] = [[ + 'name' => 'param', + 'in' => 'query', + 'schema' => ['type' => 'string'], + 'content' => ['application/json' => ['type' => 'string']] + ]]; + $openAPIArray['paths'] = ['/firstpath' => ['get' => $path],]; + return json_encode($openAPIArray); + })(), + InvalidOpenAPI::mustHaveSchemaXorContent('param'), + ]; } #[Test]