Explorar o código

Laid groundwork for the new array/object syntax in next major

shalvah %!s(int64=4) %!d(string=hai) anos
pai
achega
d7b9b89488

+ 1 - 0
composer.dingo.json

@@ -17,6 +17,7 @@
     ],
     ],
     "require": {
     "require": {
         "php": ">=7.2.5",
         "php": ">=7.2.5",
+        "ext-fileinfo": "*",
         "ext-json": "*",
         "ext-json": "*",
         "dingo/api": "^2.3",
         "dingo/api": "^2.3",
         "erusev/parsedown": "^1.7.4",
         "erusev/parsedown": "^1.7.4",

+ 84 - 83
composer.json

@@ -1,85 +1,86 @@
 {
 {
-    "name": "knuckleswtf/scribe",
-    "license": "MIT",
-    "description": "Generate API documentation for humans from your Laravel codebase.✍",
-    "keywords": [
-        "API",
-        "documentation",
-        "laravel",
-        "dingo"
-    ],
-    "homepage": "http://github.com/knuckleswtf/scribe",
-    "authors": [
-        {
-            "name": "Shalvah",
-            "email": "hello@shalvah.me"
-        }
-    ],
-    "require": {
-        "php": ">=7.2.5",
-        "ext-json": "*",
-        "erusev/parsedown": "^1.7.4",
-        "fzaninotto/faker": "^1.9.1",
-        "illuminate/console": "^5.8|^6.0|^7.0",
-        "illuminate/routing": "^5.8|^6.0|^7.0",
-        "illuminate/support": "^5.8|^6.0|^7.0",
-        "knuckleswtf/pastel": "^1.3.3",
-        "league/flysystem": "^1.0",
-        "mpociot/reflection-docblock": "^1.0.1",
-        "nunomaduro/collision": "^3.0|^4.0|^5.0",
-        "ramsey/uuid": "^3.8|^4.0",
-        "shalvah/clara": "^2.6",
-        "symfony/var-exporter": "^4.0|^5.0",
-        "symfony/yaml": "^4.0|^5.0"
-    },
-    "require-dev": {
-        "brianium/paratest": "^4.0",
-        "dms/phpunit-arraysubset-asserts": "^0.1.0",
-        "laravel/lumen-framework": "^5.7|^6.0|^7.0",
-        "league/fractal": "^0.19.0",
-        "orchestra/testbench": "^3.7|^4.0|^5.0",
-        "phpstan/phpstan": "^0.12.19",
-        "phpunit/phpunit": "^8.0|^9.0"
-    },
-    "suggest": {
-        "league/fractal": "Required for transformers support"
-    },
-    "autoload": {
-        "psr-4": {
-            "Knuckles\\Scribe\\": "src/"
-        }
-    },
-    "autoload-dev": {
-        "psr-4": {
-            "Knuckles\\Scribe\\Tests\\": "tests/"
-        }
-    },
-    "scripts": {
-        "lint": "phpstan analyse -c ./phpstan.neon src",
-        "test": "phpunit --stop-on-failure --exclude-group dingo",
-        "test-ci": "phpunit --exclude-group dingo",
-        "test-parallel": "paratest -p3 --stop-on-failure --parallel-suite --exclude-group dingo tests",
-        "test-parallel-ci": "paratest -p3 --parallel-suite --exclude-group dingo tests"
-    },
-    "extra": {
-        "laravel": {
-            "providers": [
-                "Knuckles\\Scribe\\ScribeServiceProvider"
-            ]
-        }
-    },
-    "config": {
-        "preferred-install": "dist",
-        "sort-packages": true,
-        "process-timeout": 600
-    },
-    "replace": {
-        "mpociot/laravel-apidoc-generator": "*"
-    },
-    "funding": [
-        {
-            "type": "patreon",
-            "url": "https://patreon.com/shalvah"
-        }
-    ]
+  "name": "knuckleswtf/scribe",
+  "license": "MIT",
+  "description": "Generate API documentation for humans from your Laravel codebase.✍",
+  "keywords": [
+    "API",
+    "documentation",
+    "laravel",
+    "dingo"
+  ],
+  "homepage": "http://github.com/knuckleswtf/scribe",
+  "authors": [
+    {
+      "name": "Shalvah",
+      "email": "hello@shalvah.me"
+    }
+  ],
+  "require": {
+    "php": ">=7.2.5",
+    "ext-fileinfo": "*",
+    "ext-json": "*",
+    "erusev/parsedown": "^1.7.4",
+    "fzaninotto/faker": "^1.9.1",
+    "illuminate/console": "^5.8|^6.0|^7.0",
+    "illuminate/routing": "^5.8|^6.0|^7.0",
+    "illuminate/support": "^5.8|^6.0|^7.0",
+    "knuckleswtf/pastel": "^1.3.3",
+    "league/flysystem": "^1.0",
+    "mpociot/reflection-docblock": "^1.0.1",
+    "nunomaduro/collision": "^3.0|^4.0|^5.0",
+    "ramsey/uuid": "^3.8|^4.0",
+    "shalvah/clara": "^2.6",
+    "symfony/var-exporter": "^4.0|^5.0",
+    "symfony/yaml": "^4.0|^5.0"
+  },
+  "require-dev": {
+    "brianium/paratest": "^4.0",
+    "dms/phpunit-arraysubset-asserts": "^0.1.0",
+    "laravel/lumen-framework": "^5.7|^6.0|^7.0",
+    "league/fractal": "^0.19.0",
+    "orchestra/testbench": "^3.7|^4.0|^5.0",
+    "phpstan/phpstan": "^0.12.19",
+    "phpunit/phpunit": "^8.0|^9.0"
+  },
+  "suggest": {
+    "league/fractal": "Required for transformers support"
+  },
+  "autoload": {
+    "psr-4": {
+      "Knuckles\\Scribe\\": "src/"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Knuckles\\Scribe\\Tests\\": "tests/"
+    }
+  },
+  "scripts": {
+    "lint": "phpstan analyse -c ./phpstan.neon src",
+    "test": "phpunit --stop-on-failure --exclude-group dingo",
+    "test-ci": "phpunit --exclude-group dingo",
+    "test-parallel": "paratest -p3 --stop-on-failure --parallel-suite --exclude-group dingo tests",
+    "test-parallel-ci": "paratest -p3 --parallel-suite --exclude-group dingo tests"
+  },
+  "extra": {
+    "laravel": {
+      "providers": [
+        "Knuckles\\Scribe\\ScribeServiceProvider"
+      ]
+    }
+  },
+  "config": {
+    "preferred-install": "dist",
+    "sort-packages": true,
+    "process-timeout": 600
+  },
+  "replace": {
+    "mpociot/laravel-apidoc-generator": "*"
+  },
+  "funding": [
+    {
+      "type": "patreon",
+      "url": "https://patreon.com/shalvah"
+    }
+  ]
 }
 }

+ 0 - 17
config/scribe.php

@@ -115,23 +115,6 @@ INTRO
     'postman' => [
     'postman' => [
         'enabled' => true,
         'enabled' => true,
 
 
-        /*
-         * The base URL to be used in the Postman collection.
-         * If this is null, Scribe will use the value of base_url set above.
-         */
-        'base_url' => null,
-
-        /*
-         * The description for the exported Postman collection.
-         */
-        'description' => null,
-
-        /*
-         * The "Auth" section that should appear in the postman collection. See the schema docs for more information:
-         * https://schema.getpostman.com/json/collection/v2.0.0/docs/index.html
-         */
-        'auth' => null,
-
         /*
         /*
          * Manually override some generated content in the spec. Dot notation is supported.
          * Manually override some generated content in the spec. Dot notation is supported.
          */
          */

+ 4 - 6
docs/config.md

@@ -63,15 +63,13 @@ For `static` output, the collection will be created in `public/docs/collection.j
 
 
 - `overrides`: List of fields to apply to the generated collection. Dot notation is supported. For instance, if you'd like to override the version (in the `info` object, you can set `overrides` to `['info.version' => '2.0.0']`.
 - `overrides`: List of fields to apply to the generated collection. Dot notation is supported. For instance, if you'd like to override the version (in the `info` object, you can set `overrides` to `['info.version' => '2.0.0']`.
 
 
-- `description`: The description for the generated Postman collection.
-
-- `base_url`: The base URL to be used in the Postman collection. If this is null, Scribe will use the value of [`base_url`](#base_url) set above.
-
-- `auth`: The "Auth" section that should appear in the postman collection. See the [Postman schema docs](https://schema.getpostman.com/json/collection/v2.0.0/docs/index.html) for more information.
-
 ### `openapi`
 ### `openapi`
 Scribe can also generate an OpenAPI (Swagger) spec for your routes (disabled by default). This section is where you can configure or enable that.
 Scribe can also generate an OpenAPI (Swagger) spec for your routes (disabled by default). This section is where you can configure or enable that.
 
 
+```eval_rst
+.. Important:: The OpenAPI spec is an opinionated spec that doesn't cover all features of APIs in the wild. Scribe does its best, but there's no guarantee that the spec generated will exactly match your API structure.
+```
+
 For `static` output, the spec will be created in `public/docs/openapi.yaml`. For `laravel` output, the spec will be generated to `storage/app/scribe/openapi.yaml`. Setting `laravel.add_routes` to `true` will add a `/docs.openapi` endpoint to fetch it.
 For `static` output, the spec will be created in `public/docs/openapi.yaml`. For `laravel` output, the spec will be generated to `storage/app/scribe/openapi.yaml`. Setting `laravel.add_routes` to `true` will add a `/docs.openapi` endpoint to fetch it.
 
 
 - `enabled`: Whether or not to generate an OpenAPI spec. Default: `false`
 - `enabled`: Whether or not to generate an OpenAPI spec. Default: `false`

+ 0 - 2
docs/generating-documentation.md

@@ -25,8 +25,6 @@ You can configure Postman collection generation in the `postman` section of your
 
 
 - To override fields in the generated collection, set the `postman.overrides` config option to your changes. You can use dot notation to update specific nested fields. For instance, `['info.version' => '2.0.0']` will override the 'version` key in the 'info` object whenever generating.
 - To override fields in the generated collection, set the `postman.overrides` config option to your changes. You can use dot notation to update specific nested fields. For instance, `['info.version' => '2.0.0']` will override the 'version` key in the 'info` object whenever generating.
 
 
-- The base URL used in the Postman collection is the value of `config('app.url')` by default. To change this, set the value of the `postman.base_url` key.
-
 ## OpenAPI (Swagger) spec generation
 ## OpenAPI (Swagger) spec generation
 Scribe can also generate an OpenAPI spec file. This is disabled by default. You can configure this in the `openapi` section of your `scribe.php` file.
 Scribe can also generate an OpenAPI spec file. This is disabled by default. You can configure this in the `openapi` section of your `scribe.php` file.
 
 

+ 26 - 28
src/Extracting/Generator.php

@@ -59,9 +59,9 @@ class Generator
      * @param \Illuminate\Routing\Route $route
      * @param \Illuminate\Routing\Route $route
      * @param array $routeRules Rules to apply when generating documentation for this route
      * @param array $routeRules Rules to apply when generating documentation for this route
      *
      *
+     * @return array
      * @throws \ReflectionException
      * @throws \ReflectionException
      *
      *
-     * @return array
      */
      */
     public function processRoute(Route $route, array $routeRules = [])
     public function processRoute(Route $route, array $routeRules = [])
     {
     {
@@ -240,35 +240,31 @@ class Generator
      *     'description' => 'The age',
      *     'description' => 'The age',
      *     'value' => 12,
      *     'value' => 12,
      *     'required' => false,
      *     'required' => false,
-     *   ]
-     * And transforms them into key-value pairs : ['age' => 12]
+     *   ]]
+     * And transforms them into key-example pairs : ['age' => 12]
      * It also filters out parameters which have null values and have 'required' as false.
      * It also filters out parameters which have null values and have 'required' as false.
      * It converts all file params that have string examples to actual files (instances of UploadedFile).
      * It converts all file params that have string examples to actual files (instances of UploadedFile).
      * Finally, it adds a '.0' key for each array parameter (eg users.* ->users.0),
      * Finally, it adds a '.0' key for each array parameter (eg users.* ->users.0),
      * so that the array ends up containing a 1-item array.
      * so that the array ends up containing a 1-item array.
      *
      *
-     * @param array $params
+     * @param array $parameters
      *
      *
      * @return array
      * @return array
      */
      */
-    public static function cleanParams(array $params): array
+    public static function cleanParams(array $parameters): array
     {
     {
         $cleanParams = [];
         $cleanParams = [];
 
 
-        // Remove params which have no examples and are optional.
-        $params = array_filter($params, function ($details) {
-            return ! (is_null($details['value']) && $details['required'] === false);
-        });
+        foreach ($parameters as $paramName => $details) {
+            // Remove params which have no examples and are optional.
+            if (is_null($details['value']) && $details['required'] === false) {
+                continue;
+            }
 
 
-        foreach ($params as $paramName => $details) {
             if (($details['type'] ?? '') === 'file' && is_string($details['value'])) {
             if (($details['type'] ?? '') === 'file' && is_string($details['value'])) {
-                // Convert any string file examples to instances of UploadedFile
-                $filePath = $details['value'];
-                $fileName = basename($filePath);
-                $details['value'] = new UploadedFile(
-                    $filePath, $fileName, mime_content_type($filePath), 0,false
-                );
+                $details['value'] = self::convertStringValueToUploadedFileInstance($details['value']);
             }
             }
+
             self::generateConcreteKeysForArrayParameters(
             self::generateConcreteKeysForArrayParameters(
                 $paramName,
                 $paramName,
                 $details['value'],
                 $details['value'],
@@ -318,16 +314,10 @@ class Generator
         $token = $faker->shuffle('abcdefghkvaZVDPE1864563');
         $token = $faker->shuffle('abcdefghkvaZVDPE1864563');
         $valueToUse = $this->config->get('auth.use_value');
         $valueToUse = $this->config->get('auth.use_value');
 
 
-        if ($valueToUse) {
-            c::deprecated('the `auth.use_value` config item', 'change this to `response_calls.auth`');
-        } else {
-            $this->config->get('response_calls.auth');
-        }
-
         switch ($strategy) {
         switch ($strategy) {
             case 'query':
             case 'query':
             case 'query_or_body':
             case 'query_or_body':
-                $parsedRoute['auth'] = "cleanQueryParameters.$parameterName.".($valueToUse ?: $token);
+                $parsedRoute['auth'] = "cleanQueryParameters.$parameterName." . ($valueToUse ?: $token);
                 $parsedRoute['queryParameters'][$parameterName] = [
                 $parsedRoute['queryParameters'][$parameterName] = [
                     'name' => $parameterName,
                     'name' => $parameterName,
                     'value' => $token,
                     'value' => $token,
@@ -336,7 +326,7 @@ class Generator
                 ];
                 ];
                 break;
                 break;
             case 'body':
             case 'body':
-                $parsedRoute['auth'] = "cleanBodyParameters.$parameterName.".($valueToUse ?: $token);
+                $parsedRoute['auth'] = "cleanBodyParameters.$parameterName." . ($valueToUse ?: $token);
                 $parsedRoute['bodyParameters'][$parameterName] = [
                 $parsedRoute['bodyParameters'][$parameterName] = [
                     'name' => $parameterName,
                     'name' => $parameterName,
                     'type' => 'string',
                     'type' => 'string',
@@ -346,19 +336,27 @@ class Generator
                 ];
                 ];
                 break;
                 break;
             case 'bearer':
             case 'bearer':
-                $parsedRoute['auth'] = "headers.Authorization.Bearer ".($valueToUse ?: $token);
+                $parsedRoute['auth'] = "headers.Authorization.Bearer " . ($valueToUse ?: $token);
                 $parsedRoute['headers']['Authorization'] = "Bearer $token";
                 $parsedRoute['headers']['Authorization'] = "Bearer $token";
                 break;
                 break;
             case 'basic':
             case 'basic':
-                $parsedRoute['auth'] = "headers.Authorization.Basic ".($valueToUse ?: base64_encode($token));
-                $parsedRoute['headers']['Authorization'] = "Basic ".base64_encode($token);
+                $parsedRoute['auth'] = "headers.Authorization.Basic " . ($valueToUse ?: base64_encode($token));
+                $parsedRoute['headers']['Authorization'] = "Basic " . base64_encode($token);
                 break;
                 break;
             case 'header':
             case 'header':
-                $parsedRoute['auth'] = "headers.$parameterName.".($valueToUse ?: $token);
+                $parsedRoute['auth'] = "headers.$parameterName." . ($valueToUse ?: $token);
                 $parsedRoute['headers'][$parameterName] = $token;
                 $parsedRoute['headers'][$parameterName] = $token;
                 break;
                 break;
         }
         }
 
 
         return $parsedRoute;
         return $parsedRoute;
     }
     }
+
+    protected static function convertStringValueToUploadedFileInstance(string $filePath): UploadedFile
+    {
+        $fileName = basename($filePath);
+        return new UploadedFile(
+            $filePath, $fileName, mime_content_type($filePath), 0, false
+        );
+    }
 }
 }

+ 34 - 13
src/Extracting/ParamHelpers.php

@@ -4,7 +4,7 @@ namespace Knuckles\Scribe\Extracting;
 
 
 use Faker\Factory;
 use Faker\Factory;
 use Illuminate\Http\UploadedFile;
 use Illuminate\Http\UploadedFile;
-use stdClass;
+use Illuminate\Support\Str;
 
 
 trait ParamHelpers
 trait ParamHelpers
 {
 {
@@ -20,6 +20,14 @@ trait ParamHelpers
 
 
     protected function generateDummyValue(string $type)
     protected function generateDummyValue(string $type)
     {
     {
+        $baseType = $type;
+        $isListType = false;
+
+        if (Str::endsWith($type, '[]')) {
+            $baseType = strtolower(substr($type, 0, strlen($type) - 2));
+            $isListType = true;
+        }
+
         $faker = $this->getFaker();
         $faker = $this->getFaker();
 
 
         $fakeFactories = [
         $fakeFactories = [
@@ -29,9 +37,6 @@ trait ParamHelpers
             'number' => function () use ($faker) {
             'number' => function () use ($faker) {
                 return $faker->randomFloat();
                 return $faker->randomFloat();
             },
             },
-            'float' => function () use ($faker) {
-                return $faker->randomFloat();
-            },
             'boolean' => function () use ($faker) {
             'boolean' => function () use ($faker) {
                 return $faker->boolean();
                 return $faker->boolean();
             },
             },
@@ -42,17 +47,18 @@ trait ParamHelpers
                 return [];
                 return [];
             },
             },
             'object' => function () {
             'object' => function () {
-                return new stdClass();
+                return [];
             },
             },
             'file' => function () {
             'file' => function () {
-                $file = UploadedFile::fake()->create('test.jpg')->size(10);
-                return $file;
+                return UploadedFile::fake()->create('test.jpg')->size(10);
             },
             },
         ];
         ];
 
 
-        $fakeFactory = $fakeFactories[$type] ?? $fakeFactories['string'];
+        $fakeFactory = $fakeFactories[$this->normalizeParameterType($baseType)] ?? $fakeFactories['string'];
+        $value = $fakeFactory();
 
 
-        return $fakeFactory();
+        // Return a two-array item for a list
+        return $isListType ? [$value, $this->generateDummyValue($baseType)] : $value;
     }
     }
 
 
     protected function isSupportedTypeInDocBlocks(string $type)
     protected function isSupportedTypeInDocBlocks(string $type)
@@ -60,17 +66,17 @@ trait ParamHelpers
         $types = [
         $types = [
             'integer',
             'integer',
             'int',
             'int',
-            'float',
             'number',
             'number',
+            'float',
             'double',
             'double',
             'boolean',
             'boolean',
             'bool',
             'bool',
             'string',
             'string',
-            'list',
-            'array',
+            'list', // todo remove this
+            'array', // todo remove this
             'object',
             'object',
         ];
         ];
-        return in_array($type, $types);
+        return in_array(preg_replace('/\[]$/', '', $type), $types);
     }
     }
 
 
     /**
     /**
@@ -83,6 +89,17 @@ trait ParamHelpers
      */
      */
     protected function castToType($value, string $type)
     protected function castToType($value, string $type)
     {
     {
+        if ($value === null) {
+            return null;
+        }
+
+        if (Str::endsWith($type, '[]')) {
+            $baseType = strtolower(substr($type, 0, strlen($type) - 2));
+            return is_array($value) ? array_map(function ($v) use ($baseType) {
+                return $this->castToType($v, $baseType);
+            }, $value) : json_decode($value);
+        }
+
         if ($type === 'array' && is_string($value)) {
         if ($type === 'array' && is_string($value)) {
             $value = trim($value);
             $value = trim($value);
             if ($value[0] == '[' && $value[strlen($value) - 1] == ']') {
             if ($value[0] == '[' && $value[strlen($value) - 1] == ']') {
@@ -130,9 +147,13 @@ trait ParamHelpers
 
 
         $typeMap = [
         $typeMap = [
             'int' => 'integer',
             'int' => 'integer',
+            'int[]' => 'integer[]',
             'bool' => 'boolean',
             'bool' => 'boolean',
+            'bool[]' => 'boolean[]',
             'double' => 'number',
             'double' => 'number',
+            'double[]' => 'number[]',
             'float' => 'number',
             'float' => 'number',
+            'float[]' => 'number[]',
         ];
         ];
 
 
         return $typeMap[$type] ?? $type;
         return $typeMap[$type] ?? $type;

+ 1 - 1
tests/Unit/PostmanCollectionWriterTest.php

@@ -111,7 +111,7 @@ class PostmanCollectionWriterTest extends TestCase
     }
     }
 
 
     /** @test */
     /** @test */
-    public function testQueryParametersAreDocumented()
+    public function query_parameters_are_documented()
     {
     {
         $fakeRoute = $this->createMockRouteData('fake/path');
         $fakeRoute = $this->createMockRouteData('fake/path');