Procházet zdrojové kódy

Fix missing required list in OpenAPI for nested body params

Shalvah před 3 měsíci
rodič
revize
99b71ebf05

+ 11 - 9
src/Writing/OpenAPISpecWriter.php

@@ -197,7 +197,7 @@ class OpenAPISpecWriter
         return $parameters;
     }
 
-    protected function generateEndpointRequestBodySpec(OutputEndpointData $endpoint)
+    protected function generateEndpointRequestBodySpec(OutputEndpointData $endpoint): array|\stdClass
     {
         $body = [];
 
@@ -405,9 +405,9 @@ class OpenAPISpecWriter
 
             case 'object':
                 $properties = collect($decoded)->mapWithKeys(function ($value, $key) use ($endpoint) {
-                    return [$key => $this->generateSchemaForValue($value, $endpoint, $key)];
+                    return [$key => $this->generateSchemaForResponseValue($value, $endpoint, $key)];
                 })->toArray();
-                $required = $this->filterRequiredFields($endpoint, array_keys($properties));
+                $required = $this->filterRequiredResponseFields($endpoint, array_keys($properties));
 
                 $data = [
                     'application/json' => [
@@ -551,6 +551,7 @@ class OpenAPISpecWriter
                 'properties' => $this->objectIfEmpty(collect($field->__fields)->mapWithKeys(function ($subfield, $subfieldName) {
                     return [$subfieldName => $this->generateFieldData($subfield)];
                 })->all()),
+                'required' => collect($field->__fields)->filter(fn ($f) => $f['required'])->keys()->toArray(),
             ];
         } else {
             $schema = [
@@ -589,17 +590,18 @@ class OpenAPISpecWriter
      * object)}, and possibly a description for each property. The $endpoint and $path are used for looking up response
      * field descriptions.
      */
-    public function generateSchemaForValue(mixed $value, OutputEndpointData $endpoint, string $path): array
+    public function generateSchemaForResponseValue(mixed $value, OutputEndpointData $endpoint, string $path): array
     {
+        // If $value is a JSON object
         if ($value instanceof \stdClass) {
             $value = (array)$value;
             $properties = [];
             // Recurse into the object
             foreach ($value as $subField => $subValue) {
                 $subFieldPath = sprintf('%s.%s', $path, $subField);
-                $properties[$subField] = $this->generateSchemaForValue($subValue, $endpoint, $subFieldPath);
+                $properties[$subField] = $this->generateSchemaForResponseValue($subValue, $endpoint, $subFieldPath);
             }
-            $required = $this->filterRequiredFields($endpoint, array_keys($properties), $path);
+            $required = $this->filterRequiredResponseFields($endpoint, array_keys($properties), $path);
 
             $schema = [
                 'type' => 'object',
@@ -633,10 +635,10 @@ class OpenAPISpecWriter
 
             if ($typeOfEachItem === 'object') {
                 $schema['items']['properties'] = collect($sample)->mapWithKeys(function ($v, $k) use ($endpoint, $path) {
-                    return [$k => $this->generateSchemaForValue($v, $endpoint, "$path.$k")];
+                    return [$k => $this->generateSchemaForResponseValue($v, $endpoint, "$path.$k")];
                 })->toArray();
 
-                $required = $this->filterRequiredFields($endpoint, array_keys($schema['items']['properties']), $path);
+                $required = $this->filterRequiredResponseFields($endpoint, array_keys($schema['items']['properties']), $path);
                 if ($required) {
                     $schema['required'] = $required;
                 }
@@ -649,7 +651,7 @@ class OpenAPISpecWriter
     /**
      * Given an enpoint and a set of object keys at a path, return the properties that are specified as required.
      */
-    public function filterRequiredFields(OutputEndpointData $endpoint, array $properties, string $path = ''): array
+    public function filterRequiredResponseFields(OutputEndpointData $endpoint, array $properties, string $path = ''): array
     {
         $required = [];
         foreach ($properties as $property) {

+ 111 - 8
tests/Unit/OpenAPISpecWriterTest.php

@@ -861,17 +861,39 @@ class OpenAPISpecWriterTest extends BaseUnitTest
     public function adds_enum_values_to_response_properties()
     {
         $endpointData = $this->createMockEndpointData([
-            'uri' => '/path',
-            'httpMethods' => ['POST'],
+            'httpMethods' => ['GEt'],
+            'uri' => '/path1',
             'responses' => [
                 [
                     'status' => 200,
-                    'description' => 'This one',
-                    'content' => '{"status": "one"}',
+                    'description' => 'List of entities',
+                    'content' => '{"data":[{"name":"Resource name","uuid":"UUID","primary":true}]}',
                 ],
             ],
             'responseFields' => [
-                'status' => ['enumValues' => ['one', 'two', 'three']],
+                'data' => [
+                    'name' => 'data',
+                    'type' => 'array',
+                    'description' => 'Data wrapper',
+                ],
+                'data.name' => [
+                    'name' => 'Resource name',
+                    'type' => 'string',
+                    'description' => 'Name of the resource object',
+                    'required' => true,
+                ],
+                'data.uuid' => [
+                    'name' => 'Resource UUID',
+                    'type' => 'string',
+                    'description' => 'Unique ID for the resource',
+                    'required' => true,
+                ],
+                'data.primary' => [
+                    'name' => 'Is primary',
+                    'type' => 'bool',
+                    'description' => 'Is primary resource',
+                    'required' => true,
+                ],
             ],
         ]);
 
@@ -881,19 +903,100 @@ class OpenAPISpecWriterTest extends BaseUnitTest
 
         $this->assertArraySubset([
             '200' => [
+                'description' => 'List of entities',
+                'content' => [
+                    'application/json' => [
+                        'schema' => [
+                            'type' => 'object',
+                            'properties' => [
+                                'data' => [
+                                    'type' => 'array',
+                                    'description' => 'Data wrapper',
+                                    'items' => [
+                                        'type' => 'object',
+                                        'properties' => [
+                                            'name' => [
+                                                'type' => 'string',
+                                                'description' => 'Name of the resource object',
+                                            ],
+                                            'uuid' => [
+                                                'type' => 'string',
+                                                'description' => 'Unique ID for the resource',
+                                            ],
+                                            'primary' => [
+                                                'type' => 'boolean',
+                                                'description' => 'Is primary resource',
+                                            ],
+                                        ],
+                                    ],
+                                    'required' => [
+                                        'name',
+                                        'uuid',
+                                        'primary',
+                                    ]
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ], $results['paths']['/path1']['get']['responses']);
+    }
+
+    /** @test */
+    public function lists_required_properties_in_request_body()
+    {
+        $endpointData = $this->createMockEndpointData([
+            'uri' => '/path',
+            'httpMethods' => ['POST'],
+            'bodyParameters' => [
+                'my_field' => [
+                    'name' => 'my_field',
+                    'description' => '',
+                    'required' => true,
+                    'example' => 'abc',
+                    'type' => 'string',
+                    'nullable' => false,
+                ],
+                'other_field.nested_field' => [
+                    'name' => 'nested_field',
+                    'description' => '',
+                    'required' => true,
+                    'example' => 'abc',
+                    'type' => 'string',
+                    'nullable' => false,
+                ],
+            ],
+        ]);
+        $groups = [$this->createGroup([$endpointData])];
+        $results = $this->generate($groups);
+
+        $this->assertArraySubset([
+            'requestBody' => [
                 'content' => [
                     'application/json' => [
                         'schema' => [
+                            'type' => 'object',
                             'properties' => [
-                                'status' => [
-                                    'enum' => ['one', 'two', 'three'],
+                                'my_field' => [
+                                    'type' => 'string',
+                                ],
+                                'other_field' => [
+                                    'type' => 'object',
+                                    'properties' => [
+                                        'nested_field' => [
+                                            'type' => 'string',
+                                        ],
+                                    ],
+                                    'required' => ['nested_field'],
                                 ],
                             ],
+                            'required' => ['my_field']
                         ],
                     ],
                 ],
             ],
-        ], $results['paths']['/path']['post']['responses']);
+        ], $results['paths']['/path']['post']);
     }
 
     protected function createMockEndpointData(array $custom = []): OutputEndpointData