diff --git a/.travis.yml b/.travis.yml index 0eccdcb..fe71f85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,12 +10,12 @@ env: matrix: fast_finish: true include: - - php: 7.4 - env: COMPOSER=composer.dingo.json - name: "With Dingo router" - php: 7.4 env: SETUP=lint name: "Lint code" + - php: 7.4 + env: COMPOSER=composer.dingo.json + name: "With Dingo router" - php: 7.4.7 - php: 7.4 env: SETUP=lowest @@ -31,7 +31,6 @@ before_install: install: - if [[ $SETUP = 'stable' ]]; then travis_retry composer update --prefer-dist --prefer-stable; fi - if [[ $SETUP = 'lowest' ]]; then travis_retry composer require laravel/framework:^$LOWEST; composer require laravel/lumen-framework:^$LOWEST; fi - - if [[ $SETUP = 'lint' ]]; then travis_retry composer update --prefer-dist --prefer-stable; composer lint; fi script: - if [[ $SETUP = 'lint' ]]; then exit 0; fi; composer test-parallel-ci; diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d26ef2..9594806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removals +## 3.5.1 (Tuesday, 6 July 2021) +### Fixed +- Try It Out: Turn off autocomplete; make sure it works for array body; improve UI spacing ([579f672b57ad0417a5563aee1621b84c3b4ff1f2](https://github.com/knuckleswtf/scribe/commit/579f672b57ad0417a5563aee1621b84c3b4ff1f2), [2af8d8eacd661e0601b2d6f4dbc1766bf75e702a](https://github.com/knuckleswtf/scribe/commit/2af8d8eacd661e0601b2d6f4dbc1766bf75e702a)) + ## 3.5.0 (Monday, 5 July 2021) ### Modified - Get URL parameter name from field bindings (https://github.com/knuckleswtf/scribe/commit/ce6be7ca68ed0e682258eca5bbeb2f7d84774714) diff --git a/camel/Output/OutputEndpointData.php b/camel/Output/OutputEndpointData.php index e0f85ac..13de1a4 100644 --- a/camel/Output/OutputEndpointData.php +++ b/camel/Output/OutputEndpointData.php @@ -150,6 +150,12 @@ class OutputEndpointData extends BaseDTO return count($this->fileParameters) > 0; } + public function isArrayBody(): bool + { + return count($this->nestedBodyParameters) === 1 + && array_keys($this->nestedBodyParameters)[0] === "[]"; + } + public function isGet(): bool { return in_array('GET', $this->httpMethods); diff --git a/resources/css/theme-default.style.css b/resources/css/theme-default.style.css index 5e21dd0..75970f1 100644 --- a/resources/css/theme-default.style.css +++ b/resources/css/theme-default.style.css @@ -858,7 +858,7 @@ html { .content pre { background-color: #292929; color: #fff; - padding: 2em 28px; + padding: 1.5em 28px; margin: 0; width: 50%; float: right; diff --git a/resources/js/tryitout.js b/resources/js/tryitout.js index 824fc9c..e10fd03 100644 --- a/resources/js/tryitout.js +++ b/resources/js/tryitout.js @@ -142,12 +142,15 @@ async function executeTryOut(endpointId, form) { let body; let setter; - if (form.dataset.hasfiles === "0") { - body = {}; - setter = (name, value) => _.set(body, name, value); - } else { + if (form.dataset.hasfiles === "1") { body = new FormData(); setter = (name, value) => body.append(name, value); + } else if (form.dataset.isarraybody === "1") { + body = []; + setter = (name, value) => _.set(body, name, value); + } else { + body = {}; + setter = (name, value) => _.set(body, name, value); } const bodyParameters = form.querySelectorAll('input[data-component=body]'); bodyParameters.forEach(el => { diff --git a/resources/views/components/field-details.blade.php b/resources/views/components/field-details.blade.php index 029565f..3bd6d8f 100644 --- a/resources/views/components/field-details.blade.php +++ b/resources/views/components/field-details.blade.php @@ -10,6 +10,8 @@ $fullName .= '.0'; $baseType = substr($baseType, 0, -2); } + // When the body is an array, the item names will be ".0.thing" + $fullName = ltrim($fullName, '.'); switch($baseType) { case 'number': case 'integer': diff --git a/resources/views/themes/default/endpoint.blade.php b/resources/views/themes/default/endpoint.blade.php index e08b400..a228b12 100644 --- a/resources/views/themes/default/endpoint.blade.php +++ b/resources/views/themes/default/endpoint.blade.php @@ -32,24 +32,20 @@ Show headers -
-            @foreach($response->headers as $header => $value)
+            
@foreach($response->headers as $header => $value)
 {{ $header }}: {{ is_array($value) ? implode('; ', $value) : $value }}
-@endforeach 
-            
+@endforeach
@endif
-                
 @if(is_string($response->content) && Str::startsWith($response->content, "<>"))
-[Binary data] - {{ htmlentities(str_replace("<>", "", $response->content)) }}
+[Binary data] - {{ htmlentities(str_replace("<>", "", $response->content)) }}
 @elseif($response->status == 204)
-[Empty response]
+[Empty response]
 @else
 @php($parsed = json_decode($response->content))
 {{-- If response is a JSON string, prettify it. Otherwise, just print it --}}
-{!! htmlentities($parsed != null ? json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : $response->content) !!}
-@endif 
-        
+{!! htmlentities($parsed != null ? json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : $response->content) !!} +@endif @endforeach @endif @@ -67,7 +63,9 @@ data-path="{{ $endpoint->uri }}" data-authed="{{ $endpoint->metadata->authenticated ? 1 : 0 }}" data-hasfiles="{{ $endpoint->hasFiles() ? 1 : 0 }}" + data-isarraybody="{{ $endpoint->isArrayBody() ? 1 : 0 }}" data-headers='@json($endpoint->headers)' + autocomplete="off" onsubmit="event.preventDefault(); executeTryOut('{{ $endpoint->endpointId() }}', this);">

Request    diff --git a/src/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php index 284c2d5..f0c3980 100644 --- a/src/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -84,7 +84,7 @@ class GenerateDocumentation extends Command throw new \Exception("Can't use --force and --no-extraction together."); } - // Reset this map useful for tests) + // Reset this map (useful for tests) Camel::$groupFileNames = []; } diff --git a/src/Extracting/Strategies/Headers/GetFromHeaderTag.php b/src/Extracting/Strategies/Headers/GetFromHeaderTag.php index ec948b4..342148b 100644 --- a/src/Extracting/Strategies/Headers/GetFromHeaderTag.php +++ b/src/Extracting/Strategies/Headers/GetFromHeaderTag.php @@ -56,7 +56,7 @@ class GetFromHeaderTag extends Strategy return $this->getHeadersFromDocBlock($methodDocBlock->getTags()); } - public function getHeadersFromDocBlock($tags) + public function getHeadersFromDocBlock($tags): array { $headers = collect($tags) ->filter(function ($tag) { diff --git a/src/Tools/Globals.php b/src/Tools/Globals.php index 579294c..c2c46bc 100644 --- a/src/Tools/Globals.php +++ b/src/Tools/Globals.php @@ -4,7 +4,7 @@ namespace Knuckles\Scribe\Tools; class Globals { - public const SCRIBE_VERSION = '3.5.0'; + public const SCRIBE_VERSION = '3.5.1'; public static bool $shouldBeVerbose = false; } diff --git a/tests/GenerateDocumentationTest.php b/tests/GenerateDocumentationTest.php index 7a8e829..4779252 100644 --- a/tests/GenerateDocumentationTest.php +++ b/tests/GenerateDocumentationTest.php @@ -22,6 +22,9 @@ class GenerateDocumentationTest extends BaseLaravelTest parent::setUp(); config(['scribe.database_connections_to_transact' => []]); + // Skip these ones for faster tests + config(['scribe.openapi.enabled' => false]); + config(['scribe.postman.enabled' => false]); $factory = app(\Illuminate\Database\Eloquent\Factory::class); $factory->define(TestUser::class, function () { @@ -206,7 +209,6 @@ class GenerateDocumentationTest extends BaseLaravelTest ], ]); config(['scribe.postman.enabled' => true]); - config(['scribe.openapi.enabled' => false]); $this->artisan('scribe:generate'); @@ -230,7 +232,6 @@ class GenerateDocumentationTest extends BaseLaravelTest // We want to have the same values for params each time config(['scribe.faker_seed' => 1234]); - config(['scribe.postman.enabled' => false]); config(['scribe.openapi.enabled' => true]); config(['scribe.openapi.overrides' => [ 'info.version' => '3.9.9', @@ -372,8 +373,6 @@ class GenerateDocumentationTest extends BaseLaravelTest ]); }); config(['scribe.routes.0.match.prefixes' => ['*']]); - config(['scribe.openapi.enabled' => false]); - config(['scribe.postman.enabled' => false]); $this->artisan('scribe:generate'); @@ -387,8 +386,6 @@ class GenerateDocumentationTest extends BaseLaravelTest public function will_not_extract_if_noExtraction_flag_is_set() { config(['scribe.routes.0.exclude' => ['*']]); - config(['scribe.openapi.enabled' => false]); - config(['scribe.postman.enabled' => false]); Utils::copyDirectory(__DIR__.'/Fixtures/.scribe', '.scribe'); $output = $this->artisan('scribe:generate', ['--no-extraction' => true]); @@ -412,8 +409,6 @@ class GenerateDocumentationTest extends BaseLaravelTest RouteFacade::get('/api/action1', [TestGroupController::class, 'action1']); RouteFacade::get('/api/action2', [TestGroupController::class, 'action2']); config(['scribe.routes.0.match.prefixes' => ['api/*']]); - config(['scribe.openapi.enabled' => false]); - config(['scribe.postman.enabled' => false]); if (!is_dir('.scribe/endpoints')) mkdir('.scribe/endpoints', 0777, true); copy(__DIR__ . '/Fixtures/custom.0.yaml', '.scribe/endpoints/custom.0.yaml'); @@ -443,8 +438,6 @@ class GenerateDocumentationTest extends BaseLaravelTest RouteFacade::get('/api/action1b', [TestGroupController::class, 'action1b']); RouteFacade::get('/api/action2', [TestGroupController::class, 'action2']); config(['scribe.routes.0.match.prefixes' => ['api/*']]); - config(['scribe.openapi.enabled' => false]); - config(['scribe.postman.enabled' => false]); $this->artisan('scribe:generate'); @@ -485,4 +478,35 @@ class GenerateDocumentationTest extends BaseLaravelTest $this->assertEquals("Another endpoint.", $expectedEndpoints->getNode(1)->textContent); $this->assertEquals("Some endpoint.", $expectedEndpoints->getNode(2)->textContent); } + + /** @test */ + public function will_auto_set_content_type_to_multipart_if_file_params_are_present() + { + /** + * @bodyParam param string required + */ + RouteFacade::post('no-file', fn() => null); + /** + * @bodyParam a_file file required + */ + RouteFacade::post('top-level-file', fn() => null); + /** + * @bodyParam data object + * @bodyParam data.thing string + * @bodyParam data.a_file file + */ + RouteFacade::post('nested-file', fn() => null); + config(['scribe.routes.0.match.prefixes' => ['*']]); + + $this->artisan('scribe:generate'); + + $group = Yaml::parseFile('.scribe/endpoints/0.yaml'); + $this->assertEquals('no-file', $group['endpoints'][0]['uri']); + $this->assertEquals('application/json', $group['endpoints'][0]['headers']['Content-Type']); + $this->assertEquals('top-level-file', $group['endpoints'][1]['uri']); + $this->assertEquals('multipart/form-data', $group['endpoints'][1]['headers']['Content-Type']); + $this->assertEquals('nested-file', $group['endpoints'][2]['uri']); + $this->assertEquals('multipart/form-data', $group['endpoints'][2]['headers']['Content-Type']); + + } }