From e8ac03d04e4f64a2aaae7129c43f184ce71b9440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=B1=95?= <32818030+lz-freedom@users.noreply.github.com> Date: Sun, 10 Mar 2024 05:44:43 +0800 Subject: [PATCH 01/32] fix Fixed page sidebar style error when 'type' is' External_Static','theme' is' Elements' (#820) Co-authored-by: ifreedom --- resources/views/external/elements.blade.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/views/external/elements.blade.php b/resources/views/external/elements.blade.php index db48c4ff..fec80eaa 100644 --- a/resources/views/external/elements.blade.php +++ b/resources/views/external/elements.blade.php @@ -8,6 +8,11 @@ + From 32c5f08bd2ae742a9d8b153d5b63c4151399607f Mon Sep 17 00:00:00 2001 From: Peter Ragheb Date: Fri, 15 Mar 2024 21:13:43 +0200 Subject: [PATCH 02/32] Refactor classes instantiation for easier custom bindings (#822) * Refactor classes instantiation for easier custom binding * Make class properties protected --------- Co-authored-by: Peter Ragheb --- src/Commands/GenerateDocumentation.php | 2 +- src/ScribeServiceProvider.php | 2 +- src/Tools/Utils.php | 2 +- src/Writing/Writer.php | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php index bddc5e33..127edd62 100644 --- a/src/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -63,7 +63,7 @@ public function handle(RouteMatcherInterface $routeMatcher, GroupedEndpointsFact $this->writeExampleCustomEndpoint(); } - $writer = new Writer($this->docConfig, $this->paths); + $writer = app(Writer::class, ['config' => $this->docConfig, 'paths' => $this->paths]); $writer->writeDocs($groupedEndpoints); $this->upgradeConfigFileIfNeeded(); diff --git a/src/ScribeServiceProvider.php b/src/ScribeServiceProvider.php index 3b1d404c..0c5fce5f 100644 --- a/src/ScribeServiceProvider.php +++ b/src/ScribeServiceProvider.php @@ -120,7 +120,7 @@ protected function registerCommands(): void public function loadCustomTranslationLayer(): void { $this->app->extend('translation.loader', function ($defaultFileLoader) { - return new CustomTranslationsLoader($defaultFileLoader); + return app(CustomTranslationsLoader::class, ['loader' => $defaultFileLoader]); }); $this->app->forgetInstance('translator'); self::$customTranslationLayerLoaded = true; diff --git a/src/Tools/Utils.php b/src/Tools/Utils.php index d16eaf8a..e7db3e15 100644 --- a/src/Tools/Utils.php +++ b/src/Tools/Utils.php @@ -364,7 +364,7 @@ public static function trans(string $key, array $replace = []) { // We only load our custom translation layer if we really need it if (!ScribeServiceProvider::$customTranslationLayerLoaded) { - (new ScribeServiceProvider(app()))->loadCustomTranslationLayer(); + app(ScribeServiceProvider::class, ['app' => app()])->loadCustomTranslationLayer(); } $translation = trans($key, $replace); diff --git a/src/Writing/Writer.php b/src/Writing/Writer.php index f0ce56d7..d9fa1602 100644 --- a/src/Writing/Writer.php +++ b/src/Writing/Writer.php @@ -13,12 +13,12 @@ class Writer { - private bool $isStatic; - private bool $isExternal; + protected bool $isStatic; + protected bool $isExternal; - private ?string $staticTypeOutputPath; + protected ?string $staticTypeOutputPath; - private ?string $laravelTypeOutputPath; + protected ?string $laravelTypeOutputPath; protected array $generatedFiles = [ 'postman' => null, 'openapi' => null, @@ -31,7 +31,7 @@ class Writer ], ]; - private string $laravelAssetsPath; + protected string $laravelAssetsPath; public function __construct(protected DocumentationConfig $config, public PathConfig $paths) { From 30624433a37a8d2ee277c86297bab62d4a2b8338 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Fri, 15 Mar 2024 20:24:53 +0100 Subject: [PATCH 03/32] 4.34.0 --- CHANGELOG.md | 7 +++++++ src/Scribe.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6cda304..c5363a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed +# 4.34.0 (15 March 2024) +## Added +- Laravel 11 compatibility [#812](https://github.com/knuckleswtf/scribe/pull/812) + +## Modified +- Instantiate some classes via service container for easier overriding. [#822](https://github.com/knuckleswtf/scribe/pull/822) + # 4.33.0 (29 February 2024) ## Fixed - List enums for array items in OpenAPI spec [#818](https://github.com/knuckleswtf/scribe/pull/818) diff --git a/src/Scribe.php b/src/Scribe.php index d83371d8..30c3231f 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -9,7 +9,7 @@ class Scribe { - public const VERSION = '4.33.0'; + public const VERSION = '4.34.0'; /** * Specify a callback that will be executed just before a response call is made From 88b6c67c49ad9fd8e0deb8809f69e3c1d8079c0d Mon Sep 17 00:00:00 2001 From: Yormy <37929978+yormy@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:00:34 +0100 Subject: [PATCH 04/32] Allow examples to be shown in response fields (#825) * Allow examples to be shown in response fields * Update type --------- Co-authored-by: Rob Co-authored-by: Shalvah --- camel/Extraction/ResponseField.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/camel/Extraction/ResponseField.php b/camel/Extraction/ResponseField.php index ffc92baf..8a51da8b 100644 --- a/camel/Extraction/ResponseField.php +++ b/camel/Extraction/ResponseField.php @@ -19,5 +19,8 @@ class ResponseField extends BaseDTO /** @var string */ public $type; + /** @var mixed */ + public $example; + public array $enumValues = []; } From 84e49b545768b2c570533875faf892a4bec4e9e9 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 26 Mar 2024 23:37:39 +0300 Subject: [PATCH 05/32] added cast number value to float if value is string (#830) Co-authored-by: Pavel Televich --- resources/js/tryitout.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/js/tryitout.js b/resources/js/tryitout.js index a3feb1bd..01c584f2 100644 --- a/resources/js/tryitout.js +++ b/resources/js/tryitout.js @@ -194,6 +194,11 @@ async function executeTryOut(endpointId, form) { const bodyParameters = form.querySelectorAll('input[data-component=body]'); bodyParameters.forEach(el => { let value = el.value; + + if (el.type === 'number' && typeof value === 'string') { + value = parseFloat(value); + } + if (el.type === 'file' && el.files[0]) { setter(el.name, el.files[0]); return; From 7955c8056deec4689449cf0b415106f4e9675be7 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 27 Mar 2024 04:47:15 +0800 Subject: [PATCH 06/32] make directory recursive (#829) * make directory recursive Signed-off-by: michael * add utils test case Signed-off-by: michael --------- Signed-off-by: michael --- src/Tools/Utils.php | 6 ++++++ src/Writing/Writer.php | 2 +- tests/Unit/UtilsTest.php | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/UtilsTest.php diff --git a/src/Tools/Utils.php b/src/Tools/Utils.php index e7db3e15..114de014 100644 --- a/src/Tools/Utils.php +++ b/src/Tools/Utils.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Routing\Route; +use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use Knuckles\Scribe\Exceptions\CouldntFindFactory; use Knuckles\Scribe\Exceptions\CouldntGetRouteDetails; @@ -191,6 +192,11 @@ public static function copyDirectory(string $src, string $dest): void } } + public static function makeDirectoryRecursive(string $dir): void + { + File::isDirectory($dir) || File::makeDirectory($dir, 0777, true, true); + } + public static function deleteFilesMatching(string $dir, callable $condition): void { if (class_exists(LocalFilesystemAdapter::class)) { diff --git a/src/Writing/Writer.php b/src/Writing/Writer.php index d9fa1602..69db61cc 100644 --- a/src/Writing/Writer.php +++ b/src/Writing/Writer.php @@ -3,7 +3,6 @@ namespace Knuckles\Scribe\Writing; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; use Knuckles\Scribe\Tools\ConsoleOutputUtils as c; use Knuckles\Scribe\Tools\DocumentationConfig; use Knuckles\Scribe\Tools\Globals; @@ -96,6 +95,7 @@ protected function writeOpenAPISpec(array $parsedRoutes): void $spec = $this->generateOpenAPISpec($parsedRoutes); if ($this->isStatic) { + Utils::makeDirectoryRecursive($this->staticTypeOutputPath); $specPath = "{$this->staticTypeOutputPath}/openapi.yaml"; file_put_contents($specPath, $spec); } else { diff --git a/tests/Unit/UtilsTest.php b/tests/Unit/UtilsTest.php new file mode 100644 index 00000000..a5eab141 --- /dev/null +++ b/tests/Unit/UtilsTest.php @@ -0,0 +1,23 @@ +assertDirectoryExists($dir); // Directory exists + + if (rmdir($dir)) { // Remove the directory + dump("Directory deleted successfully: $dir"); + } else { // If deletion fails, you can handle the error as needed + dump("Failed to delete directory: $dir"); + } + } +} From 90caff1f83877fa82d9032296ec26ae0e12ae9c4 Mon Sep 17 00:00:00 2001 From: bjhijmans <59833909+bjhijmans@users.noreply.github.com> Date: Tue, 26 Mar 2024 21:57:55 +0100 Subject: [PATCH 07/32] Fix translating rules with translation engines that don't return arrays. (#826) * translating validation rules no longer relies on the translator returning arrays for rules that apply to multiple types * Added test for translator without array support. --------- Co-authored-by: Bart Hijmans --- src/Extracting/ParsesValidationRules.php | 16 ++++++++-- tests/Unit/ValidationRuleParsingTest.php | 39 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Extracting/ParsesValidationRules.php b/src/Extracting/ParsesValidationRules.php index bec00aa1..2204481d 100644 --- a/src/Extracting/ParsesValidationRules.php +++ b/src/Extracting/ParsesValidationRules.php @@ -778,11 +778,21 @@ protected function getDescription(string $rule, array $arguments = [], $baseType return "Must match the regex {$arguments[':regex']}."; } - $description = trans("validation.{$rule}"); - // For rules that can apply to multiple types (eg 'max' rule), Laravel returns an array of possible messages + $translationString = "validation.{$rule}"; + $description = trans($translationString); + + // For rules that can apply to multiple types (eg 'max' rule), There is an array of possible messages // 'numeric' => 'The :attribute must not be greater than :max' // 'file' => 'The :attribute must have a size less than :max kilobytes' - if (is_array($description)) { + // Depending on the translation engine, trans may return the array, or it will fail to translate the string + // and will need to be called with the baseType appended. + if ($description === $translationString) { + $translationString = "{$translationString}.{$baseType}"; + $translated = trans($translationString); + if ($translated !== $translationString) { + $description = $translated; + } + } elseif (is_array($description)) { $description = $description[$baseType]; } diff --git a/tests/Unit/ValidationRuleParsingTest.php b/tests/Unit/ValidationRuleParsingTest.php index 95765404..03aa4e9a 100644 --- a/tests/Unit/ValidationRuleParsingTest.php +++ b/tests/Unit/ValidationRuleParsingTest.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Validator; +use Illuminate\Translation\Translator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Knuckles\Scribe\Extracting\ParsesValidationRules; @@ -597,6 +598,32 @@ public function can_parse_enum_rules() array_map(fn ($case) => $case->value, Fixtures\TestStringBackedEnum::cases()) )); } + + /** @test */ + public function can_translate_validation_rules_with_types_with_translator_without_array_support() + { + // Single line DocComment + $ruleset = [ + 'nested' => [ + 'string', 'max:20', + ], + ]; + + $results = $this->strategy->parse($ruleset); + + $this->assertEquals('Must not be greater than 20 characters.', $results['nested']['description']); + + $this->app->extend('translator', function ($command, $app) { + $loader = $app['translation.loader']; + $locale = $app['config']['app.locale']; + return new DummyTranslator($loader, $locale); + }); + + $results = $this->strategy->parse($ruleset); + + $this->assertEquals('successfully translated by concatenated string.', $results['nested']['description']); + + } } class DummyValidationRule implements \Illuminate\Contracts\Validation\Rule @@ -673,3 +700,15 @@ public static function docs() } } } + +class DummyTranslator extends Translator +{ + public function get($key, array $replace = [], $locale = null, $fallback = true) + { + if ($key === 'validation.max.string') { + return 'successfully translated by concatenated string'; + } + + return $key; + } +} From dd6696676981139de814bb0eeda7d5e5891a5244 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Tue, 26 Mar 2024 21:58:51 +0100 Subject: [PATCH 08/32] 4.35.0 --- CHANGELOG.md | 9 +++++++++ src/Scribe.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5363a53..a5a13439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed +# 4.35.0 (26 March 2024) +## Modified +- Allow examples to be shown in response fields [#825](https://github.com/knuckleswtf/scribe/pull/825) + +## Fixed +- Try It Out: send numbers in JSON as float, not strings [#830](https://github.com/knuckleswtf/scribe/pull/830) +- Fix "No such file or directory" error [#829](https://github.com/knuckleswtf/scribe/pull/829) +- Fix translating rules with translation engines that don't return arrays [#826](https://github.com/knuckleswtf/scribe/pull/826) + # 4.34.0 (15 March 2024) ## Added - Laravel 11 compatibility [#812](https://github.com/knuckleswtf/scribe/pull/812) diff --git a/src/Scribe.php b/src/Scribe.php index 30c3231f..ce514a41 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -9,7 +9,7 @@ class Scribe { - public const VERSION = '4.34.0'; + public const VERSION = '4.35.0'; /** * Specify a callback that will be executed just before a response call is made From 09b49b5829647597825b2cc7162382e926d53f90 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Tue, 26 Mar 2024 22:03:35 +0100 Subject: [PATCH 09/32] Unescape triItOutBaseURL (fixes #796) --- resources/views/themes/default/index.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/themes/default/index.blade.php b/resources/views/themes/default/index.blade.php index d0f46344..672a5074 100644 --- a/resources/views/themes/default/index.blade.php +++ b/resources/views/themes/default/index.blade.php @@ -33,9 +33,9 @@ @if($tryItOut['enabled'] ?? true) @endif From 846a0bac50e772f41caa162badafa20a33fd4828 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 1 May 2024 17:52:12 +0800 Subject: [PATCH 10/32] Hotfix/fix type comparison (#841) * make directory recursive Signed-off-by: michael * add utils test case Signed-off-by: michael * fix type comparison Signed-off-by: michael --------- Signed-off-by: michael --- src/Extracting/Strategies/GetParamsFromAttributeStrategy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php b/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php index 42ff07cc..d6e1264d 100644 --- a/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php +++ b/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php @@ -34,7 +34,7 @@ protected function normalizeParameterData(array $data): array 'name' => $data['name'], 'enumValues' => $data['enumValues'], ]); - } else if ($data['example'] == 'No-example' || $data['example'] == 'No-example.') { + } else if ($data['example'] === 'No-example' || $data['example'] === 'No-example.') { $data['example'] = null; } From 0fd9abb979b98e39856f81d0e46789bb7c916c67 Mon Sep 17 00:00:00 2001 From: Wanderson Costa <54078100+wcostaprijo@users.noreply.github.com> Date: Wed, 1 May 2024 06:54:29 -0300 Subject: [PATCH 11/32] fix: Array with Form request doesn't generate both example and fields at the same time (#840) --- src/Extracting/ParsesValidationRules.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Extracting/ParsesValidationRules.php b/src/Extracting/ParsesValidationRules.php index 2204481d..b78b113e 100644 --- a/src/Extracting/ParsesValidationRules.php +++ b/src/Extracting/ParsesValidationRules.php @@ -636,8 +636,11 @@ public function convertGenericArrayType(array $parameters): array // 2. If `users.` exists, `users` is an `object` // 3. Otherwise, default to `object` // Important: We're iterating in reverse, to ensure we set child items before parent items - // (assuming the user specified parents first, which is the more common thing) - if ($childKey = Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*"))) { + // (assuming the user specified parents first, which is the more common thing)y + if(Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*."))) { + $details['type'] = 'object[]'; + unset($details['setter']); + } else if ($childKey = Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*"))) { $childType = ($converted[$childKey] ?? $parameters[$childKey])['type']; $details['type'] = "{$childType}[]"; } else { // `array` types default to `object` if no subtype is specified From 51eaeeced1e1a4a71f4504e0066e60a115721c56 Mon Sep 17 00:00:00 2001 From: max13fr Date: Wed, 22 May 2024 20:59:07 +0200 Subject: [PATCH 12/32] External Elements : fix title & missing logo (#844) Two fixes when choose theme="elements" : - use the title in config - don't set logo param if not set --- resources/views/external/elements.blade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/views/external/elements.blade.php b/resources/views/external/elements.blade.php index fec80eaa..848dd180 100644 --- a/resources/views/external/elements.blade.php +++ b/resources/views/external/elements.blade.php @@ -4,7 +4,7 @@ - Elements in HTML + {!! $metadata['title'] !!}L @@ -25,7 +25,9 @@ router="hash" layout="sidebar" hideTryIt="{!! ($tryItOut['enabled'] ?? true) ? '' : 'true'!!}" +@if(!empty($metadata['logo'])) logo="{!! $metadata['logo'] !!}" +@endif /> From eb179c6f3f5486a70fece807f5a9918d5f62d778 Mon Sep 17 00:00:00 2001 From: max13fr Date: Wed, 22 May 2024 21:00:09 +0200 Subject: [PATCH 13/32] feat: add afterResponseCall hook (#847) * feat: add after response hook * fix(runPostRequestHook): avoid forcing using JsonResponse * fix(runPostRequestHook): cleaning --- .../Strategies/Responses/ResponseCalls.php | 14 ++++++++++++-- src/Scribe.php | 11 +++++++++++ src/Tools/Globals.php | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Extracting/Strategies/Responses/ResponseCalls.php b/src/Extracting/Strategies/Responses/ResponseCalls.php index 3b9ff2c5..fdd640f7 100644 --- a/src/Extracting/Strategies/Responses/ResponseCalls.php +++ b/src/Extracting/Strategies/Responses/ResponseCalls.php @@ -2,8 +2,6 @@ namespace Knuckles\Scribe\Extracting\Strategies\Responses; -use Illuminate\Support\Facades\Config; -use Knuckles\Camel\Extraction\ExtractedEndpointData; use Dingo\Api\Dispatcher; use Dingo\Api\Routing\Route as DingoRoute; use Exception; @@ -12,7 +10,9 @@ use Illuminate\Http\Response; use Illuminate\Http\UploadedFile; use Illuminate\Routing\Route; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Str; +use Knuckles\Camel\Extraction\ExtractedEndpointData; use Knuckles\Scribe\Extracting\DatabaseTransactionHelpers; use Knuckles\Scribe\Extracting\ParamHelpers; use Knuckles\Scribe\Extracting\Strategies\Strategy; @@ -89,6 +89,9 @@ public function makeResponseCall(ExtractedEndpointData $endpointData, array $set try { $response = $this->makeApiCall($request, $endpointData->route); + + $this->runPostRequestHook($request, $endpointData, $response); + $response = [ [ 'status' => $response->getStatusCode(), @@ -170,6 +173,13 @@ protected function runPreRequestHook(Request $request, ExtractedEndpointData $en } } + protected function runPostRequestHook(Request $request, ExtractedEndpointData $endpointData, mixed $response): void + { + if (is_callable(Globals::$__afterResponseCall)) { + call_user_func_array(Globals::$__afterResponseCall, [$request, $endpointData, $response]); + } + } + private function setLaravelConfigs(array $config) { if (empty($config)) { diff --git a/src/Scribe.php b/src/Scribe.php index ce514a41..8ef0f861 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -22,6 +22,17 @@ public static function beforeResponseCall(callable $callable) Globals::$__beforeResponseCall = $callable; } + /** + * Specify a callback that will be executed just after a response call is done + * (allowing to modify the response). + * + * @param callable(Request, ExtractedEndpointData, mixed): mixed $callable + */ + public static function afterResponseCall(callable $callable) + { + Globals::$__afterResponseCall = $callable; + } + /** * Specify a callback that will be executed just before the generate command is executed * diff --git a/src/Tools/Globals.php b/src/Tools/Globals.php index 28a661d7..8f7657df 100644 --- a/src/Tools/Globals.php +++ b/src/Tools/Globals.php @@ -12,6 +12,8 @@ class Globals public static $__beforeResponseCall; + public static $__afterResponseCall; + public static $__bootstrap; public static $__afterGenerating; From f56a480140d25ada8a441f69db9a6a14b5f0dcd1 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Mon, 27 May 2024 23:19:38 +0200 Subject: [PATCH 14/32] Ignore `external.html_attributes` for upgrades --- src/Commands/GenerateDocumentation.php | 2 +- src/Commands/Upgrade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php index 127edd62..ab48547b 100644 --- a/src/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -178,7 +178,7 @@ protected function upgradeConfigFileIfNeeded(): void ) ->dontTouch( 'routes', 'example_languages', 'database_connections_to_transact', 'strategies', 'laravel.middleware', - 'postman.overrides', 'openapi.overrides', 'groups', 'examples.models_source' + 'postman.overrides', 'openapi.overrides', 'groups', 'examples.models_source', 'external.html_attributes' ); $changes = $upgrader->dryRun(); if (!empty($changes)) { diff --git a/src/Commands/Upgrade.php b/src/Commands/Upgrade.php index dc53ffe5..b25d8505 100644 --- a/src/Commands/Upgrade.php +++ b/src/Commands/Upgrade.php @@ -43,7 +43,7 @@ public function handle(): void $upgrader = Upgrader::ofConfigFile("config/$this->configName.php", __DIR__ . '/../../config/scribe.php') ->dontTouch('routes', 'laravel.middleware', 'postman.overrides', 'openapi.overrides', - 'example_languages', 'database_connections_to_transact', 'strategies', 'examples.models_source') + 'example_languages', 'database_connections_to_transact', 'strategies', 'examples.models_source', 'external.html_attributes') ->move('default_group', 'groups.default') ->move('faker_seed', 'examples.faker_seed'); From 1304e1503600b1fe8f6f546445de0ebd927386fa Mon Sep 17 00:00:00 2001 From: Shalvah Date: Mon, 27 May 2024 23:51:57 +0200 Subject: [PATCH 15/32] 4.36.0 --- CHANGELOG.md | 10 ++++++++++ .../Strategies/GetFromInlineValidatorBase.php | 1 - src/Scribe.php | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5a13439..4df9aa49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed +# 4.36.0 (27 May 2024) +## Added +- Add `afterResponseCall` hook [#847](https://github.com/knuckleswtf/scribe/pull/847) + +## Fixed +- Unescape tryItOutBaseURL [09b49b582](https://github.com/knuckleswtf/scribe/commit/09b49b5829647597825b2cc7162382e926d53f90) +- Ignore `external.html_attributes` for upgrades [f56a48014](https://github.com/knuckleswtf/scribe/commit/f56a480140d25ada8a441f69db9a6a14b5f0dcd1) +- Fix missing title and logo in `elements` theme [#844](https://github.com/knuckleswtf/scribe/pull/844) + + # 4.35.0 (26 March 2024) ## Modified - Allow examples to be shown in response fields [#825](https://github.com/knuckleswtf/scribe/pull/825) diff --git a/src/Extracting/Strategies/GetFromInlineValidatorBase.php b/src/Extracting/Strategies/GetFromInlineValidatorBase.php index 5757b55a..b6ff7ff8 100644 --- a/src/Extracting/Strategies/GetFromInlineValidatorBase.php +++ b/src/Extracting/Strategies/GetFromInlineValidatorBase.php @@ -81,7 +81,6 @@ public function lookForInlineValidationRules(ClassMethod $methodAst): array if ($arrayItem->value instanceof Node\Scalar\String_) { $rulesList[] = $arrayItem->value->value; } - // Try to extract Enum rule else if ( function_exists('enum_exists') && diff --git a/src/Scribe.php b/src/Scribe.php index 8ef0f861..d248cf69 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -9,7 +9,7 @@ class Scribe { - public const VERSION = '4.35.0'; + public const VERSION = '4.36.0'; /** * Specify a callback that will be executed just before a response call is made From b856570df1a522e5321486e438df217a5fd60955 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Mon, 17 Jun 2024 21:32:13 +0200 Subject: [PATCH 16/32] Better tests + fixes for route rules vs strategy config --- config/scribe.php | 8 ++- src/Extracting/DatabaseTransactionHelpers.php | 9 ++- src/Extracting/Extractor.php | 25 +++++---- tests/Unit/ExtractorTest.php | 56 +++++++++++++++++-- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/config/scribe.php b/config/scribe.php index 17e6d70a..66dbb831 100644 --- a/config/scribe.php +++ b/config/scribe.php @@ -241,7 +241,13 @@ Strategies\Responses\UseResponseFileTag::class, [ Strategies\Responses\ResponseCalls::class, - ['only' => ['GET *']] + [ + 'only' => ['GET *'], + // Disable debug mode when generating response calls to avoid error stack traces in responses + 'config' => [ + 'app.debug' => false, + ], + ] ] ], 'responseFields' => [ diff --git a/src/Extracting/DatabaseTransactionHelpers.php b/src/Extracting/DatabaseTransactionHelpers.php index 044c133c..c9b860c0 100644 --- a/src/Extracting/DatabaseTransactionHelpers.php +++ b/src/Extracting/DatabaseTransactionHelpers.php @@ -15,9 +15,9 @@ private function connectionsToTransact() private function startDbTransaction() { - $database = app('db'); - foreach ($this->connectionsToTransact() as $connection) { + $database ??= app('db'); + $driver = $database->connection($connection); if (self::driverSupportsTransactions($driver)) { @@ -30,7 +30,6 @@ private function startDbTransaction() " If you aren't using this database, remove it from the `database_connections_to_transact` config array." ); } - continue; } else { $driverClassName = get_class($driver); throw DatabaseTransactionsNotSupported::create($connection, $driverClassName); @@ -43,9 +42,9 @@ private function startDbTransaction() */ private function endDbTransaction() { - $database = app('db'); - foreach ($this->connectionsToTransact() as $connection) { + $database ??= app('db'); + $driver = $database->connection($connection); try { $driver->rollback(); diff --git a/src/Extracting/Extractor.php b/src/Extracting/Extractor.php index 1c8f6d25..fd56d469 100644 --- a/src/Extracting/Extractor.php +++ b/src/Extracting/Extractor.php @@ -215,23 +215,24 @@ protected function iterateThroughStrategies( } $settings = self::transformOldRouteRulesIntoNewSettings($stage, $rulesToApply, $strategyClass, $settings); - $routesToSkip = $settings["except"] ?? []; - $routesToInclude = $settings["only"] ?? []; - - if (!empty($routesToSkip)) { - if (RoutePatternMatcher::matches($endpointData->route, $routesToSkip)) { - continue; - } - } elseif (!empty($routesToInclude)) { - if (!RoutePatternMatcher::matches($endpointData->route, $routesToInclude)) { - continue; - } - } } else { $strategyClass = $strategyClassOrTuple; $settings = self::transformOldRouteRulesIntoNewSettings($stage, $rulesToApply, $strategyClass); } + $routesToSkip = $settings["except"] ?? []; + $routesToInclude = $settings["only"] ?? []; + + if (!empty($routesToSkip)) { + if (RoutePatternMatcher::matches($endpointData->route, $routesToSkip)) { + continue; + } + } elseif (!empty($routesToInclude)) { + if (!RoutePatternMatcher::matches($endpointData->route, $routesToInclude)) { + continue; + } + } + $strategy = new $strategyClass($this->config); $results = $strategy($endpointData, $settings); if (is_array($results)) { diff --git a/tests/Unit/ExtractorTest.php b/tests/Unit/ExtractorTest.php index 6e355041..a009ca6b 100644 --- a/tests/Unit/ExtractorTest.php +++ b/tests/Unit/ExtractorTest.php @@ -7,11 +7,12 @@ use Knuckles\Camel\Extraction\Parameter; use Knuckles\Scribe\Extracting\Extractor; use Knuckles\Scribe\Extracting\Strategies; +use Knuckles\Scribe\Tests\BaseLaravelTest; use Knuckles\Scribe\Tests\BaseUnitTest; use Knuckles\Scribe\Tests\Fixtures\TestController; use Knuckles\Scribe\Tools\DocumentationConfig; -class ExtractorTest extends BaseUnitTest +class ExtractorTest extends BaseLaravelTest { protected Extractor $extractor; @@ -52,7 +53,7 @@ public function setUp(): void { parent::setUp(); - $this->extractor = new Extractor(new DocumentationConfig($this->config)); + $this->extractor = $this->makeExtractor($this->config); } /** @test */ @@ -181,6 +182,48 @@ public function can_parse_route_methods() $this->assertEquals(['DELETE'], $parsed->httpMethods); } + /** @test */ + public function invokes_strategy_based_on_deprecated_route_apply_rules() + { + $config = $this->config; + $config['strategies']['responses'] = [Strategies\Responses\ResponseCalls::class]; + + $extractor = $this->makeExtractor($config); + $route = $this->createRoute('GET', '/get', 'shouldFetchRouteResponse'); + $parsed = $extractor->processRoute($route, ['response_calls' => ['methods' => ['POST']]]); + $this->assertEmpty($parsed->responses); + + $parsed = $extractor->processRoute($route, ['response_calls' => ['methods' => ['GET']]]); + $this->assertNotEmpty($parsed->responses); + } + + /** @test */ + public function invokes_strategy_based_on_new_strategy_configs() + { + $route = $this->createRoute('GET', '/get', 'shouldFetchRouteResponse'); + $config = $this->config; + $config['strategies']['responses'] = [ + [ + Strategies\Responses\ResponseCalls::class, + ['only' => 'POST *'] + ] + ]; + $extractor = $this->makeExtractor($config); + + $parsed = $extractor->processRoute($route); + $this->assertEmpty($parsed->responses); + + $config['strategies']['responses'] = [ + [ + Strategies\Responses\ResponseCalls::class, + ['only' => 'GET *'] + ] + ]; + $extractor = $this->makeExtractor($config); + $parsed = $extractor->processRoute($route); + $this->assertNotEmpty($parsed->responses); + } + /** * @test * @dataProvider authRules @@ -188,7 +231,7 @@ public function can_parse_route_methods() public function adds_appropriate_field_based_on_configured_auth_type($config, $expected) { $route = $this->createRouteOldSyntax('POST', '/withAuthenticatedTag', 'withAuthenticatedTag'); - $generator = new Extractor(new DocumentationConfig(array_merge($this->config, $config))); + $generator = $this->makeExtractor(array_merge($this->config, $config)); $parsed = $generator->processRoute($route, [])->toArray(); $this->assertNotNull($parsed[$expected['where']][$expected['name']]); $this->assertEquals($expected['where'], $parsed['auth'][0]); @@ -210,7 +253,7 @@ public function generates_consistent_examples_when_faker_seed_is_set() // Examples should have different values $this->assertNotEquals(1, count($results)); - $generator = new Extractor(new DocumentationConfig($this->config + ['examples' => ['faker_seed' => 12345]])); + $generator = $this->makeExtractor($this->config + ['examples' => ['faker_seed' => 12345]]); $results = []; $results[$generator->processRoute($route)->cleanBodyParameters[$paramName]] = true; $results[$generator->processRoute($route)->cleanBodyParameters[$paramName]] = true; @@ -374,6 +417,11 @@ public static function authRules() ], ]; } + + protected function makeExtractor(mixed $config): Extractor + { + return new Extractor(new DocumentationConfig($config)); + } } From a1cb38e76f1e67b198348293768ba872d633e298 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Mon, 17 Jun 2024 21:46:23 +0200 Subject: [PATCH 17/32] Add test for header overrides --- tests/Unit/ExtractorTest.php | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/Unit/ExtractorTest.php b/tests/Unit/ExtractorTest.php index a009ca6b..6f74e6ec 100644 --- a/tests/Unit/ExtractorTest.php +++ b/tests/Unit/ExtractorTest.php @@ -224,6 +224,47 @@ public function invokes_strategy_based_on_new_strategy_configs() $this->assertNotEmpty($parsed->responses); } + /** @test */ + public function adds_override_for_headers_based_on_deprecated_route_apply_rules() + { + $config = $this->config; + $config['strategies']['headers'] = [Strategies\Headers\GetFromRouteRules::class]; + + $extractor = $this->makeExtractor($config); + $route = $this->createRoute('GET', '/get', 'dummy'); + $parsed = $extractor->processRoute($route, ['headers' => ['content-type' => 'application/json+vnd']]); + $this->assertArraySubset($parsed->headers, ['content-type' => 'application/json+vnd']); + + $parsed = $extractor->processRoute($route); + $this->assertEmpty($parsed->headers); + } + + /** @test */ + public function adds_override_for_headers_based_on_strategy_configs() + { + $route = $this->createRoute('GET', '/get', 'dummy'); + $config = $this->config; + + $config['strategies']['headers'] = [Strategies\Headers\GetFromHeaderAttribute::class]; + $extractor = $this->makeExtractor($config); + $parsed = $extractor->processRoute($route); + $this->assertEmpty($parsed->headers); + + $headers = [ + 'accept' => 'application/json', + 'Content-Type' => 'application/json+vnd', + ]; + $config['strategies']['headers'] = [ + Strategies\Headers\GetFromHeaderAttribute::class, + [ + 'override', $headers + ], + ]; + $extractor = $this->makeExtractor($config); + $parsed = $extractor->processRoute($route); + $this->assertArraySubset($parsed->headers, $headers); + } + /** * @test * @dataProvider authRules From bd2812731cf03e0632118000e73a5e72c8442847 Mon Sep 17 00:00:00 2001 From: Rouven Hurling Date: Mon, 17 Jun 2024 22:32:02 +0200 Subject: [PATCH 18/32] Allow multiple content types and schemas (via oneOf) according to OpenAPI 3.0 spec (#792) * rewrite generateEndpointResponsesSpec to allow multiple content types and schemas (via oneOf) according to OpenAPI 3.0 spec * Update OpenAPISpecWriter.php * Add test --------- Co-authored-by: Shalvah --- src/Writing/OpenAPISpecWriter.php | 25 +++++++++ tests/Unit/OpenAPISpecWriterTest.php | 81 +++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/Writing/OpenAPISpecWriter.php b/src/Writing/OpenAPISpecWriter.php index 3a0d6299..2c46eae6 100644 --- a/src/Writing/OpenAPISpecWriter.php +++ b/src/Writing/OpenAPISpecWriter.php @@ -269,7 +269,32 @@ protected function generateEndpointResponsesSpec(OutputEndpointData $endpoint) $responses[204] = [ 'description' => $this->getResponseDescription($response), ]; + } elseif (isset($responses[$response->status])) { + // If we already have a response for this status code and content type, + // we change to a `oneOf` which includes all the responses + $content = $this->generateResponseContentSpec($response->content, $endpoint); + $contentType = array_keys($content)[0]; + if (isset($responses[$response->status]['content'][$contentType])) { + // If we've already created the oneOf object, add this response + if (isset($responses[$response->status]['content'][$contentType]['schema']['oneOf'])) { + $responses[$response->status]['content'][$contentType]['schema']['oneOf'][] = $content[$contentType]; + } else { + // Create the oneOf object + $existingResponseExample = array_replace([ + 'description' => $responses[$response->status]['description'], + ], $responses[$response->status]['content'][$contentType]['schema']); + $newResponseExample = array_replace([ + 'description' => $this->getResponseDescription($response), + ], $content[$contentType]['schema']); + + $responses[$response->status]['description'] = ''; + $responses[$response->status]['content'][$contentType]['schema'] = [ + 'oneOf' => [$existingResponseExample, $newResponseExample] + ]; + } + } } else { + // Store as the response for this status $responses[$response->status] = [ 'description' => $this->getResponseDescription($response), 'content' => $this->generateResponseContentSpec($response->content, $endpoint), diff --git a/tests/Unit/OpenAPISpecWriterTest.php b/tests/Unit/OpenAPISpecWriterTest.php index 6d2c94a5..648e3529 100644 --- a/tests/Unit/OpenAPISpecWriterTest.php +++ b/tests/Unit/OpenAPISpecWriterTest.php @@ -445,7 +445,7 @@ public function adds_responses_correctly_as_responses_on_operation_object() 'type' => 'string', 'description' => 'Parameter description, ha!', ], - 'sub level 0.sub level 1 key 3.sub level 2 key 1'=> [ + 'sub level 0.sub level 1 key 3.sub level 2 key 1' => [ 'description' => 'This is description of nested object', ] ], @@ -557,6 +557,85 @@ public function adds_responses_correctly_as_responses_on_operation_object() ], $results['paths']['/path2']['put']['responses']); } + /** @test */ + public function adds_multiple_responses_correctly_using_oneOf() + { + $endpointData1 = $this->createMockEndpointData([ + 'httpMethods' => ['POST'], + 'uri' => '/path1', + 'responses' => [ + [ + 'status' => 201, + 'description' => 'This one', + 'content' => '{"this": "one"}', + ], + [ + 'status' => 201, + 'description' => 'No, that one.', + 'content' => '{"that": "one"}', + ], + [ + 'status' => 200, + 'description' => 'A separate one', + 'content' => '{"the other": "one"}', + ], + ], + ]); + $groups = [$this->createGroup([$endpointData1])]; + + $results = $this->generate($groups); + + $this->assertArraySubset([ + '200' => [ + 'description' => 'A separate one', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'the other' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + ], + ], + ], + '201' => [ + 'description' => '', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'oneOf' => [ + [ + 'type' => 'object', + 'description' => 'This one', + 'properties' => [ + 'this' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + [ + 'type' => 'object', + 'description' => 'No, that one.', + 'properties' => [ + 'that' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + ], + ], + ], + ], + ], + ], $results['paths']['/path1']['post']['responses']); + } + protected function createMockEndpointData(array $custom = []): OutputEndpointData { $faker = Factory::create(); From 85ad49144a10a69c5dbaaf1627feae48e6f2ec17 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Mon, 17 Jun 2024 22:34:33 +0200 Subject: [PATCH 19/32] 4.37.0 --- CHANGELOG.md | 5 +++++ src/Scribe.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4df9aa49..a7095b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed +# 4.37.0 (17 june 2024) +## Added +- Support multiple responses in OpenAPI spec using oneOf [#739](https://github.com/knuckleswtf/scribe/pull/739) + + # 4.36.0 (27 May 2024) ## Added - Add `afterResponseCall` hook [#847](https://github.com/knuckleswtf/scribe/pull/847) diff --git a/src/Scribe.php b/src/Scribe.php index d248cf69..eebc719c 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -9,7 +9,7 @@ class Scribe { - public const VERSION = '4.36.0'; + public const VERSION = '4.37.0'; /** * Specify a callback that will be executed just before a response call is made From 4dddeb4e3643dfb85bc84d18e192e34ec4d5b134 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Thu, 11 Jul 2024 15:41:34 +0200 Subject: [PATCH 20/32] Fix typo on elements.blade.php (#861) --- resources/views/external/elements.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/external/elements.blade.php b/resources/views/external/elements.blade.php index 848dd180..bff12b3a 100644 --- a/resources/views/external/elements.blade.php +++ b/resources/views/external/elements.blade.php @@ -4,7 +4,7 @@ - {!! $metadata['title'] !!}L + {!! $metadata['title'] !!} From 87c2dbd89a6467888c7d57331affeb18cc6ec64d Mon Sep 17 00:00:00 2001 From: Giovani Vrech Date: Thu, 11 Jul 2024 10:45:31 -0300 Subject: [PATCH 21/32] fix: multiple responses with the same status code (#863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Giovani Müller --- src/Writing/OpenAPISpecWriter.php | 9 +-- tests/Unit/OpenAPISpecWriterTest.php | 94 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/src/Writing/OpenAPISpecWriter.php b/src/Writing/OpenAPISpecWriter.php index 2c46eae6..fc89ace4 100644 --- a/src/Writing/OpenAPISpecWriter.php +++ b/src/Writing/OpenAPISpecWriter.php @@ -275,17 +275,18 @@ protected function generateEndpointResponsesSpec(OutputEndpointData $endpoint) $content = $this->generateResponseContentSpec($response->content, $endpoint); $contentType = array_keys($content)[0]; if (isset($responses[$response->status]['content'][$contentType])) { + $newResponseExample = array_replace([ + 'description' => $this->getResponseDescription($response), + ], $content[$contentType]['schema']); + // If we've already created the oneOf object, add this response if (isset($responses[$response->status]['content'][$contentType]['schema']['oneOf'])) { - $responses[$response->status]['content'][$contentType]['schema']['oneOf'][] = $content[$contentType]; + $responses[$response->status]['content'][$contentType]['schema']['oneOf'][] = $newResponseExample; } else { // Create the oneOf object $existingResponseExample = array_replace([ 'description' => $responses[$response->status]['description'], ], $responses[$response->status]['content'][$contentType]['schema']); - $newResponseExample = array_replace([ - 'description' => $this->getResponseDescription($response), - ], $content[$contentType]['schema']); $responses[$response->status]['description'] = ''; $responses[$response->status]['content'][$contentType]['schema'] = [ diff --git a/tests/Unit/OpenAPISpecWriterTest.php b/tests/Unit/OpenAPISpecWriterTest.php index 648e3529..d906dc6d 100644 --- a/tests/Unit/OpenAPISpecWriterTest.php +++ b/tests/Unit/OpenAPISpecWriterTest.php @@ -636,6 +636,100 @@ public function adds_multiple_responses_correctly_using_oneOf() ], $results['paths']['/path1']['post']['responses']); } + /** @test */ + public function adds_more_than_two_answers_correctly_using_oneOf() + { + $endpointData1 = $this->createMockEndpointData([ + 'httpMethods' => ['POST'], + 'uri' => '/path1', + 'responses' => [ + [ + 'status' => 201, + 'description' => 'This one', + 'content' => '{"this": "one"}', + ], + [ + 'status' => 201, + 'description' => 'No, that one.', + 'content' => '{"that": "one"}', + ], + [ + 'status' => 201, + 'description' => 'No, another one.', + 'content' => '{"another": "one"}', + ], + [ + 'status' => 200, + 'description' => 'A separate one', + 'content' => '{"the other": "one"}', + ], + ], + ]); + $groups = [$this->createGroup([$endpointData1])]; + + $results = $this->generate($groups); + + $this->assertArraySubset([ + '200' => [ + 'description' => 'A separate one', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'the other' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + ], + ], + ], + '201' => [ + 'description' => '', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'oneOf' => [ + [ + 'type' => 'object', + 'description' => 'This one', + 'properties' => [ + 'this' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + [ + 'type' => 'object', + 'description' => 'No, that one.', + 'properties' => [ + 'that' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + [ + 'type' => 'object', + 'description' => 'No, another one.', + 'properties' => [ + 'another' => [ + 'example' => "one", + 'type' => 'string', + ], + ], + ], + ], + ], + ], + ], + ], + ], $results['paths']['/path1']['post']['responses']); + } + protected function createMockEndpointData(array $custom = []): OutputEndpointData { $faker = Factory::create(); From 7f7b74a78866900dcd38a53f89978db1837fbd58 Mon Sep 17 00:00:00 2001 From: wlhrtr Date: Thu, 11 Jul 2024 15:48:45 +0200 Subject: [PATCH 22/32] fix: fixes elements theme mutlipart form file upload (#864) Co-authored-by: Peter Weilharter --- resources/views/themes/elements/index.blade.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/views/themes/elements/index.blade.php b/resources/views/themes/elements/index.blade.php index bc1ba060..dfa760b6 100644 --- a/resources/views/themes/elements/index.blade.php +++ b/resources/views/themes/elements/index.blade.php @@ -111,6 +111,11 @@ function tryItOut(btnElement) { }); } + // content type has to be unset otherwise file upload won't work + if (form.dataset.hasfiles === "1") { + delete headers['Content-Type']; + } + return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId)) .then(([responseStatus, statusText, responseContent, responseHeaders]) => { responsePanel.hidden = false; From 5eb0f65973db9df5ba455f8bbcc8a1cd6573564b Mon Sep 17 00:00:00 2001 From: Shalvah Date: Thu, 11 Jul 2024 15:57:05 +0200 Subject: [PATCH 23/32] 4.37.1 --- CHANGELOG.md | 9 ++++++++- src/Scribe.php | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7095b65..c964282d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed -# 4.37.0 (17 june 2024) +# 4.37.1 (11 July 2024) +## Fixed +- Multipart file upload in `elements` theme [#864](https://github.com/knuckleswtf/scribe/pull/864) +- Properly set multiple responses in OpenAPI spec with the same status code [#863](https://github.com/knuckleswtf/scribe/pull/863) + + + +# 4.37.0 (17 June 2024) ## Added - Support multiple responses in OpenAPI spec using oneOf [#739](https://github.com/knuckleswtf/scribe/pull/739) diff --git a/src/Scribe.php b/src/Scribe.php index eebc719c..9d0b2cda 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -9,7 +9,7 @@ class Scribe { - public const VERSION = '4.37.0'; + public const VERSION = '4.37.1'; /** * Specify a callback that will be executed just before a response call is made From 754eacda09e4e75e383246d401aa950fe54e6447 Mon Sep 17 00:00:00 2001 From: Patryk Vizauer Date: Sat, 24 Aug 2024 14:55:17 +0200 Subject: [PATCH 24/32] Allow custom output path for `static` and `external_static` instead of only `static` (#884) --- src/Writing/HtmlWriter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Writing/HtmlWriter.php b/src/Writing/HtmlWriter.php index b40b8522..78fa4c82 100644 --- a/src/Writing/HtmlWriter.php +++ b/src/Writing/HtmlWriter.php @@ -30,7 +30,7 @@ public function __construct(DocumentationConfig $config = null) // If they're using the default static path, // then use '../docs/{asset}', so assets can work via Laravel app or via index.html $this->assetPathPrefix = '../docs/'; - if ($this->config->get('type') == 'static' + if (in_array($this->config->get('type'), ['static', 'external_static']) && rtrim($this->config->get('static.output_path', ''), '/') != 'public/docs' ) { $this->assetPathPrefix = './'; From a995f1a94bd1621341d05e959ec974ea3b1c526b Mon Sep 17 00:00:00 2001 From: Tom <7723105+thomasfw@users.noreply.github.com> Date: Sat, 24 Aug 2024 13:58:07 +0100 Subject: [PATCH 25/32] Allow comments to be parsed for validator rules with non `string`/`array` values in `GetFromInlineValidatorBase` (#880) --- src/Extracting/Strategies/GetFromInlineValidatorBase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Extracting/Strategies/GetFromInlineValidatorBase.php b/src/Extracting/Strategies/GetFromInlineValidatorBase.php index b6ff7ff8..e52640f5 100644 --- a/src/Extracting/Strategies/GetFromInlineValidatorBase.php +++ b/src/Extracting/Strategies/GetFromInlineValidatorBase.php @@ -96,7 +96,6 @@ enum_exists($enum) && method_exists($enum, 'tryFrom') $rules[$paramName] = join('|', $rulesList); } else { $rules[$paramName] = []; - continue; } $dataFromComment = []; From a98476b031c70732f84ee920c8c381c09f4ff039 Mon Sep 17 00:00:00 2001 From: Diego Smania Date: Sat, 24 Aug 2024 09:59:43 -0300 Subject: [PATCH 26/32] Update theme-default.style.css (#868) Fix issue 867 and remove some duplicated code --- resources/css/theme-default.style.css | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/resources/css/theme-default.style.css b/resources/css/theme-default.style.css index dd7d3aa7..b46a0745 100644 --- a/resources/css/theme-default.style.css +++ b/resources/css/theme-default.style.css @@ -688,6 +688,7 @@ html { .content>p, .content>table, .content>ul, +.content>div, .content>form>aside, .content>form>details, .content>form>h1, @@ -961,20 +962,6 @@ html { .page-wrapper .lang-selector { display: none } - .content aside, - .content dl, - .content h1, - .content h2, - .content h3, - .content h4, - .content h5, - .content h6, - .content ol, - .content p, - .content table, - .content ul { - margin-right: 0 - } .content>aside, .content>details, .content>dl, @@ -988,6 +975,7 @@ html { .content>p, .content>table, .content>ul, + .content>div, .content>form>aside, .content>form>details, .content>form>h1, From 6318f3f68cbf09328e5cb6843ce1739e529ef1ac Mon Sep 17 00:00:00 2001 From: Shalvah Date: Fri, 30 Aug 2024 14:15:51 +0200 Subject: [PATCH 27/32] 4.37.2 --- CHANGELOG.md | 7 +++++++ src/Scribe.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c964282d..ced6e9d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed +# 4.37.2 (30 August 2024) +## Fixed +- Stop response fields from overflowing to the dark box zone [#868](https://github.com/knuckleswtf/scribe/pull/868) +- Don't ignore comments for validator parameters with non string/array (e.g. conditional) rule lists [#880](https://github.com/knuckleswtf/scribe/pull/880) +- Allow custom output path for static and external_static instead of only static [#884](https://github.com/knuckleswtf/scribe/pull/884) + + # 4.37.1 (11 July 2024) ## Fixed - Multipart file upload in `elements` theme [#864](https://github.com/knuckleswtf/scribe/pull/864) diff --git a/src/Scribe.php b/src/Scribe.php index 9d0b2cda..074ff08e 100644 --- a/src/Scribe.php +++ b/src/Scribe.php @@ -9,7 +9,7 @@ class Scribe { - public const VERSION = '4.37.1'; + public const VERSION = '4.37.2'; /** * Specify a callback that will be executed just before a response call is made From 18e873f0403d2938b9aebf9564e87b3e581b0c44 Mon Sep 17 00:00:00 2001 From: Diego Smania Date: Sat, 7 Sep 2024 00:10:46 -0300 Subject: [PATCH 28/32] [resources/views]: Fix the display of boolean examples on the elements theme (#887) --- .../views/themes/elements/components/field-details.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/themes/elements/components/field-details.blade.php b/resources/views/themes/elements/components/field-details.blade.php index b2682fc0..d8c57cf4 100644 --- a/resources/views/themes/elements/components/field-details.blade.php +++ b/resources/views/themes/elements/components/field-details.blade.php @@ -49,12 +49,12 @@ class="svg-inline--fa fa-chevron-right fa-fw fa-sm sl-icon" role="img" @endif @endif - @if(!$hasChildren && !is_null($example) && $example != '') + @if(!$hasChildren && !is_null($example) && $example !== '')
Example:
- {{ is_array($example) ? json_encode($example) : $example }} + {{ is_array($example) || is_bool($example) ? json_encode($example) : $example }}
From 02f4f3f4d342bb7d95e5f45281ea9a6700583fdc Mon Sep 17 00:00:00 2001 From: MrCrayon Date: Mon, 16 Sep 2024 06:27:15 +0800 Subject: [PATCH 29/32] Fixes html content not showing in received response (#890) Replaces html special characters with html entities --- resources/views/themes/elements/index.blade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/views/themes/elements/index.blade.php b/resources/views/themes/elements/index.blade.php index dfa760b6..b921e15c 100644 --- a/resources/views/themes/elements/index.blade.php +++ b/resources/views/themes/elements/index.blade.php @@ -137,6 +137,9 @@ function tryItOut(btnElement) { } } catch (e) {} + // Replace HTML entities + responseContent = responseContent.replace(/[<>&]/g, (i) => '&#' + i.charCodeAt(0) + ';'); + contentEl.innerHTML = responseContent; isJson && window.hljs.highlightElement(contentEl); }) From 3ab4ae7029b3db58b5509e3721b2dc146f04a17e Mon Sep 17 00:00:00 2001 From: eschricker <56429442+eschricker@users.noreply.github.com> Date: Mon, 16 Sep 2024 00:27:42 +0200 Subject: [PATCH 30/32] Postman collection export: encoding value to prevent validation errors (#888) * encoded value before putting into postman structure to prevent validation errors * apply urlencoding just if value is not a string * replaced urlencode with strval --- src/Writing/PostmanCollectionWriter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Writing/PostmanCollectionWriter.php b/src/Writing/PostmanCollectionWriter.php index a115f465..29be7765 100644 --- a/src/Writing/PostmanCollectionWriter.php +++ b/src/Writing/PostmanCollectionWriter.php @@ -293,7 +293,7 @@ protected function generateUrlObject(OutputEndpointData $endpointData): array // See https://www.php.net/manual/en/function.parse-str.php $query[] = [ 'key' => "{$name}[$index]", - 'value' => $value, + 'value' => is_string($value) ? $value : strval($value), 'description' => strip_tags($parameterData->description), // Default query params to disabled if they aren't required and have empty values 'disabled' => !$parameterData->required && empty($parameterData->example), From a26d9c912a6c42f19a133df1db571c802dbc47ee Mon Sep 17 00:00:00 2001 From: Ahmad Shakib Date: Fri, 20 Sep 2024 00:56:38 +0330 Subject: [PATCH 31/32] render laravel dd() dump output properly (#893) --- resources/css/theme-default.style.css | 5 +++++ resources/js/tryitout.js | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/resources/css/theme-default.style.css b/resources/css/theme-default.style.css index b46a0745..9a4741c1 100644 --- a/resources/css/theme-default.style.css +++ b/resources/css/theme-default.style.css @@ -894,6 +894,11 @@ html { text-shadow: 0 1px 2px rgba(0, 0, 0, .4) } +.content blockquote pre.sf-dump, +.content pre pre.sf-dump { + width: 100%; +} + .content .annotation { background-color: #292929; color: #fff; diff --git a/resources/js/tryitout.js b/resources/js/tryitout.js index 01c584f2..2a1d2b8a 100644 --- a/resources/js/tryitout.js +++ b/resources/js/tryitout.js @@ -139,6 +139,17 @@ function handleResponse(endpointId, response, status, headers) { const responseContentEl = document.querySelector('#execution-response-content-' + endpointId); + // Check if the response contains Laravel's dd() default dump output + const isLaravelDump = response.includes('Sfdump'); + + // If it's a Laravel dd() dump, use innerHTML to render it safely + if (isLaravelDump) { + responseContentEl.innerHTML = response === '' ? responseContentEl.dataset.emptyResponseText : response; + } else { + // Otherwise, stick to textContent for regular responses + responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response; + } + // Prettify it if it's JSON let isJson = false; try { @@ -146,11 +157,12 @@ function handleResponse(endpointId, response, status, headers) { if (jsonParsed !== null) { isJson = true; response = JSON.stringify(jsonParsed, null, 4); + responseContentEl.textContent = response; } } catch (e) { } - responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response; + isJson && window.hljs.highlightElement(responseContentEl); const statusEl = document.querySelector('#execution-response-status-' + endpointId); statusEl.textContent = ` (${status})`; From 8a6c64498c7b00d76ce3287b81f26eb982bd0d46 Mon Sep 17 00:00:00 2001 From: lotfimostafa Date: Wed, 25 Sep 2024 10:53:50 +0330 Subject: [PATCH 32/32] feat: Add parsing support for exists rule in parseRule switch case (#886) * feat: add parsing support for exists rule in parseRule switch case * feat: add test support for exists rule in supportedRules yield * Add schema creation * Improve message * Skip example comparison --------- Co-authored-by: Shalvah --- src/Extracting/ParsesValidationRules.php | 4 +++- tests/Unit/ValidationRuleParsingTest.php | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Extracting/ParsesValidationRules.php b/src/Extracting/ParsesValidationRules.php index b78b113e..0dd9038c 100644 --- a/src/Extracting/ParsesValidationRules.php +++ b/src/Extracting/ParsesValidationRules.php @@ -531,7 +531,9 @@ protected function parseRule($rule, array &$parameterData, bool $independentOnly case 'different': $parameterData['description'] .= " The value and {$arguments[0]} must be different."; break; - + case 'exists': + $parameterData['description'] .= " The {$arguments[1]} of an existing record in the {$arguments[0]} table."; + break; default: // Other rules not supported break; diff --git a/tests/Unit/ValidationRuleParsingTest.php b/tests/Unit/ValidationRuleParsingTest.php index 03aa4e9a..5a877ac9 100644 --- a/tests/Unit/ValidationRuleParsingTest.php +++ b/tests/Unit/ValidationRuleParsingTest.php @@ -3,6 +3,7 @@ namespace Knuckles\Scribe\Tests\Unit; use Illuminate\Foundation\Application; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Validator; use Illuminate\Translation\Translator; use Illuminate\Validation\Rule; @@ -40,6 +41,11 @@ public function parse($validationRules, $customParameterData = []): array */ public function can_parse_supported_rules(array $ruleset, array $customInfo, array $expected) { + // Needed for `exists` rule + Schema::create('users', function ($table) { + $table->id(); + }); + $results = $this->strategy->parse($ruleset, $customInfo); $parameterName = array_keys($ruleset)[0]; @@ -49,7 +55,9 @@ public function can_parse_supported_rules(array $ruleset, array $customInfo, arr $this->assertEquals($expected['type'], $results[$parameterName]['type']); } - // Validate that the generated values actually pass validation + // Validate that the generated values actually pass validation (for rules where we can generate some data) + if (is_string($ruleset[$parameterName]) && str_contains($ruleset[$parameterName], "exists")) return; + $exampleData = [$parameterName => $results[$parameterName]['example']]; $validator = Validator::make($exampleData, $ruleset); try { @@ -439,6 +447,13 @@ public static function supportedRules() 'description' => 'Must be accepted.', ], ]; + yield 'exists' => [ + ['exists_param' => 'exists:users,id'], + [], + [ + 'description' => 'The id of an existing record in the users table.', + ], + ]; yield 'unsupported' => [ ['unsupported_param' => [new DummyValidationRule, 'bail']], ['unsupported_param' => ['description' => $description]],