瀏覽代碼

Transform FormRequest-parsed parameters into new array/object syntax

shalvah 4 年之前
父節點
當前提交
13cec547b5

+ 6 - 6
resources/views/partials/body-parameters.blade.php

@@ -7,8 +7,8 @@
 @component('scribe::components.field-details', [
   'name' => $name,
   'type' => $parameter['type'] ?? 'string',
-  'required' => $parameter['required'] ?? true,
-  'description' => $parameter['description'],
+  'required' => $parameter['required'] ?? false,
+  'description' => $parameter['description'] ?? '',
 ])
 @endcomponent
 </summary>
@@ -22,8 +22,8 @@
 @component('scribe::components.field-details', [
   'name' => $subfieldName,
   'type' => $subfield['type'] ?? 'string',
-  'required' => $subfield['required'] ?? true,
-  'description' => $subfield['description'],
+  'required' => $subfield['required'] ?? false,
+  'description' => $subfield['description'] ?? '',
 ])
 @endcomponent
 </p>
@@ -36,8 +36,8 @@
 @component('scribe::components.field-details', [
   'name' => $name,
   'type' => $parameter['type'] ?? 'string',
-  'required' => $parameter['required'] ?? true,
-  'description' => $parameter['description'],
+  'required' => $parameter['required'] ?? false,
+  'description' => $parameter['description'] ?? '',
 ])
 @endcomponent
 </p>

+ 25 - 27
src/Extracting/Generator.php

@@ -283,7 +283,7 @@ class Generator
                 $details['value'] = self::convertStringValueToUploadedFileInstance($details['value']);
             }
 
-            if (Str::contains($paramName, '.')) { // Object field
+            if (Str::contains($paramName, '.')) { // Object field (or array of objects)
                 self::setObject($cleanParameters, $paramName, $details['value'], $parameters);
             } else {
                 $cleanParameters[$paramName] = $details['value'];
@@ -295,32 +295,30 @@ class Generator
 
     public static function setObject(array &$results, string $path, $value, array $source)
     {
-        if (Str::contains($path, '.')) {
-            $parts = array_reverse(explode('.', $path));
-
-            array_shift($parts); // Get rid of the field name
-
-            $baseName = join('.', array_reverse($parts));
-            // The type should be indicated in the source object by now; we don't need it in the name
-            $normalisedBaseName = str_replace('[]', '', $baseName);
-
-            $parentData = Arr::get($source, $normalisedBaseName);
-            if ($parentData) {
-                // Path we use for lodash set
-                $dotPath = str_replace('[]', '.0', $path);
-                $noValue = new \stdClass();
-                if ($parentData['type'] === 'object') {
-                    if (Arr::get($results, $dotPath, $noValue) === $noValue) {
-                        Arr::set($results, $dotPath, $value);
-                    }
-                } else if ($parentData['type'] === 'object[]') {
-                    if (Arr::get($results, $dotPath, $noValue) === $noValue) {
-                        Arr::set($results, $dotPath, $value);
-                    }
-                    // If there's a second item in the array, set for that too.
-                    if ($value !== null && Arr::get($results, str_replace('[]', '.1', $baseName), $noValue) !== $noValue) {
-                        Arr::set($results, str_replace('.0', '.1', $dotPath), $value);
-                    }
+        $parts = array_reverse(explode('.', $path));
+
+        array_shift($parts); // Get rid of the field name
+
+        $baseName = join('.', array_reverse($parts));
+        // The type should be indicated in the source object by now; we don't need it in the name
+        $normalisedBaseName = str_replace('[]', '', $baseName);
+
+        $parentData = Arr::get($source, $normalisedBaseName);
+        if ($parentData) {
+            // Path we use for data_set
+            $dotPath = str_replace('[]', '.0', $path);
+            $noValue = new \stdClass();
+            if ($parentData['type'] === 'object') {
+                if (Arr::get($results, $dotPath, $noValue) === $noValue) {
+                    Arr::set($results, $dotPath, $value);
+                }
+            } else if ($parentData['type'] === 'object[]') {
+                if (Arr::get($results, $dotPath, $noValue) === $noValue) {
+                    Arr::set($results, $dotPath, $value);
+                }
+                // If there's a second item in the array, set for that too.
+                if ($value !== null && Arr::get($results, str_replace('[]', '.1', $baseName), $noValue) !== $noValue) {
+                    Arr::set($results, str_replace('.0', '.1', $dotPath), $value);
                 }
             }
         }

+ 95 - 19
src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php

@@ -50,7 +50,7 @@ class GetFromFormRequest extends Strategy
                 $parameterClass = new ReflectionClass($parameterClassName);
             } catch (ReflectionException $e) {
 
-                dump($e->getMessage());
+                dump("Exception: " . $e->getMessage());
                 continue;
             }
 
@@ -65,7 +65,7 @@ class GetFromFormRequest extends Strategy
                     $this->getCustomParameterData($formRequest)
                 );
 
-                return $bodyParametersFromFormRequest;
+                return $this->normaliseArrayAndObjectParameters($bodyParametersFromFormRequest);
             }
         }
 
@@ -159,6 +159,7 @@ class GetFromFormRequest extends Strategy
                 $parameterData['value'] = $this->castToType($parameterData['value'], $parameterData['type']);
             }
 
+            $parameterData['name'] = $parameter;
             $parameters[$parameter] = $parameterData;
         }
 
@@ -179,26 +180,17 @@ class GetFromFormRequest extends Strategy
         // but Laravel will ignore any nested array rules (`ids.*')
         // unless the key referenced (`ids`) exists in the dataset and is a non-empty array
         // So we'll create a single-item array for each array parameter
-        $values = collect($rules)
-            ->filter(function ($value, $key) {
-                return Str::contains($key, '.*');
-            })->mapWithKeys(function ($value, $key) {
-                if (Str::endsWith($key, '.*')) {
-                    // We're dealing with a simple array of primitives
-                    return [Str::substr($key, 0, -2) => [Str::random()]];
-                } elseif (Str::contains($key, '.*.')) {
-                    // We're dealing with an array of objects
-                    [$key, $property] = explode('.*.', $key);
-
-                    // Even though this will be overwritten by another property declaration in the rules, we're fine.
-                    // All we need is for Laravel to see this key exists
-                    return [$key => [[$property => Str::random()]]];
-                }
-            })->all();
+        $testData = [];
+        foreach ($rules as $key => $ruleset) {
+            if (!Str::contains($key, '.*')) continue;
+
+            // All we need is for Laravel to see this key exists
+            Arr::set($testData, str_replace('.*', '.0', $key), Str::random());
+        }
 
         // Now this will return the complete ruleset.
         // Nested array parameters will be present, with '*' replaced by '0'
-        $newRules = Validator::make($values, $rules)->getRules();
+        $newRules = Validator::make($testData, $rules)->getRules();
 
         // Transform the key names back from 'ids.0' to 'ids.*'
         return collect($newRules)->mapWithKeys(function ($val, $paramName) use ($rules) {
@@ -413,5 +405,89 @@ class GetFromFormRequest extends Strategy
 
         return [strtolower(trim($rule)), $ruleArguments];
     }
+
+    /**
+     * Laravel uses .* notation for arrays. This PR aims to normalise that into our "new syntax".
+     *
+     * 'years.*' with type 'integer' becomes 'years' with type 'integer[]'
+     * 'cars.*.age' with type 'string' becomes 'cars[].age' with type 'string' and 'cars' with type 'object[]'
+     * 'cars.*.things.*.*' with type 'string' becomes 'cars[].things' with type 'string[][]' and 'cars' with type
+     * 'object[]'
+     *
+     * @param <string, array>[] $bodyParametersFromValidationRules
+     *
+     * @return array
+     */
+    public function normaliseArrayAndObjectParameters(array $bodyParametersFromValidationRules): array
+    {
+        $results = [];
+        foreach ($bodyParametersFromValidationRules as $name => $details) {
+            // Change cars.*.dogs.things.*.* with type X to cars.*.dogs.things with type X[][]
+            while (Str::endsWith($name, '.*')) {
+                $details['type'] .= '[]';
+                $name = substr($name, 0, -2);
+            }
+
+            // Now make sure the field cars.*.dogs exists
+            $parentPath = $name;
+            while (Str::contains($parentPath, '.')) {
+                $parentPath = preg_replace('/\.[^.]+$/', '', $parentPath);
+                if (empty($bodyParametersFromValidationRules[$parentPath])) {
+                    if (Str::endsWith($parentPath, '.*')) {
+                        $parentPath = substr($parentPath, 0, -2);
+                        $type = 'object[]';
+                        $value = [[]];
+                    } else {
+                        $type = 'object';
+                        $value = [];
+                    }
+                    $normalisedPath = str_replace('.*.', '[].', $parentPath);
+                    $results[$normalisedPath] = [
+                        'name' => $normalisedPath,
+                        'type' => $type,
+                        'required' => false,
+                        'description' => '',
+                        'value' => $value,
+                    ];
+                } else {
+                    // if the parent field already exists with a type 'array'
+                    $parentDetails = $bodyParametersFromValidationRules[$parentPath];
+                    unset($bodyParametersFromValidationRules[$parentPath]);
+                    if (Str::endsWith($parentPath, '.*')) {
+                        $parentPath = substr($parentPath, 0, -2);
+                        $parentDetails['type'] = 'object[]';
+                        // Set the example too. Very likely the example array was an array of strings or an empty array
+                        if (empty($parentDetails['value']) || is_string($parentDetails['value'][0]) || is_string($parentDetails['value'][0][0])) {
+                            $parentDetails['value'] = [[]];
+                        }
+                    } else {
+                        $parentDetails['type'] = 'object';
+                        if (empty($parentDetails['value']) || is_string($parentDetails['value'][0])) {
+                            $parentDetails['value'] = [];
+                        }
+                    }
+                    $normalisedPath = str_replace('.*.', '[].', $parentPath);
+                    $parentDetails['name'] = $normalisedPath;
+                    $results[$normalisedPath] = $parentDetails;
+                }
+            }
+
+            $details['name'] = $name = str_replace('.*.', '[].', $name);
+            unset($details['setter']);
+
+            // Change type 'array' to 'object' if there are subfields
+            if (
+                $details['type'] === 'array'
+                && Arr::first(array_keys($bodyParametersFromValidationRules), function ($key) use ($name) {
+                    return preg_match("/{$name}\\.[^*]/", $key);
+                })
+            ) {
+                $details['type'] = 'object';
+            }
+            $results[$name] = $details;
+        }
+
+        return $results;
+    }
 }
 

+ 2 - 1
src/Writing/OpenAPISpecWriter.php

@@ -429,7 +429,8 @@ class OpenAPISpecWriter
             case 'double':
                 return 'number';
             case 'NULL':
-                return 'null';
+                // null is not an allowed type in OpenAPI
+                return 'string';
             default:
                 return $type;
         }

+ 11 - 9
tests/Fixtures/TestController.php

@@ -76,12 +76,14 @@ class TestController extends Controller
      * @bodyParam yet_another_param object required Some object params.
      * @bodyParam yet_another_param.name string required Subkey in the object param.
      * @bodyParam even_more_param number[] Subkey in the array param.
+     * @bodyParam book object
      * @bodyParam book.name string
      * @bodyParam book.author_id integer
-     * @bodyParam book[pages_count] integer
-     * @bodyParam ids.* integer
-     * @bodyParam users.*.first_name string The first name of the user. Example: John
-     * @bodyParam users.*.last_name string The last name of the user. Example: Doe
+     * @bodyParam book.pages_count integer
+     * @bodyParam ids int[]
+     * @bodyParam users object[]
+     * @bodyParam users[].first_name string The first name of the user. Example: John
+     * @bodyParam users[].last_name string The last name of the user. Example: Doe
      */
     public function withBodyParameters()
     {
@@ -102,11 +104,11 @@ class TestController extends Controller
     /**
      * Endpoint with body parameters as array.
      *
-     * @bodyParam *.first_name string The first name of the user. Example: John
-     * @bodyParam *.last_name string The last name of the user. Example: Doe
-     * @bodyParam *.contacts.*.first_name string The first name of the contact. Example: John
-     * @bodyParam *.contacts.*.last_name string The last name of the contact. Example: Doe
-     * @bodyParam *.roles.* string The name of the role. Example: Admin
+     * @bodyParam [].first_name string The first name of the user. Example: John
+     * @bodyParam [].last_name string The last name of the user. Example: Doe
+     * @bodyParam [].contacts[].first_name string The first name of the contact. Example: John
+     * @bodyParam [].contacts[].last_name string The last name of the contact. Example: Doe
+     * @bodyParam [].roles[] string[] The name of the role. Example: Admin
      */
     public function withBodyParametersAsArray()
     {

+ 4 - 3
tests/Fixtures/TestRequest.php

@@ -17,8 +17,9 @@ use Illuminate\Foundation\Http\FormRequest;
  * @bodyParam even_more_param array
  * @bodyParam book.name string
  * @bodyParam book.author_id integer
- * @bodyParam book[pages_count] integer
- * @bodyParam ids.* integer
+ * @bodyParam book.pages_count integer
+ * @bodyParam ids integer[]
+ * @bodyParam users OBJECT[] User details
  * @bodyParam users.*.first_name string The first name of the user. Example: John
  * @bodyParam users.*.last_name string The last name of the user. Example: Doe
  */
@@ -34,7 +35,7 @@ class TestRequest extends FormRequest
             'even_more_param' => 'array',
             'book.name' => 'string',
             'book.author_id' => 'integer',
-            'book[pages_count]' => 'integer',
+            'book.pages_count' => 'integer',
             'ids.*' => 'integer',
             'users.*.first_name' => ['string'],
             'users.*.last_name' => 'string',

+ 1 - 1
tests/Fixtures/collection.json

@@ -123,7 +123,7 @@
                         ],
                         "body": {
                             "mode": "raw",
-                            "raw": "{\n    \"user_id\": 9,\n    \"room_id\": \"consequatur\",\n    \"forever\": false,\n    \"another_one\": 11613.31890586,\n    \"yet_another_param\": {\n        \"name\": \"consequatur\"\n    },\n    \"even_more_param\": [\n        11613.31890586\n    ],\n    \"book\": {\n        \"name\": \"consequatur\",\n        \"author_id\": 17,\n        \"pages_count\": 17\n    },\n    \"ids\": [\n        17\n    ],\n    \"users\": [\n        {\n            \"first_name\": \"John\",\n            \"last_name\": \"Doe\"\n        }\n    ]\n}"
+                            "raw": "{\n    \"user_id\": 9,\n    \"room_id\": \"consequatur\",\n    \"forever\": false,\n    \"another_one\": 11613.31890586,\n    \"yet_another_param\": {\n        \"name\": \"consequatur\"\n    },\n    \"even_more_param\": [\n        11613.31890586,\n        11613.31890586\n    ],\n    \"book\": {\n        \"name\": \"consequatur\",\n        \"author_id\": 17,\n        \"pages_count\": 17\n    },\n    \"ids\": [\n        17,\n        17\n    ],\n    \"users\": [\n        {\n            \"first_name\": \"John\",\n            \"last_name\": \"Doe\"\n        },\n        {\n            \"first_name\": \"John\",\n            \"last_name\": \"Doe\"\n        }\n    ]\n}"
                         },
                         "description": "",
                         "auth": {

+ 139 - 18
tests/Fixtures/openapi.yaml

@@ -14,21 +14,55 @@ paths:
             parameters:
                 -
                     in: header
-                    name: Content-Type
+                    name: Custom-Header
+                    description: ''
+                    example: NotSoCustom
+                    schema:
+                        type: string
+            responses: {  }
+            tags:
+                - 'Group A'
+            security: []
+    /api/withFormDataParams:
+        post:
+            summary: 'Endpoint with body form data parameters.'
+            description: ''
+            parameters:
+                -
+                    in: header
+                    name: Custom-Header
                     description: ''
-                    example: application/json
+                    example: NotSoCustom
                     schema:
                         type: string
                 -
                     in: header
-                    name: Accept
+                    name: Content-Type
                     description: ''
-                    example: application/json
+                    example: multipart/form-data
                     schema:
                         type: string
             responses: {  }
             tags:
                 - 'Group A'
+            requestBody:
+                required: true
+                content:
+                    multipart/form-data:
+                        schema:
+                            type: object
+                            properties:
+                                name:
+                                    type: string
+                                    description: 'Name of image.'
+                                    example: cat.jpg
+                                image:
+                                    type: string
+                                    format: binary
+                                    description: 'The image.'
+                            required:
+                                - name
+                                - image
             security: []
     /api/withResponseTag:
         get:
@@ -37,16 +71,9 @@ paths:
             parameters:
                 -
                     in: header
-                    name: Content-Type
+                    name: Custom-Header
                     description: ''
-                    example: application/json
-                    schema:
-                        type: string
-                -
-                    in: header
-                    name: Accept
-                    description: ''
-                    example: application/json
+                    example: NotSoCustom
                     schema:
                         type: string
             responses:
@@ -86,6 +113,8 @@ paths:
                     required: true
                     schema:
                         type: string
+                        description: 'The id of the location.'
+                        example: consequatur
                 -
                     in: query
                     name: user_id
@@ -94,6 +123,8 @@ paths:
                     required: true
                     schema:
                         type: string
+                        description: 'The id of the user.'
+                        example: me
                 -
                     in: query
                     name: page
@@ -102,14 +133,18 @@ paths:
                     required: true
                     schema:
                         type: string
+                        description: 'The page number.'
+                        example: '4'
                 -
                     in: query
-                    name: 'filters.*'
+                    name: filters
                     description: 'The filters.'
                     example: consequatur
                     required: false
                     schema:
                         type: string
+                        description: 'The filters.'
+                        example: consequatur
                 -
                     in: query
                     name: url_encoded
@@ -118,21 +153,107 @@ paths:
                     required: false
                     schema:
                         type: string
+                        description: 'Used for testing that URL parameters will be URL-encoded where needed.'
+                        example: '+ []&='
                 -
                     in: header
-                    name: Content-Type
+                    name: Custom-Header
                     description: ''
-                    example: application/json
+                    example: NotSoCustom
                     schema:
                         type: string
+            responses: {  }
+            tags:
+                - 'Group A'
+            security: []
+    /api/withAuthTag:
+        get:
+            summary: ''
+            description: ''
+            parameters:
                 -
                     in: header
-                    name: Accept
+                    name: Custom-Header
                     description: ''
-                    example: application/json
+                    example: NotSoCustom
                     schema:
                         type: string
             responses: {  }
             tags:
                 - 'Group A'
+    '/api/echoesUrlParameters/{param}-{param2}/{param3}':
+        get:
+            summary: ''
+            description: ''
+            parameters:
+                -
+                    in: query
+                    name: something
+                    description: ''
+                    example: consequatur
+                    required: false
+                    schema:
+                        type: string
+                        description: ''
+                        example: consequatur
+                -
+                    in: header
+                    name: Custom-Header
+                    description: ''
+                    example: NotSoCustom
+                    schema:
+                        type: string
+            responses:
+                200:
+                    description: ''
+                    content:
+                        application/json:
+                            schema:
+                                type: object
+                                example:
+                                    param: '4'
+                                    param2: consequatur
+                                    param3: null
+                                    param4: null
+                                properties:
+                                    param: { type: string, example: '4' }
+                                    param2: { type: string, example: consequatur }
+                                    param3: { type: string, example: null }
+                                    param4: { type: string, example: null }
+            tags:
+                - Other😎
             security: []
+        parameters:
+            -
+                in: path
+                name: param
+                description: ''
+                example: '4'
+                required: true
+                schema:
+                    type: string
+            -
+                in: path
+                name: param2
+                description: 'Optional parameter.'
+                required: true
+                schema:
+                    type: string
+                examples:
+                    omitted:
+                        summary: 'When the value is omitted'
+                        value: ''
+                    present:
+                        summary: 'When the value is present'
+                        value: consequatur
+            -
+                in: path
+                name: param4
+                description: 'Optional parameter.'
+                required: true
+                schema:
+                    type: string
+                examples:
+                    omitted:
+                        summary: 'When the value is omitted'
+                        value: ''

+ 4 - 3
tests/GenerateDocumentationTest.php

@@ -16,6 +16,7 @@ use Knuckles\Scribe\Tests\Fixtures\TestUser;
 use Knuckles\Scribe\Tools\Utils;
 use Orchestra\Testbench\TestCase;
 use ReflectionException;
+use Symfony\Component\Yaml\Yaml;
 
 class GenerateDocumentationTest extends TestCase
 {
@@ -38,7 +39,7 @@ class GenerateDocumentationTest extends TestCase
 
     public function tearDown(): void
     {
-        Utils::deleteDirectoryAndContents('/public/docs');
+        // Utils::deleteDirectoryAndContents('/public/docs');
         Utils::deleteDirectoryAndContents('/resources/docs');
     }
 
@@ -282,8 +283,8 @@ class GenerateDocumentationTest extends TestCase
 
         $this->artisan('scribe:generate');
 
-        $generatedCollection = json_decode(file_get_contents(__DIR__ . '/../public/docs/openapi.yaml'), true);
-        $fixtureCollection = json_decode(file_get_contents(__DIR__ . '/Fixtures/openapi.yaml'), true);
+        $generatedCollection = Yaml::parseFile(__DIR__ . '/../public/docs/openapi.yaml');
+        $fixtureCollection = Yaml::parseFile(__DIR__ . '/Fixtures/openapi.yaml');
         $this->assertEquals($fixtureCollection, $generatedCollection);
     }
 

+ 60 - 6
tests/Strategies/BodyParameters/GetFromFormRequestTest.php

@@ -63,6 +63,12 @@ class GetFromFormRequestTest extends TestCase
                 'required' => false,
                 'description' => '',
             ],
+            'book' => [
+                'type' => 'object',
+                'description' => '',
+                'required' => false,
+                'value' => [],
+            ],
             'book.name' => [
                 'type' => 'string',
                 'description' => '',
@@ -73,23 +79,29 @@ class GetFromFormRequestTest extends TestCase
                 'description' => '',
                 'required' => false,
             ],
-            'book[pages_count]' => [
+            'book.pages_count' => [
                 'type' => 'integer',
                 'description' => '',
                 'required' => false,
             ],
-            'ids.*' => [
-                'type' => 'integer',
+            'ids' => [
+                'type' => 'integer[]',
                 'description' => '',
                 'required' => false,
             ],
-            'users.*.first_name' => [
+            'users' => [
+                'type' => 'object[]',
+                'description' => '',
+                'required' => false,
+                'value' => [[]],
+            ],
+            'users[].first_name' => [
                 'type' => 'string',
                 'description' => 'The first name of the user.',
                 'required' => false,
                 'value' => 'John',
             ],
-            'users.*.last_name' => [
+            'users[].last_name' => [
                 'type' => 'string',
                 'description' => 'The last name of the user.',
                 'required' => false,
@@ -132,9 +144,52 @@ class GetFromFormRequestTest extends TestCase
         }
     }
 
+    /** @test */
+    public function can_transform_arrays_and_objects()
+    {
+        $strategy = new GetFromFormRequest(new DocumentationConfig([]));
+        $ruleset = [
+                'array_param' => 'array|required',
+                'array_param.*' => 'string',
+            ];
+        $results = $strategy->normaliseArrayAndObjectParameters($strategy->getBodyParametersFromValidationRules($ruleset));
+        $this->assertCount(1, $results);
+        $this->assertEquals('string[]', $results['array_param']['type']);
+
+        $ruleset = [
+            'object_param' => 'array|required',
+            'object_param.field1.*' => 'string',
+            'object_param.field2' => 'integer|required',
+        ];
+        $results = $strategy->normaliseArrayAndObjectParameters($strategy->getBodyParametersFromValidationRules($ruleset));
+        $this->assertCount(3, $results);
+        $this->assertEquals('object', $results['object_param']['type']);
+        $this->assertEquals('string[]', $results['object_param.field1']['type']);
+        $this->assertEquals('integer', $results['object_param.field2']['type']);
+
+        $ruleset = [
+            'array_of_objects_with_array.*.another.*.one.field1.*' => 'string|required',
+            'array_of_objects_with_array.*.another.*.one.field2' => 'integer',
+            'array_of_objects_with_array.*.another.*.two.field2' => 'numeric',
+        ];
+        $results = $strategy->normaliseArrayAndObjectParameters($strategy->getBodyParametersFromValidationRules($ruleset));
+        $this->assertCount(7, $results);
+        $this->assertEquals('object[]', $results['array_of_objects_with_array']['type']);
+        $this->assertEquals('object[]', $results['array_of_objects_with_array[].another']['type']);
+        $this->assertEquals('object', $results['array_of_objects_with_array[].another[].one']['type']);
+        $this->assertEquals('object', $results['array_of_objects_with_array[].another[].two']['type']);
+        $this->assertEquals('string[]', $results['array_of_objects_with_array[].another[].one.field1']['type']);
+        $this->assertEquals('integer', $results['array_of_objects_with_array[].another[].one.field2']['type']);
+        $this->assertEquals('number', $results['array_of_objects_with_array[].another[].two.field2']['type']);
+    }
+
     public function supportedRules()
     {
         $description = 'A description';
+        // Key is just an identifier
+        // First array in each key is the validation ruleset,
+        // Second is custom information from bodyParameters()
+        // Third is expected result
         return [
             'required' => [
                 ['required_param' => 'required'],
@@ -252,5 +307,4 @@ class GetFromFormRequestTest extends TestCase
             ],
         ];
     }
-
 }