Browse Source

Allow multiple content types and schemas (via oneOf) according to OpenAPI 3.0 spec (#792)

* rewrite generateEndpointResponsesSpec to allow multiple content types and schemas (via oneOf) according to OpenAPI 3.0 spec

* Update OpenAPISpecWriter.php

* Add test

---------

Co-authored-by: Shalvah <shalvah@users.noreply.github.com>
Rouven Hurling 1 year ago
parent
commit
bd2812731c
2 changed files with 105 additions and 1 deletions
  1. 25 0
      src/Writing/OpenAPISpecWriter.php
  2. 80 1
      tests/Unit/OpenAPISpecWriterTest.php

+ 25 - 0
src/Writing/OpenAPISpecWriter.php

@@ -269,7 +269,32 @@ class OpenAPISpecWriter
                 $responses[204] = [
                     'description' => $this->getResponseDescription($response),
                 ];
+            } elseif (isset($responses[$response->status])) {
+                // If we already have a response for this status code and content type,
+                // we change to a `oneOf` which includes all the responses
+                $content = $this->generateResponseContentSpec($response->content, $endpoint);
+                $contentType = array_keys($content)[0];
+                if (isset($responses[$response->status]['content'][$contentType])) {
+                    // If we've already created the oneOf object, add this response
+                    if (isset($responses[$response->status]['content'][$contentType]['schema']['oneOf'])) {
+                        $responses[$response->status]['content'][$contentType]['schema']['oneOf'][] = $content[$contentType];
+                    } else {
+                        // Create the oneOf object
+                        $existingResponseExample = array_replace([
+                            'description' => $responses[$response->status]['description'],
+                        ], $responses[$response->status]['content'][$contentType]['schema']);
+                        $newResponseExample = array_replace([
+                            'description' => $this->getResponseDescription($response),
+                        ], $content[$contentType]['schema']);
+
+                        $responses[$response->status]['description'] = '';
+                        $responses[$response->status]['content'][$contentType]['schema'] = [
+                            'oneOf' => [$existingResponseExample, $newResponseExample]
+                        ];
+                    }
+                }
             } else {
+                // Store as the response for this status
                 $responses[$response->status] = [
                     'description' => $this->getResponseDescription($response),
                     'content' => $this->generateResponseContentSpec($response->content, $endpoint),

+ 80 - 1
tests/Unit/OpenAPISpecWriterTest.php

@@ -445,7 +445,7 @@ class OpenAPISpecWriterTest extends BaseUnitTest
                     'type' => 'string',
                     'description' => 'Parameter description, ha!',
                 ],
-                'sub level 0.sub level 1 key 3.sub level 2 key 1'=> [
+                'sub level 0.sub level 1 key 3.sub level 2 key 1' => [
                     'description' => 'This is description of nested object',
                 ]
             ],
@@ -557,6 +557,85 @@ class OpenAPISpecWriterTest extends BaseUnitTest
         ], $results['paths']['/path2']['put']['responses']);
     }
 
+    /** @test */
+    public function adds_multiple_responses_correctly_using_oneOf()
+    {
+        $endpointData1 = $this->createMockEndpointData([
+            'httpMethods' => ['POST'],
+            'uri' => '/path1',
+            'responses' => [
+                [
+                    'status' => 201,
+                    'description' => 'This one',
+                    'content' => '{"this": "one"}',
+                ],
+                [
+                    'status' => 201,
+                    'description' => 'No, that one.',
+                    'content' => '{"that": "one"}',
+                ],
+                [
+                    'status' => 200,
+                    'description' => 'A separate one',
+                    'content' => '{"the other": "one"}',
+                ],
+            ],
+        ]);
+        $groups = [$this->createGroup([$endpointData1])];
+
+        $results = $this->generate($groups);
+
+        $this->assertArraySubset([
+            '200' => [
+                'description' => 'A separate one',
+                'content' => [
+                    'application/json' => [
+                        'schema' => [
+                            'type' => 'object',
+                            'properties' => [
+                                'the other' => [
+                                    'example' => "one",
+                                    'type' => 'string',
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+            '201' => [
+                'description' => '',
+                'content' => [
+                    'application/json' => [
+                        'schema' => [
+                            'oneOf' => [
+                                [
+                                    'type' => 'object',
+                                    'description' => 'This one',
+                                    'properties' => [
+                                        'this' => [
+                                            'example' => "one",
+                                            'type' => 'string',
+                                        ],
+                                    ],
+                                ],
+                                [
+                                    'type' => 'object',
+                                    'description' => 'No, that one.',
+                                    'properties' => [
+                                        'that' => [
+                                            'example' => "one",
+                                            'type' => 'string',
+                                        ],
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ], $results['paths']['/path1']['post']['responses']);
+    }
+
     protected function createMockEndpointData(array $custom = []): OutputEndpointData
     {
         $faker = Factory::create();