Skip to content

Commit 3278e9e

Browse files
feature: add custom content-type for response (#1029)
Co-authored-by: Shalvah <shalvah@users.noreply.github.com>
1 parent 5f2cf63 commit 3278e9e

File tree

2 files changed

+229
-5
lines changed

2 files changed

+229
-5
lines changed

src/Writing/OpenApiSpecGenerators/BaseGenerator.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,16 @@ protected function generateResponseContentSpec(?string $responseContent, OutputE
334334
];
335335
}
336336

337+
$response = $endpoint->responses->where('content', $responseContent)->first();
338+
$contentType = $response->headers['content-type'] ?? $response->headers['Content-Type'] ?? 'application/json';
339+
337340
switch ($type = gettype($decoded)) {
338341
case 'string':
339342
case 'boolean':
340343
case 'integer':
341344
case 'double':
342345
return [
343-
'application/json' => [
346+
$contentType => [
344347
'schema' => [
345348
'type' => $type === 'double' ? 'number' : $type,
346349
'example' => $decoded,
@@ -352,7 +355,7 @@ protected function generateResponseContentSpec(?string $responseContent, OutputE
352355
if (!count($decoded)) {
353356
// empty array
354357
return [
355-
'application/json' => [
358+
$contentType => [
356359
'schema' => [
357360
'type' => 'array',
358361
'items' => [
@@ -386,7 +389,7 @@ protected function generateResponseContentSpec(?string $responseContent, OutputE
386389
}
387390

388391
return [
389-
'application/json' => [
392+
$contentType => [
390393
'schema' => [
391394
'type' => 'array',
392395
'items' => [
@@ -404,7 +407,7 @@ protected function generateResponseContentSpec(?string $responseContent, OutputE
404407
$required = $this->filterRequiredResponseFields($endpoint, array_keys($properties));
405408

406409
$data = [
407-
'application/json' => [
410+
$contentType => [
408411
'schema' => [
409412
'type' => 'object',
410413
'example' => $decoded,
@@ -413,7 +416,7 @@ protected function generateResponseContentSpec(?string $responseContent, OutputE
413416
],
414417
];
415418
if ($required) {
416-
$data['application/json']['schema']['required'] = $required;
419+
$data[$contentType]['schema']['required'] = $required;
417420
}
418421

419422
return $data;

tests/Unit/OpenAPISpecWriterTest.php

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ public function adds_responses_correctly_as_responses_on_operation_object()
681681
}
682682

683683
/** @test */
684+
684685
public function adds_responses_correctly_as_array_of_objects()
685686
{
686687
$endpointData1 = $this->createMockEndpointData([
@@ -725,6 +726,226 @@ public function adds_responses_correctly_as_array_of_objects()
725726
],
726727
], $results['paths']['/path1']['get']['responses']);
727728
}
729+
730+
public function adds_response_content_type_correctly()
731+
{
732+
$endpointData1 = $this->createMockEndpointData([
733+
'httpMethods' => ['GET'],
734+
'uri' => '/path1',
735+
'responses' => [
736+
[
737+
'status' => 404,
738+
'description' => 'No Found',
739+
'content' => '{"this": "shouldn\'t be ignored"}',
740+
'headers' => [
741+
'Content-Type' => 'application/problem+json',
742+
]
743+
],
744+
]
745+
]);
746+
$endpointData2 = $this->createMockEndpointData([
747+
'httpMethods' => ['GET'],
748+
'uri' => '/path2',
749+
'responses' => [
750+
[
751+
'status' => 404,
752+
'description' => 'No Found',
753+
'content' => '{"this": "shouldn\'t be ignored"}',
754+
'headers' => [
755+
'content-type' => 'application/problem+json',
756+
]
757+
],
758+
]
759+
]);
760+
$endpointData3 = $this->createMockEndpointData([
761+
'httpMethods' => ['GET'],
762+
'uri' => '/path3',
763+
'responses' => [
764+
[
765+
'status' => 404,
766+
'description' => 'No Found',
767+
'content' => '{"this": "shouldn\'t be ignored"}',
768+
],
769+
]
770+
]);
771+
772+
$groups = [$this->createGroup([$endpointData1, $endpointData2, $endpointData3])];
773+
$results = $this->generate($groups);
774+
775+
$this->assertCount(1, $results['paths']['/path1']['get']['responses']);
776+
$this->assertArraySubset([
777+
'404' => [
778+
'description' => 'No Found',
779+
'content' => [
780+
'application/problem+json' => [
781+
'schema' => [
782+
'type' => 'object',
783+
'properties' => [
784+
'this' => [
785+
'example' => "shouldn't be ignored",
786+
'type' => 'string',
787+
],
788+
],
789+
],
790+
],
791+
],
792+
],
793+
], $results['paths']['/path1']['get']['responses']);
794+
795+
$this->assertCount(1, $results['paths']['/path2']['get']['responses']);
796+
$this->assertArraySubset([
797+
'404' => [
798+
'description' => 'No Found',
799+
'content' => [
800+
'application/problem+json' => [
801+
'schema' => [
802+
'type' => 'object',
803+
'properties' => [
804+
'this' => [
805+
'example' => "shouldn't be ignored",
806+
'type' => 'string',
807+
],
808+
],
809+
],
810+
],
811+
],
812+
],
813+
], $results['paths']['/path2']['get']['responses']);
814+
815+
$this->assertCount(1, $results['paths']['/path3']['get']['responses']);
816+
$this->assertArraySubset([
817+
'404' => [
818+
'description' => 'No Found',
819+
'content' => [
820+
'application/json' => [
821+
'schema' => [
822+
'type' => 'object',
823+
'properties' => [
824+
'this' => [
825+
'example' => "shouldn't be ignored",
826+
'type' => 'string',
827+
],
828+
],
829+
],
830+
],
831+
],
832+
],
833+
], $results['paths']['/path3']['get']['responses']);
834+
}
835+
836+
/** @test */
837+
public function handles_custom_content_type_with_various_response_body_types()
838+
{
839+
$customJsonType = 'application/vnd.api+json';
840+
841+
$endpointWithArray = $this->createMockEndpointData([
842+
'httpMethods' => ['GET'],
843+
'uri' => '/array-response',
844+
'responses' => [[
845+
'status' => 200,
846+
'content' => '["foo", "bar"]',
847+
'headers' => ['Content-Type' => $customJsonType],
848+
]],
849+
]);
850+
851+
$endpointWithString = $this->createMockEndpointData([
852+
'httpMethods' => ['GET'],
853+
'uri' => '/string-response',
854+
'responses' => [[
855+
'status' => 200,
856+
'content' => '"a simple string"',
857+
'headers' => ['Content-Type' => $customJsonType],
858+
]],
859+
]);
860+
861+
$endpointWithInteger = $this->createMockEndpointData([
862+
'httpMethods' => ['GET'],
863+
'uri' => '/integer-response',
864+
'responses' => [[
865+
'status' => 200,
866+
'content' => '123',
867+
'headers' => ['Content-Type' => $customJsonType],
868+
]],
869+
]);
870+
871+
872+
$groups = [$this->createGroup([$endpointWithArray, $endpointWithString, $endpointWithInteger])];
873+
$results = $this->generate($groups);
874+
875+
$arrayResponseSpec = $results['paths']['/array-response']['get']['responses']['200'];
876+
$this->assertArrayHasKey($customJsonType, $arrayResponseSpec['content']);
877+
$this->assertEquals('array', $arrayResponseSpec['content'][$customJsonType]['schema']['type']);
878+
$this->assertEquals(['foo', 'bar'], $arrayResponseSpec['content'][$customJsonType]['schema']['example']);
879+
880+
$stringResponseSpec = $results['paths']['/string-response']['get']['responses']['200'];
881+
$this->assertArrayHasKey($customJsonType, $stringResponseSpec['content']);
882+
$this->assertEquals('string', $stringResponseSpec['content'][$customJsonType]['schema']['type']);
883+
$this->assertEquals('a simple string', $stringResponseSpec['content'][$customJsonType]['schema']['example']);
884+
885+
$integerResponseSpec = $results['paths']['/integer-response']['get']['responses']['200'];
886+
$this->assertArrayHasKey($customJsonType, $integerResponseSpec['content']);
887+
$this->assertEquals('integer', $integerResponseSpec['content'][$customJsonType]['schema']['type']);
888+
$this->assertEquals(123, $integerResponseSpec['content'][$customJsonType]['schema']['example']);
889+
}
890+
891+
/** @test */
892+
public function handles_non_json_response_content_as_text_plain()
893+
{
894+
$endpoint = $this->createMockEndpointData([
895+
'httpMethods' => ['GET'],
896+
'uri' => '/text-response',
897+
'responses' => [[
898+
'status' => 200,
899+
'content' => 'This is a simple text response.',
900+
]],
901+
]);
902+
903+
$groups = [$this->createGroup([$endpoint])];
904+
$results = $this->generate($groups);
905+
906+
$responseSpec = $results['paths']['/text-response']['get']['responses']['200'];
907+
$this->assertArrayHasKey('text/plain', $responseSpec['content']);
908+
$this->assertEquals('string', $responseSpec['content']['text/plain']['schema']['type']);
909+
$this->assertEquals('This is a simple text response.', $responseSpec['content']['text/plain']['schema']['example']);
910+
}
911+
912+
/** @test */
913+
public function handles_null_and_empty_array_response_content()
914+
{
915+
$endpointWithNullContent = $this->createMockEndpointData([
916+
'uri' => '/null-response',
917+
'httpMethods' => ['GET'],
918+
'responses' => [[
919+
'status' => 200,
920+
'content' => null,
921+
]],
922+
]);
923+
924+
$endpointWithEmptyArray = $this->createMockEndpointData([
925+
'uri' => '/empty-array-response',
926+
'httpMethods' => ['GET'],
927+
'responses' => [[
928+
'status' => 200,
929+
'content' => '[]',
930+
]],
931+
]);
932+
933+
$groups = [$this->createGroup([$endpointWithNullContent, $endpointWithEmptyArray])];
934+
$results = $this->generate($groups);
935+
936+
$nullResponseSpec = $results['paths']['/null-response']['get']['responses']['200'];
937+
$this->assertArrayHasKey('application/json', $nullResponseSpec['content']);
938+
$schemaForNull = $nullResponseSpec['content']['application/json']['schema'];
939+
$this->assertEquals('object', $schemaForNull['type']);
940+
$this->assertTrue($schemaForNull['nullable']);
941+
942+
$emptyArrayResponseSpec = $results['paths']['/empty-array-response']['get']['responses']['200'];
943+
$this->assertArrayHasKey('application/json', $emptyArrayResponseSpec['content']);
944+
$schemaForEmptyArray = $emptyArrayResponseSpec['content']['application/json']['schema'];
945+
$this->assertEquals('array', $schemaForEmptyArray['type']);
946+
$this->assertEquals(['type' => 'object'], $schemaForEmptyArray['items']); // Scribe defaults items to 'object' for empty arrays
947+
$this->assertEquals([], $schemaForEmptyArray['example']);
948+
}
728949

729950
/** @test */
730951
public function adds_required_fields_on_array_of_objects()

0 commit comments

Comments
 (0)