Skip to content

Commit

Permalink
fix(openapi): allow overriding of openapi responses (#5393)
Browse files Browse the repository at this point in the history
Co-authored-by: Saji Xavier <saji.xavier@eqs.com>
  • Loading branch information
sajixavier and Saji Xavier authored Feb 27, 2023
1 parent 268c274 commit ea71416
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 14 deletions.
50 changes: 36 additions & 14 deletions src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,38 +279,44 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
}
}

$existingResponses = $openapiOperation?->getResponses() ?: [];
// Create responses
switch ($method) {
case HttpOperation::METHOD_GET:
$successStatus = (string) $operation->getStatus() ?: 200;
$responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas);
$openapiOperation = $openapiOperation->withResponse($successStatus, new Response(sprintf('%s %s', $resourceShortName, $operation instanceof CollectionOperationInterface ? 'collection' : 'resource'), $responseContent));
$openapiOperation = $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s %s', $resourceShortName, $operation instanceof CollectionOperationInterface ? 'collection' : 'resource'), $openapiOperation, $operation, $responseMimeTypes, $operationOutputSchemas);
break;
case HttpOperation::METHOD_POST:
$responseLinks = $this->getLinks($resourceMetadataCollection, $operation);
$responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas);
$successStatus = (string) $operation->getStatus() ?: 201;
$openapiOperation = $openapiOperation->withResponse($successStatus, new Response(sprintf('%s resource created', $resourceShortName), $responseContent, null, $responseLinks));
$openapiOperation = $openapiOperation->withResponse(400, new Response('Invalid input'));
$openapiOperation = $openapiOperation->withResponse(422, new Response('Unprocessable entity'));

$openapiOperation = $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s resource created', $resourceShortName), $openapiOperation, $operation, $responseMimeTypes, $operationOutputSchemas, $resourceMetadataCollection);

$openapiOperation = $this->buildOpenApiResponse($existingResponses, '400', 'Invalid input', $openapiOperation);

$openapiOperation = $this->buildOpenApiResponse($existingResponses, '422', 'Unprocessable entity', $openapiOperation);
break;
case HttpOperation::METHOD_PATCH:
case HttpOperation::METHOD_PUT:
$responseLinks = $this->getLinks($resourceMetadataCollection, $operation);
$successStatus = (string) $operation->getStatus() ?: 200;
$responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas);
$openapiOperation = $openapiOperation->withResponse($successStatus, new Response(sprintf('%s resource updated', $resourceShortName), $responseContent, null, $responseLinks));
$openapiOperation = $openapiOperation->withResponse(400, new Response('Invalid input'));
$openapiOperation = $openapiOperation->withResponse(422, new Response('Unprocessable entity'));
$openapiOperation = $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s resource updated', $resourceShortName), $openapiOperation, $operation, $responseMimeTypes, $operationOutputSchemas, $resourceMetadataCollection);
$openapiOperation = $this->buildOpenApiResponse($existingResponses, '400', 'Invalid input', $openapiOperation);
if (!isset($existingResponses[400])) {
$openapiOperation = $openapiOperation->withResponse(400, new Response('Invalid input'));
}
$openapiOperation = $this->buildOpenApiResponse($existingResponses, '422', 'Unprocessable entity', $openapiOperation);
break;
case HttpOperation::METHOD_DELETE:
$successStatus = (string) $operation->getStatus() ?: 204;
$openapiOperation = $openapiOperation->withResponse($successStatus, new Response(sprintf('%s resource deleted', $resourceShortName)));

$openapiOperation = $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s resource deleted', $resourceShortName), $openapiOperation);

break;
}

if (!$operation instanceof CollectionOperationInterface && HttpOperation::METHOD_POST !== $operation->getMethod()) {
$openapiOperation = $openapiOperation->withResponse(404, new Response('Resource not found'));
if (!isset($existingResponses[404])) {
$openapiOperation = $openapiOperation->withResponse(404, new Response('Resource not found'));
}
}

if (!$openapiOperation->getResponses()) {
Expand Down Expand Up @@ -377,6 +383,22 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
}
}

private function buildOpenApiResponse(array $existingResponses, int|string $status, string $description, Model\Operation $openapiOperation = null, HttpOperation $operation = null, array $responseMimeTypes = null, array $operationOutputSchemas = null, ResourceMetadataCollection $resourceMetadataCollection = null): Model\Operation
{
if (isset($existingResponses[$status])) {
return $openapiOperation;
}
$responseLinks = $responseContent = null;
if ($responseMimeTypes && $operationOutputSchemas) {
$responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas);
}
if ($resourceMetadataCollection && $operation) {
$responseLinks = $this->getLinks($resourceMetadataCollection, $operation);
}

return $openapiOperation->withResponse($status, new Response($description, $responseContent, null, $responseLinks));
}

/**
* @return \ArrayObject<Model\MediaType>
*/
Expand Down
151 changes: 151 additions & 0 deletions tests/OpenApi/Factory/OpenApiFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,55 @@ public function testInvoke(): void
]),
),
)),
'putDummyItemWithResponse' => (new Put())->withUriTemplate('/dummyitems/{id}')->withOperation($baseOperation)->withOpenapi(new OpenApiOperation(
responses: [
'200' => new OpenApiResponse(
description: 'Success',
content: new \ArrayObject([
'application/json' => [
'schema' => ['$ref' => '#/components/schemas/Dummy'],
],
]),
headers: new \ArrayObject([
'API_KEY' => ['description' => 'Api Key', 'schema' => ['type' => 'string']],
]),
links: new \ArrayObject([
'link' => ['$ref' => '#/components/schemas/Dummy'],
]),
),
'400' => new OpenApiResponse(
description: 'Error',
),
],
)),
'getDummyItemImageCollection' => (new GetCollection())->withUriTemplate('/dummyitems/{id}/images')->withOperation($baseOperation)->withOpenapi(new OpenApiOperation(
responses: [
'200' => new OpenApiResponse(
description: 'Success',
),
],
)),
'postDummyItemWithResponse' => (new Post())->withUriTemplate('/dummyitems')->withOperation($baseOperation)->withOpenapi(new OpenApiOperation(
responses: [
'201' => new OpenApiResponse(
description: 'Created',
content: new \ArrayObject([
'application/json' => [
'schema' => ['$ref' => '#/components/schemas/Dummy'],
],
]),
headers: new \ArrayObject([
'API_KEY' => ['description' => 'Api Key', 'schema' => ['type' => 'string']],
]),
links: new \ArrayObject([
'link' => ['$ref' => '#/components/schemas/Dummy'],
]),
),
'400' => new OpenApiResponse(
description: 'Error',
),
],
)),
])
);

Expand Down Expand Up @@ -694,5 +743,107 @@ public function testInvoke(): void
),
deprecated: false,
), $requestBodyPath->getPost());

$dummyItemPath = $paths->getPath('/dummyitems/{id}');
$this->assertEquals(new Operation(
'putDummyItemWithResponse',
['Dummy'],
[
'200' => new Response(
'Success',
new \ArrayObject([
'application/json' => [
'schema' => ['$ref' => '#/components/schemas/Dummy'],
],
]),
new \ArrayObject([
'API_KEY' => ['description' => 'Api Key', 'schema' => ['type' => 'string']],
]),
new \ArrayObject([
'link' => ['$ref' => '#/components/schemas/Dummy'],
])
),
'400' => new Response('Error'),
'422' => new Response('Unprocessable entity'),
'404' => new Response('Resource not found'),
],
'Replaces the Dummy resource.',
'Replaces the Dummy resource.',
null,
[],
new RequestBody(
'The updated Dummy resource',
new \ArrayObject([
'application/ld+json' => new MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy'])),
]),
true
),
deprecated: false
), $dummyItemPath->getPut());

$dummyItemPath = $paths->getPath('/dummyitems');
$this->assertEquals(new Operation(
'postDummyItemWithResponse',
['Dummy'],
[
'201' => new Response(
'Created',
new \ArrayObject([
'application/json' => [
'schema' => ['$ref' => '#/components/schemas/Dummy'],
],
]),
new \ArrayObject([
'API_KEY' => ['description' => 'Api Key', 'schema' => ['type' => 'string']],
]),
new \ArrayObject([
'link' => ['$ref' => '#/components/schemas/Dummy'],
])
),
'400' => new Response('Error'),
'422' => new Response('Unprocessable entity'),
],
'Creates a Dummy resource.',
'Creates a Dummy resource.',
null,
[],
new RequestBody(
'The new Dummy resource',
new \ArrayObject([
'application/ld+json' => new MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy'])),
]),
true
),
deprecated: false
), $dummyItemPath->getPost());

$dummyItemPath = $paths->getPath('/dummyitems/{id}/images');

$this->assertEquals(new Operation(
'getDummyItemImageCollection',
['Dummy'],
[
'200' => new Response(
'Success'
),
],
'Retrieves the collection of Dummy resources.',
'Retrieves the collection of Dummy resources.',
null,
[
new Parameter('page', 'query', 'The collection page number', false, false, true, [
'type' => 'integer',
'default' => 1,
]),
new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [
'type' => 'integer',
'default' => 30,
'minimum' => 0,
]),
new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [
'type' => 'boolean',
]),
]
), $dummyItemPath->getGet());
}
}

0 comments on commit ea71416

Please sign in to comment.