Skip to content

Commit

Permalink
fix(openapi): allow overriding of openapi responses
Browse files Browse the repository at this point in the history
  • Loading branch information
Saji Xavier committed Feb 7, 2023
1 parent 9b4b58c commit 5c67db4
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 9 deletions.
28 changes: 19 additions & 9 deletions src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,38 +279,41 @@ 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 = $openapiOperation->withResponse($successStatus, $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s %s', $resourceShortName, $operation instanceof CollectionOperationInterface ? 'collection' : 'resource'), $responseContent));
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 = $openapiOperation->withResponse($successStatus, $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s resource created', $resourceShortName), $responseContent, null, $responseLinks));
$openapiOperation = $openapiOperation->withResponse(400, $this->buildOpenApiResponse($existingResponses, 400, 'Invalid input'));
$openapiOperation = $openapiOperation->withResponse(422, $this->buildOpenApiResponse($existingResponses, 422, 'Unprocessable entity'));
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 = $openapiOperation->withResponse($successStatus, $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s resource updated', $resourceShortName), $responseContent, null, $responseLinks));
$openapiOperation = $openapiOperation->withResponse(400, $this->buildOpenApiResponse($existingResponses, 400, 'Invalid input'));
$openapiOperation = $openapiOperation->withResponse(422, $this->buildOpenApiResponse($existingResponses, 422, 'Unprocessable entity'));
break;
case HttpOperation::METHOD_DELETE:
$successStatus = (string) $operation->getStatus() ?: 204;
$openapiOperation = $openapiOperation->withResponse($successStatus, new Response(sprintf('%s resource deleted', $resourceShortName)));
$openapiOperation = $openapiOperation->withResponse($successStatus, $this->buildOpenApiResponse($existingResponses, $successStatus, sprintf('%s resource deleted', $resourceShortName)));
break;
}

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

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

private function buildOpenApiResponse(array $existingResponses, int|string $status, string $description, \ArrayObject $content = null, \ArrayObject $headers = null, \ArrayObject $links = null): Response
{
$statusResponse = $existingResponses[$status] ?? null;

return new Response($statusResponse?->getDescription() ?: $description, $statusResponse?->getContent() ?: $content, $statusResponse?->getHeaders() ?: $headers, $statusResponse?->getLinks() ?: $links);
}

/**
* @return \ArrayObject<Model\MediaType>
*/
Expand Down
160 changes: 160 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,116 @@ 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',
new \ArrayObject([
'application/ld+json' => new MediaType(new \ArrayObject([
'type' => 'array',
'items' => ['$ref' => '#/components/schemas/Dummy.OutputDto'],
])),
])
)
],
'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 5c67db4

Please sign in to comment.