Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Exception/InvalidOpenAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
25 changes: 23 additions & 2 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
}
}
56 changes: 56 additions & 0 deletions tests/ReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down