Ver código fonte

Respect examples set on parent items for array/objects

shalvah 3 anos atrás
pai
commit
12937e1ea1

+ 7 - 0
CHANGELOG.md

@@ -12,6 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 ### Removals
 
+## 3.11.0 (Tuesday, 21 September 2021)
+### Added
+- Introduced `beforeResponseCall()` ([25cbdc193f277c70d471a92b5019156c603255b7](https://github.com/knuckleswtf/scribe/commit/25cbdc193f277c70d471a92b5019156c603255b7))
+
+### Fixed
+- Parse multiline validation comments properly  ([e3b7dbefc1cbb25ca773f7c74c84bbe8fe8740e5](https://github.com/knuckleswtf/scribe/commit/e3b7dbefc1cbb25ca773f7c74c84bbe8fe8740e5))
+
 ## 3.10.3 (Monday, 20 September 2021)
 ### Fixed
 - Ignore user-specified values in upgrader (fixes #327) ([75b592724a8639583b4d660033549c8645a61b2b](https://github.com/knuckleswtf/scribe/commit/75b592724a8639583b4d660033549c8645a61b2b))

+ 16 - 17
src/Extracting/Extractor.php

@@ -280,7 +280,7 @@ class Extractor
             if (Str::contains($paramName, '.')) { // Object field (or array of objects)
                 self::setObject($cleanParameters, $paramName, $details->example, $parameters, $details->required);
             } else {
-                $cleanParameters[$paramName] = $details->example;
+                $cleanParameters[$paramName] = $details->example instanceof \stdClass ? $details->example : $details->example;
             }
         }
 
@@ -318,8 +318,16 @@ class Extractor
             $parentData = Arr::get($source, $baseNameInOriginalParams);
             // Path we use for data_set
             $dotPath = str_replace('[]', '.0', $path);
+
+            // Don't overwrite parent if there's already data there
+
             if ($parentData->type === 'object') {
-                if (!Arr::has($results, $dotPath)) {
+                $parentPath = explode('.', $dotPath);
+                $property = array_pop($parentPath);
+                $parentPath = implode('.', $parentPath);
+
+                $exampleFromParent = Arr::get($results, $dotPath) ?? $parentData->example[$property] ?? null;
+                if (empty($exampleFromParent)) {
                     Arr::set($results, $dotPath, $value);
                 }
             } else if ($parentData->type === 'object[]') {
@@ -329,24 +337,15 @@ class Extractor
                     if (isset($results['[]'][0]) && !Arr::has($results['[]'][0], $valueDotPath)) {
                         Arr::set($results['[]'][0], $valueDotPath, $value);
                     }
-                    // If there's a second item in the array, set for that too.
-                    if ($value !== null && isset($results['[]'][1])) {
-                        // If value is optional, flip a coin on whether to set or not
-                        if ($isRequired || array_rand([true, false], 1)) {
-                            Arr::set($results['[]'][1], $valueDotPath, $value);
-                        }
-                    }
                 } else {
-                    if (!Arr::has($results, $dotPath)) {
+                    $parentPath = explode('.', $dotPath);
+                    $index = (int)array_pop($parentPath);
+                    $parentPath = implode('.', $parentPath);
+
+                    $exampleFromParent = Arr::get($results, $dotPath) ?? $parentData->example[$index] ?? null;
+                    if (empty($exampleFromParent)) {
                         Arr::set($results, $dotPath, $value);
                     }
-                    // If there's a second item in the array, set for that too.
-                    if ($value !== null && Arr::has($results, Str::replaceLast('[]', '.1', $baseName))) {
-                        // If value is optional, flip a coin on whether to set or not
-                        if ($isRequired || array_rand([true, false], 1)) {
-                            Arr::set($results, Str::replaceLast('.0', '.1', $dotPath), $value);
-                        }
-                    }
                 }
             }
         }

+ 2 - 5
src/Extracting/ParamHelpers.php

@@ -36,11 +36,8 @@ trait ParamHelpers
         }
 
         if ($isListType) {
-            // Return a two-array item for a list.
-            return fn() => array_map(
-                fn() => $this->generateDummyValue($baseType),
-                range(0, $size ? $size - 1 : 1)
-            );
+            // Return a one-array item for a list.
+            return fn() => [$this->generateDummyValue($baseType)];
         }
 
         $faker = $this->getFaker();

+ 41 - 26
src/Extracting/ParsesValidationRules.php

@@ -98,7 +98,6 @@ trait ParsesValidationRules
                     $parameterData['description'] .= '.';
                 }
 
-                $parameterData = $this->updateParameterExample($parameterData);
                 $parameters[$parameter] = $parameterData;
             } catch (Throwable $e) {
                 if ($e instanceof ScribeException) {
@@ -484,26 +483,24 @@ trait ParsesValidationRules
         return [strtolower(trim($rule)), $ruleArguments];
     }
 
-    protected function updateParameterExample(array $parameterData): array
+    protected function getParameterExample(array $parameterData)
     {
         // If no example was given by the user, set an autogenerated example.
         // Each parsed rule returns a 'setter' function. We'll evaluate the last one.
-        if ($parameterData['example'] === self::$MISSING_VALUE && isset($parameterData['setter'])) {
-            $parameterData['example'] = $parameterData['setter']();
-        }
-
         if ($parameterData['example'] === self::$MISSING_VALUE) {
-            $parameterData['example'] = $parameterData['required']
-                ? $this->generateDummyValue($parameterData['type'])
-                : null;
-        }
-
-        if (!is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) {
+            if (isset($parameterData['setter'])) {
+                return $parameterData['setter']();
+            } else {
+                return $parameterData['required']
+                    ? $this->generateDummyValue($parameterData['type'])
+                    : null;
+            }
+        } else if (!is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) {
             // Casting again is important since values may have been cast to string in the validator
-            $parameterData['example'] = $this->castToType($parameterData['example'], $parameterData['type']);
+            return $this->castToType($parameterData['example'], $parameterData['type']);
         }
 
-        return $parameterData;
+        return $parameterData['example'] === self::$MISSING_VALUE ? null : $parameterData['example'];
     }
 
     /**
@@ -521,18 +518,20 @@ trait ParsesValidationRules
     public function normaliseArrayAndObjectParameters(array $parametersFromValidationRules): array
     {
         $results = [];
+        $originalParams = $parametersFromValidationRules;
         foreach ($parametersFromValidationRules as $name => $details) {
             if (isset($results[$name])) {
                 continue;
             }
             if ($details['type'] === 'array') {
-                // Generic array type. If a child item exists,
+                // This is the parent field, a generic array type. If a child item exists,
                 // this will be overwritten with the correct type (such as object or object[]) by the code below
                 $details['type'] = 'string[]';
             }
 
             if (Str::endsWith($name, '.*')) {
                 // Wrap array example properly
+                $details['example'] = $this->exampleOrDefault($details, $this->getParameterExample($details));
                 $needsWrapping = !is_array($details['example']);
 
                 $nestingLevel = 0;
@@ -541,14 +540,6 @@ trait ParsesValidationRules
                     $details['type'] .= '[]';
                     if ($needsWrapping) {
                         $details['example'] = [$details['example']];
-                        // Make it two items in each array
-                        if (isset($details['setter'])) {
-                            $secondArrayItem = $secondExampleValue = $details['setter']();
-                            for ($i = 0; $i < $nestingLevel; $i++) {
-                                $secondArrayItem = [$secondExampleValue];
-                            }
-                            $details['example'][] = $secondArrayItem;
-                        }
                     }
                     $name = substr($name, 0, -2);
                     $nestingLevel++;
@@ -580,19 +571,21 @@ trait ParsesValidationRules
                     // if the parent field already exists with a type 'array'
                     $parentDetails = $parametersFromValidationRules[$parentPath];
                     unset($parametersFromValidationRules[$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['example']) || is_string($parentDetails['example'][0]) || is_string($parentDetails['example'][0][0])) {
+                        if (!$this->examplePresent($parentDetails) || is_string($parentDetails['example'][0]) || is_string($parentDetails['example'][0][0])) {
                             $parentDetails['example'] = [[]];
                         }
                     } else {
                         $parentDetails['type'] = 'object';
-                        if (empty($parentDetails['example']) || is_string($parentDetails['example'][0])) {
+                        if (!$this->examplePresent($parentDetails) || is_string($parentDetails['example'][0])) {
                             $parentDetails['example'] = [];
                         }
                     }
+
                     $normalisedPath = str_replace('.*.', '[].', $parentPath);
                     $parentDetails['name'] = $normalisedPath;
                     $results[$normalisedPath] = $parentDetails;
@@ -600,7 +593,11 @@ trait ParsesValidationRules
             }
 
             $details['name'] = $name = str_replace('.*.', '[].', $name);
-            unset($details['setter']);
+
+            // If an example was specified on the parent, use that instead.
+            if (isset($originalParams[$details['name']]) && $this->examplePresent($originalParams[$details['name']])) {
+                $details['example'] = $originalParams[$details['name']]['example'];
+            }
 
             // Change type 'array' to 'object' if there are subfields
             if (
@@ -611,6 +608,10 @@ trait ParsesValidationRules
             ) {
                 $details['type'] = 'object';
             }
+
+            $details['example'] = $this->getParameterExample($details);
+            unset($details['setter']);
+
             $results[$name] = $details;
 
         }
@@ -618,6 +619,20 @@ trait ParsesValidationRules
         return $results;
     }
 
+    private function exampleOrDefault(array $parameterData, $default)
+    {
+        if (!isset($parameterData['example']) || $parameterData['example'] === self::$MISSING_VALUE) {
+            return $default;
+        }
+
+        return $parameterData['example'];
+    }
+
+    private function examplePresent(array $parameterData)
+    {
+        return isset($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE;
+    }
+
     protected function getDescription(string $rule, array $arguments = [], $baseType = 'string'): string
     {
         $description = trans("validation.{$rule}");

+ 0 - 2
src/Extracting/Strategies/Responses/ResponseCalls.php

@@ -85,8 +85,6 @@ class ResponseCalls extends Strategy
 
         $request = $this->runPreRequestHook($request, $endpointData);
 
-        ray($request->headers);
-
         try {
             $response = $this->makeApiCall($request, $endpointData->route);
             $response = [

+ 1 - 1
src/Tools/Globals.php

@@ -4,7 +4,7 @@ namespace Knuckles\Scribe\Tools;
 
 class Globals
 {
-    public const SCRIBE_VERSION = '3.10.3';
+    public const SCRIBE_VERSION = '3.11.0';
 
     public static bool $shouldBeVerbose = false;
 

+ 2 - 2
tests/Fixtures/collection.json

@@ -47,7 +47,7 @@
             ],
             "body": {
               "mode": "raw",
-              "raw": "[{\"first_name\":\"John\",\"last_name\":\"Doe\",\"contacts\":[{\"first_name\":\"Janelle\",\"last_name\":\"Monáe\"}],\"roles\":[\"Admin\"]},{\"first_name\":\"John\",\"last_name\":\"Doe\",\"contacts\":[{\"first_name\":\"Janelle\",\"last_name\":\"Monáe\"}],\"roles\":[\"Admin\"]}]"
+              "raw": "[{\"first_name\":\"John\",\"last_name\":\"Doe\",\"contacts\":[{\"first_name\":\"Janelle\",\"last_name\":\"Monáe\"}],\"roles\":[\"Admin\"]}]"
             },
             "description": "",
             "auth": {
@@ -130,7 +130,7 @@
             ],
             "body": {
               "mode": "raw",
-              "raw": "{\"user_id\":9,\"room_id\":\"consequatur\",\"forever\":false,\"another_one\":11613.31890586,\"yet_another_param\":{\"name\":\"consequatur\"},\"even_more_param\":[11613.31890586,11613.31890586],\"book\":{\"name\":\"consequatur\",\"author_id\":17,\"pages_count\":17},\"ids\":[17,17],\"users\":[{\"first_name\":\"John\",\"last_name\":\"Doe\"},{\"first_name\":\"John\",\"last_name\":\"Doe\"}]}"
+              "raw": "{\"user_id\":9,\"room_id\":\"consequatur\",\"forever\":false,\"another_one\":11613.31890586,\"yet_another_param\":{\"name\":\"consequatur\"},\"even_more_param\":[11613.31890586],\"book\":{\"name\":\"consequatur\",\"author_id\":17,\"pages_count\":17},\"ids\":[17],\"users\":[{\"first_name\":\"John\",\"last_name\":\"Doe\"}]}"
             },
             "description": "",
             "auth": {

+ 1 - 9
tests/Fixtures/openapi.yaml

@@ -296,21 +296,13 @@ paths:
                               contacts:
                               - first_name: Janelle
                                 last_name: Monáe
-                              - {}
-                              roles: [Admin]
-                            - first_name: 'John'
-                              last_name: 'Doe'
-                              contacts:
-                              - first_name: Janelle
-                                last_name: Monáe
-                              - {}
                               roles: [Admin]
                             items:
                                 type: object
                                 properties:
                                     first_name: { type: string, description: 'The first name of the user.', example: John }
                                     last_name: { type: string, description: 'The last name of the user.', example: Doe }
-                                    contacts: { type: array, description: 'Contact info', example: [ [ ], [ ] ], items: { type: object, properties: { first_name: { type: string, description: 'The first name of the contact.', example: Janelle }, last_name: { type: string, description: 'The last name of the contact.', example: Monáe } }, required: [ first_name, last_name ] } }
+                                    contacts: { type: array, description: 'Contact info', example: [ [ ] ], items: { type: object, properties: { first_name: { type: string, description: 'The first name of the contact.', example: Janelle }, last_name: { type: string, description: 'The last name of the contact.', example: Monáe } }, required: [ first_name, last_name ] } }
                                     roles: { type: array, description: 'The name of the role.', example: [ Admin ], items: { type: string } }
                                 required:
                                     - first_name

+ 1 - 5
tests/Unit/ExtractorTest.php

@@ -99,7 +99,7 @@ class ExtractorTest extends TestCase
             'list_of_objects' => [
                 'name' => 'list_of_objects',
                 'type' => 'object[]',
-                'example' => [[], []],
+                'example' => [[]],
             ],
             'list_of_objects[].key1' => [
                 'name' => 'list_of_objects.key1',
@@ -131,10 +131,6 @@ class ExtractorTest extends TestCase
                     'key1' => 'John',
                     'key2' => false,
                 ],
-                [
-                    'key1' => 'John',
-                    'key2' => false,
-                ],
             ],
         ], $cleanBodyParameters);
     }