From ac0290c12e33fa24f9c6d68222c4dd53d68939ef Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Wed, 22 Mar 2023 14:45:25 +0100 Subject: [PATCH 1/7] add multiple routes and versions support --- README.md | 5 +- config/swagger-ui.php | 112 +++++++----------- package-lock.json | 6 + resources/views/index.blade.php | 18 ++- .../Controllers/OpenApiJsonController.php | 37 +++--- .../Controllers/SwaggerViewController.php | 17 +++ src/SwaggerUiServiceProvider.php | 16 +-- tests/AuthorizationTest.php | 17 ++- tests/OpenApiRouteTest.php | 42 +++---- tests/SwaggerUiRouteTest.php | 33 ++++-- tests/TestCase.php | 22 ++++ 11 files changed, 189 insertions(+), 136 deletions(-) create mode 100644 package-lock.json create mode 100644 src/Http/Controllers/SwaggerViewController.php create mode 100644 tests/TestCase.php diff --git a/README.md b/README.md index ca9d32b..303bff6 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ protected function gate() ``` In the published `config/swagger-ui.php` file, you edit the path to the Swagger UI and the location of the Swagger (OpenAPI v3) file. By default, the package expects to find the OpenAPI file in 'resources/swagger' directory. You can also provide an url if the OpenAPI file is not present in the Laravel project itself. +This is also where you can define multiple versions for the same api. ```php // in config/swagger-ui.php @@ -53,7 +54,9 @@ return [ 'path' => 'swagger', - 'file' => resource_path('swagger/openapi.json'), + 'versions' => [ + 'v1' => resource_path('swagger/openapi.json') + ], // ... ]; diff --git a/config/swagger-ui.php b/config/swagger-ui.php index 5dedac2..3759544 100644 --- a/config/swagger-ui.php +++ b/config/swagger-ui.php @@ -3,73 +3,49 @@ use NextApps\SwaggerUi\Http\Middleware\EnsureUserIsAuthorized; return [ - /* - |-------------------------------------------------------------------------- - | Swagger UI - Path - |-------------------------------------------------------------------------- - | - | This is the URI path where SwaggerUI will be accessible from. Feel free - | to change this path to anything you like. - | - */ - - 'path' => 'swagger', - - /* - |-------------------------------------------------------------------------- - | Swagger UI - Route Middleware - |-------------------------------------------------------------------------- - | - | These middleware will be assigned to every Swagger UI route. - | - */ - - 'middleware' => [ - 'web', - EnsureUserIsAuthorized::class, - ], - - /* - |-------------------------------------------------------------------------- - | Swagger UI - OpenAPI File - |-------------------------------------------------------------------------- - | - | This is the location of the project's OpenAPI / Swagger JSON file. It's - | this file that will be used in Swagger UI. This can either be a local - | file or an url to a file. - | - */ - - 'file' => resource_path('swagger/openapi.json'), - - /* - |-------------------------------------------------------------------------- - | Swagger UI - Modify File - |-------------------------------------------------------------------------- - | - | If this option is enabled, then the file will be changed before it is - | used by Swagger UI. The server url and oauth urls will be changed to - | the base url of this Laravel application. - | - */ - - 'modify_file' => true, - - /* - |-------------------------------------------------------------------------- - | Swagger UI - OAuth Config - |-------------------------------------------------------------------------- - | - | This allows you to configure oauth within Swagger UI. It makes it easier - | to authenticate in Swagger UI by prefilling certain values. - | - */ - 'oauth' => [ - 'token_path' => 'oauth/token', - 'refresh_path' => 'oauth/token', - 'authorization_path' => 'oauth/authorize', - - 'client_id' => env('SWAGGER_UI_OAUTH_CLIENT_ID'), - 'client_secret' => env('SWAGGER_UI_OAUTH_CLIENT_SECRET'), + 'files' => [ + [ + /* + * The path where the swagger file is served. + */ + 'path' => 'swagger', + + /* + * The versions of the swagger file. The key is the version name and the value is the path to the file. + */ + 'versions' => [ + 'v1' => resource_path('swagger/openapi.json'), + ], + + /* + * The default version that is loaded when the route is accessed. + */ + 'default' => 'v1', + + /* + * The middleware that is applied to the route. + */ + 'middleware' => [ + 'web', + EnsureUserIsAuthorized::class, + ], + + /* + * The oauth configuration for the swagger file. + */ + 'oauth' => [ + 'token_path' => 'oauth/token', + 'refresh_path' => 'oauth/token', + 'authorization_path' => 'oauth/authorize', + + 'client_id' => env('SWAGGER_UI_OAUTH_CLIENT_ID'), + 'client_secret' => env('SWAGGER_UI_OAUTH_CLIENT_SECRET'), + ], + + /* + * If enabled the file will be modified to set the server url and oauth urls. + */ + 'modify_file' => true, + ], ], ]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f2862a2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "laravel-swagger-ui", + "lockfileVersion": 2, + "requires": true, + "packages": {} +} diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index d75c928..a777e60 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -27,22 +27,32 @@
+ diff --git a/src/Http/Controllers/OpenApiJsonController.php b/src/Http/Controllers/OpenApiJsonController.php index 1fe0700..58a07b5 100644 --- a/src/Http/Controllers/OpenApiJsonController.php +++ b/src/Http/Controllers/OpenApiJsonController.php @@ -8,19 +8,26 @@ class OpenApiJsonController { - public function __invoke() : JsonResponse + public function __invoke(string $path, string $filename) : JsonResponse { - $json = $this->getJson(); + $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($filename, $path) { + return isset($values['versions'][$filename]) && ltrim($values['path'], '/') === $path; + })->first(); - $json = $this->configureServer($json); - $json = $this->configureOAuth($json); + if (empty($file)) { + abort(404); + } + + $json = $this->getJson($file['versions'][$filename]); + + $json = $this->configureServer($file, $json); + $json = $this->configureOAuth($file, $json); return response()->json($json); } - protected function getJson() : array + protected function getJson(string $path) : array { - $path = config('swagger-ui.file'); $content = file_get_contents($path); if (Str::endsWith($path, '.yaml')) { @@ -34,9 +41,9 @@ protected function getJson() : array return json_decode($content, true); } - protected function configureServer(array $json) : array + protected function configureServer(array $file, array $json) : array { - if (! config('swagger-ui.modify_file')) { + if (! $file['modify_file']) { return $json; } @@ -47,28 +54,28 @@ protected function configureServer(array $json) : array return $json; } - protected function configureOAuth(array $json) : array + protected function configureOAuth(array $file, array $json) : array { - if (empty($json['components']['securitySchemes']) || ! config('swagger-ui.modify_file')) { + if (empty($json['components']['securitySchemes']) || ! $file['modify_file']) { return $json; } - $securitySchemes = collect($json['components']['securitySchemes'])->map(function ($scheme) { + $securitySchemes = collect($json['components']['securitySchemes'])->map(function ($scheme) use ($file) { if ($scheme['type'] !== 'oauth2') { return $scheme; } - $scheme['flows'] = collect($scheme['flows'])->map(function ($flow) { + $scheme['flows'] = collect($scheme['flows'])->map(function ($flow) use ($file) { if (isset($flow['tokenUrl'])) { - $flow['tokenUrl'] = url(config('swagger-ui.oauth.token_path')); + $flow['tokenUrl'] = url($file['oauth']['token_path']); } if (isset($flow['refreshUrl'])) { - $flow['refreshUrl'] = url(config('swagger-ui.oauth.refresh_path')); + $flow['refreshUrl'] = url($file['oauth']['refresh_path']); } if (isset($flow['authorizationUrl'])) { - $flow['authorizationUrl'] = url(config('swagger-ui.oauth.authorization_path')); + $flow['authorizationUrl'] = url($file['oauth']['authorization_path']); } return $flow; diff --git a/src/Http/Controllers/SwaggerViewController.php b/src/Http/Controllers/SwaggerViewController.php new file mode 100644 index 0000000..05ecd83 --- /dev/null +++ b/src/Http/Controllers/SwaggerViewController.php @@ -0,0 +1,17 @@ +filter(function ($values) use ($request) { + return ltrim($values['path'], '/') === $request->path(); + })->first(); + + return view('swagger-ui::index', ['data' => collect($file)]); + } +} diff --git a/src/SwaggerUiServiceProvider.php b/src/SwaggerUiServiceProvider.php index d801e32..f9d892b 100644 --- a/src/SwaggerUiServiceProvider.php +++ b/src/SwaggerUiServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider; use NextApps\SwaggerUi\Console\InstallCommand; use NextApps\SwaggerUi\Http\Controllers\OpenApiJsonController; +use NextApps\SwaggerUi\Http\Controllers\SwaggerViewController; use NextApps\SwaggerUi\Http\Middleware\EnsureUserIsAuthorized; class SwaggerUiServiceProvider extends ServiceProvider @@ -36,12 +37,13 @@ public function register() : void protected function loadRoutes() : void { - Route::middleware(config('swagger-ui.middleware', ['web', EnsureUserIsAuthorized::class])) - ->prefix(config('swagger-ui.path')) - ->group(function () { - Route::view('/', 'swagger-ui::index')->name('swagger-ui'); - - Route::get('openapi.json', OpenApiJsonController::class)->name('swagger-openapi-json'); - }); + collect(config('swagger-ui.files'))->each(function ($values) { + Route::middleware($values['middleware'] ?? ['web', EnsureUserIsAuthorized::class]) + ->group(function () use ($values) { + Route::get(ltrim($values['path'], '/'), SwaggerViewController::class); + + Route::get('{path}/{filename}', OpenApiJsonController::class); + }); + }); } } diff --git a/tests/AuthorizationTest.php b/tests/AuthorizationTest.php index 5bc0e37..445a8c8 100644 --- a/tests/AuthorizationTest.php +++ b/tests/AuthorizationTest.php @@ -1,11 +1,10 @@ set('swagger-ui.file', __DIR__ . '/testfiles/openapi.json'); + config()->set('swagger-ui.files.0.versions', ['v1' => __DIR__ . '/testfiles/openapi.json']); } protected function getPackageProviders($app) : array @@ -25,7 +24,7 @@ protected function getPackageProviders($app) : array public function it_denies_access_in_default_installation() { $this->get('swagger')->assertStatus(403); - $this->get('swagger/openapi.json')->assertStatus(403); + $this->get('swagger/v1')->assertStatus(403); } /** @test */ @@ -34,7 +33,7 @@ public function it_denies_access_in_default_installation_for_any_auth_user() $this->actingAs(new Authenticated()); $this->get('swagger')->assertStatus(403); - $this->get('swagger/openapi.json')->assertStatus(403); + $this->get('swagger/v1')->assertStatus(403); } /** @test */ @@ -43,7 +42,7 @@ public function it_denies_access_for_guests() Gate::define('viewSwaggerUI', fn () => true); $this->get('swagger')->assertStatus(403); - $this->get('swagger/openapi.json')->assertStatus(403); + $this->get('swagger/v1')->assertStatus(403); } /** @test */ @@ -56,7 +55,7 @@ public function it_allows_access_to_user_if_allowed_by_gate() }); $this->get('swagger')->assertStatus(200); - $this->get('swagger/openapi.json')->assertStatus(200); + $this->get('swagger/v1')->assertStatus(200); } /** @test */ @@ -67,7 +66,7 @@ public function it_denies_access_to_user_if_not_allowed_by_gate() Gate::define('viewSwaggerUI', fn () => false); $this->get('swagger')->assertStatus(403); - $this->get('swagger/openapi.json')->assertStatus(403); + $this->get('swagger/v1')->assertStatus(403); } /** @test */ @@ -76,7 +75,7 @@ public function it_allows_access_to_guest_if_allowed_by_gate() Gate::define('viewSwaggerUI', fn (?Authenticated $user) => true); $this->get('swagger')->assertStatus(200); - $this->get('swagger/openapi.json')->assertStatus(200); + $this->get('swagger/v1')->assertStatus(200); } } diff --git a/tests/OpenApiRouteTest.php b/tests/OpenApiRouteTest.php index 1ad2ceb..a5b9ddb 100644 --- a/tests/OpenApiRouteTest.php +++ b/tests/OpenApiRouteTest.php @@ -1,11 +1,10 @@ set('swagger-ui.file', __DIR__ . '/testfiles/openapi.json'); + config()->set('swagger-ui.files.0.versions', ['v1' => __DIR__ . '/testfiles/openapi.json']); Gate::define('viewSwaggerUI', fn (?Authenticatable $user) => true); } @@ -28,6 +27,10 @@ public function openApiFileProvider() : array return [ 'json file' => [__DIR__ . '/testfiles/openapi.json'], 'yaml file' => [__DIR__ . '/testfiles/openapi.yaml'], + 'multiple versions' => [ + 'v1' => __DIR__ . '/testfiles/openapi.json', + 'v2' => __DIR__ . '/testfiles/openapi.yaml', + ] ]; } @@ -38,11 +41,11 @@ public function openApiFileProvider() : array */ public function it_sets_server_to_current_app_url_if_modify_file_is_enabled($openApiFile) { - config()->set('swagger-ui.file', $openApiFile); - config()->set('swagger-ui.modify_file', true); + config()->set('swagger-ui.files.0.versions', ['v1' => $openApiFile]); + config()->set('swagger-ui.files.0.modify_file', true); config()->set('app.url', 'http://foo.bar'); - $this->get('swagger/openapi.json') + $this->get('swagger/v1') ->assertStatus(200) ->assertJsonCount(1, 'servers') ->assertJsonPath('servers.0.url', 'http://foo.bar'); @@ -55,13 +58,11 @@ public function it_sets_server_to_current_app_url_if_modify_file_is_enabled($ope */ public function it_sets_oauth_urls_by_combining_configured_paths_with_current_app_url_if_modify_file_is_enabled($openApiFile) { - config()->set('swagger-ui.file', $openApiFile); - config()->set('swagger-ui.modify_file', true); - config()->set('swagger-ui.oauth.token_path', 'this-is-token-path'); - config()->set('swagger-ui.oauth.refresh_path', 'this-is-refresh-path'); - config()->set('swagger-ui.oauth.authorization_path', 'this-is-authorization-path'); + config()->set('swagger-ui.files.0.versions', ['v1' => $openApiFile]); + config()->set('swagger-ui.files.0.modify_file', true); + config()->set('swagger-ui.files.0.oauth', ['token_path' => 'this-is-token-path', 'refresh_path' => 'this-is-refresh-path', 'authorization_path' => 'this-is-authorization-path']); - $this->get('swagger/openapi.json') + $this->get('swagger/v1') ->assertStatus(200) ->assertJsonPath('components.securitySchemes.Foobar.flows.password.tokenUrl', 'http://localhost/this-is-token-path') ->assertJsonPath('components.securitySchemes.Foobar.flows.password.refreshUrl', 'http://localhost/this-is-refresh-path') @@ -77,11 +78,12 @@ public function it_sets_oauth_urls_by_combining_configured_paths_with_current_ap */ public function it_doesnt_sets_server_to_current_app_url_if_modify_file_is_disabled($openApiFile) { - config()->set('swagger-ui.file', $openApiFile); - config()->set('swagger-ui.modify_file', false); + config()->set('swagger-ui.files.0.versions', ['v1' => $openApiFile]); + config()->set('swagger-ui.files.0.modify_file', false); + config()->set('app.url', 'http://foo.bar'); - $this->get('swagger/openapi.json') + $this->get('swagger/v1') ->assertStatus(200) ->assertJsonCount(1, 'servers') ->assertJsonPath('servers.0.url', 'http://localhost:3000'); @@ -94,13 +96,11 @@ public function it_doesnt_sets_server_to_current_app_url_if_modify_file_is_disab */ public function it_doesnt_sets_oauth_urls_by_combining_configured_paths_with_current_app_url_if_modify_file_is_disabled($openApiFile) { - config()->set('swagger-ui.file', $openApiFile); - config()->set('swagger-ui.modify_file', false); - config()->set('swagger-ui.oauth.token_path', 'this-is-token-path'); - config()->set('swagger-ui.oauth.refresh_path', 'this-is-refresh-path'); - config()->set('swagger-ui.oauth.authorization_path', 'this-is-authorization-path'); + config()->set('swagger-ui.files.0.versions', ['v1' => $openApiFile]); + config()->set('swagger-ui.files.0.modify_file', false); + config()->set('swagger-ui.files.0.oauth', ['token_path' => 'this-is-token-path', 'refresh_path' => 'this-is-refresh-path', 'authorization_path' => 'this-is-authorization-path']); - $this->get('swagger/openapi.json') + $this->get('swagger/v1') ->assertStatus(200) ->assertJsonPath('components.securitySchemes.Foobar.flows.password.tokenUrl', 'http://localhost:3000/password/tokenUrl') ->assertJsonPath('components.securitySchemes.Foobar.flows.password.refreshUrl', 'http://localhost:3000/password/refreshUrl') diff --git a/tests/SwaggerUiRouteTest.php b/tests/SwaggerUiRouteTest.php index 10281a9..ffee71a 100644 --- a/tests/SwaggerUiRouteTest.php +++ b/tests/SwaggerUiRouteTest.php @@ -1,11 +1,10 @@ set('swagger-ui.file', __DIR__ . '/testfiles/openapi.json'); + config()->set('swagger-ui.files.0.versions', ['v1' => __DIR__ . '/testfiles/openapi.json']); + config()->set('swagger-ui.files.0.oauth', ['client_id' => 1, 'client_secret' => 'foobar']); Gate::define('viewSwaggerUI', fn (?Authenticatable $user) => true); } @@ -26,23 +26,34 @@ protected function getPackageProviders($app) : array /** @test */ public function it_provides_openapi_route_as_url() { - config()->set('swagger-ui.oauth.client_id', 1); - config()->set('swagger-ui.oauth.client_secret', 'foobar'); - $this->get('swagger') ->assertStatus(200) - ->assertSee('url: \'' . route('swagger-openapi-json', [], false) . '\'', false); + ->assertSee('url: \'' . 'swagger/v1' . '\'', false); } /** @test */ public function it_fills_oauth_client_id_and_secret_from_config() { - config()->set('swagger-ui.oauth.client_id', 1); - config()->set('swagger-ui.oauth.client_secret', 'foobar'); - $this->get('swagger') ->assertStatus(200) ->assertSee('clientId: \'1\',', false) ->assertSee('clientSecret: \'foobar\',', false); } + + /** @test */ + public function it_allows_multiple_routes() + { + $this->get('swagger2') + ->assertStatus(200) + ->assertSee('url: \'' . 'swagger2/v1' . '\'', false); + } + + /** @test */ + public function it_supports_multiple_verions() + { + $this->get('swagger-with-versions') + ->assertStatus(200) + ->assertSee('url: \'' . 'swagger-with-versions/v1' . '\'', false) + ->assertSee('url: \'' . 'swagger-with-versions/v2' . '\'', false); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..b9364db --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,22 @@ +set('swagger-ui.files.1', config('swagger-ui.files.0')); + $app['config']->set('swagger-ui.files.1.oauth', ['client_id' => 1, 'client_secret' => 'foobar']); + $app['config']->set('swagger-ui.files.1.path', 'swagger2'); + + $app['config']->set('swagger-ui.files.2', config('swagger-ui.files.0')); + $app['config']->set('swagger-ui.files.2.versions', [ + 'v1' => __DIR__ . '/testfiles/openapi.json', + 'v2' => __DIR__ . '/testfiles/openapi-v2.json' + ]); + $app['config']->set('swagger-ui.files.2.path', 'swagger-with-versions'); + } +} From 0695294ed56cca4e03a90bc88bd8912f45ce617b Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Wed, 22 Mar 2023 14:49:04 +0100 Subject: [PATCH 2/7] Fix linting --- tests/OpenApiRouteTest.php | 2 +- tests/SwaggerUiRouteTest.php | 2 +- tests/TestCase.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/OpenApiRouteTest.php b/tests/OpenApiRouteTest.php index a5b9ddb..92523c8 100644 --- a/tests/OpenApiRouteTest.php +++ b/tests/OpenApiRouteTest.php @@ -30,7 +30,7 @@ public function openApiFileProvider() : array 'multiple versions' => [ 'v1' => __DIR__ . '/testfiles/openapi.json', 'v2' => __DIR__ . '/testfiles/openapi.yaml', - ] + ], ]; } diff --git a/tests/SwaggerUiRouteTest.php b/tests/SwaggerUiRouteTest.php index ffee71a..1eea693 100644 --- a/tests/SwaggerUiRouteTest.php +++ b/tests/SwaggerUiRouteTest.php @@ -2,8 +2,8 @@ namespace NextApps\SwaggerUi\Tests; -use Illuminate\Support\Facades\Gate; use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Support\Facades\Gate; use NextApps\SwaggerUi\SwaggerUiServiceProvider; class SwaggerUiRouteTest extends TestCase diff --git a/tests/TestCase.php b/tests/TestCase.php index b9364db..4501a0f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,7 +15,7 @@ protected function defineEnvironment($app) $app['config']->set('swagger-ui.files.2', config('swagger-ui.files.0')); $app['config']->set('swagger-ui.files.2.versions', [ 'v1' => __DIR__ . '/testfiles/openapi.json', - 'v2' => __DIR__ . '/testfiles/openapi-v2.json' + 'v2' => __DIR__ . '/testfiles/openapi-v2.json', ]); $app['config']->set('swagger-ui.files.2.path', 'swagger-with-versions'); } From 22219a7d1723bfdc4d6b0cafa52db2725598b55d Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Wed, 22 Mar 2023 14:53:38 +0100 Subject: [PATCH 3/7] drop EOL php and laravel support --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0208491..26a34db 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,9 @@ } ], "require": { - "php": "^7.4|^8.0|^8.1|^8.2", + "php": "^8.1|^8.2", "ext-json": "*", - "laravel/framework": "^8.0|^9.0|^10.0" + "laravel/framework": "^9.0|^10.0" }, "suggest": { "ext-yaml": "*" From 0f330e9eb08fbe4cf3a8292e9fda6b1bfa5fee3b Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Wed, 22 Mar 2023 14:54:50 +0100 Subject: [PATCH 4/7] update github action to drop support --- .github/workflows/run-tests.yml | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e0f975b..c903bd7 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,27 +10,13 @@ jobs: strategy: fail-fast: false matrix: - php: [7.4, 8.0, 8.1, 8.2] - laravel: [8.*, 9.*, 10.*] + php: [8.1, 8.2] + laravel: [9.*, 10.*] dependency-version: [prefer-lowest, prefer-stable] exclude: - php: 8.2 laravel: 9.* dependency-version: prefer-lowest - - php: 8.2 - laravel: 8.* - - php: 8.1 - laravel: 8.* - dependency-version: prefer-lowest - - php: 8.0 - laravel: 10.* - - php: 8.0 - laravel: 8.* - dependency-version: prefer-lowest - - php: 7.4 - laravel: 9.* - - php: 7.4 - laravel: 10.* name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }} From ea64882e800bbf8a992e6f5238b73ae28be23646 Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Thu, 23 Mar 2023 10:08:07 +0100 Subject: [PATCH 5/7] Fix openapi route middleware --- composer.json | 1 + config/swagger-ui.php | 10 +++++----- .../Controllers/OpenApiJsonController.php | 11 +++++------ .../Controllers/SwaggerViewController.php | 2 +- src/SwaggerUiServiceProvider.php | 7 +++---- tests/OpenApiRouteTest.php | 4 ---- tests/SwaggerUiRouteTest.php | 19 ++++++++++--------- tests/TestCase.php | 12 ++++++------ 8 files changed, 31 insertions(+), 35 deletions(-) diff --git a/composer.json b/composer.json index 26a34db..3504cb1 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "require-dev": { "adamwojs/php-cs-fixer-phpdoc-force-fqcn": "^2.0", "friendsofphp/php-cs-fixer": "^3.0", + "jasonmccreary/laravel-test-assertions": "^2.3", "orchestra/testbench": "^6.0|^7.0|^8.0", "phpunit/phpunit": "^9.1", "squizlabs/php_codesniffer": "^3.6" diff --git a/config/swagger-ui.php b/config/swagger-ui.php index 3759544..f00cfa0 100644 --- a/config/swagger-ui.php +++ b/config/swagger-ui.php @@ -30,6 +30,11 @@ EnsureUserIsAuthorized::class, ], + /* + * If enabled the file will be modified to set the server url and oauth urls. + */ + 'modify_file' => true, + /* * The oauth configuration for the swagger file. */ @@ -41,11 +46,6 @@ 'client_id' => env('SWAGGER_UI_OAUTH_CLIENT_ID'), 'client_secret' => env('SWAGGER_UI_OAUTH_CLIENT_SECRET'), ], - - /* - * If enabled the file will be modified to set the server url and oauth urls. - */ - 'modify_file' => true, ], ], ]; diff --git a/src/Http/Controllers/OpenApiJsonController.php b/src/Http/Controllers/OpenApiJsonController.php index 58a07b5..eb4faf9 100644 --- a/src/Http/Controllers/OpenApiJsonController.php +++ b/src/Http/Controllers/OpenApiJsonController.php @@ -3,20 +3,19 @@ namespace NextApps\SwaggerUi\Http\Controllers; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Str; use RuntimeException; class OpenApiJsonController { - public function __invoke(string $path, string $filename) : JsonResponse + public function __invoke(Request $request, string $filename) : JsonResponse { + $path = $request->segment(1); + $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($filename, $path) { return isset($values['versions'][$filename]) && ltrim($values['path'], '/') === $path; - })->first(); - - if (empty($file)) { - abort(404); - } + })->firstOrFail(); $json = $this->getJson($file['versions'][$filename]); diff --git a/src/Http/Controllers/SwaggerViewController.php b/src/Http/Controllers/SwaggerViewController.php index 05ecd83..333af9a 100644 --- a/src/Http/Controllers/SwaggerViewController.php +++ b/src/Http/Controllers/SwaggerViewController.php @@ -10,7 +10,7 @@ public function __invoke(Request $request) { $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($request) { return ltrim($values['path'], '/') === $request->path(); - })->first(); + })->firstOrFail(); return view('swagger-ui::index', ['data' => collect($file)]); } diff --git a/src/SwaggerUiServiceProvider.php b/src/SwaggerUiServiceProvider.php index f9d892b..4380269 100644 --- a/src/SwaggerUiServiceProvider.php +++ b/src/SwaggerUiServiceProvider.php @@ -7,7 +7,6 @@ use NextApps\SwaggerUi\Console\InstallCommand; use NextApps\SwaggerUi\Http\Controllers\OpenApiJsonController; use NextApps\SwaggerUi\Http\Controllers\SwaggerViewController; -use NextApps\SwaggerUi\Http\Middleware\EnsureUserIsAuthorized; class SwaggerUiServiceProvider extends ServiceProvider { @@ -38,11 +37,11 @@ public function register() : void protected function loadRoutes() : void { collect(config('swagger-ui.files'))->each(function ($values) { - Route::middleware($values['middleware'] ?? ['web', EnsureUserIsAuthorized::class]) + Route::middleware($values['middleware']) ->group(function () use ($values) { - Route::get(ltrim($values['path'], '/'), SwaggerViewController::class); + Route::get($values['path'], SwaggerViewController::class)->name($values['path'] . '.index'); - Route::get('{path}/{filename}', OpenApiJsonController::class); + Route::get($values['path'] . '/{filename}', OpenApiJsonController::class)->name($values['path'] . '.json'); }); }); } diff --git a/tests/OpenApiRouteTest.php b/tests/OpenApiRouteTest.php index 92523c8..ec8d4e6 100644 --- a/tests/OpenApiRouteTest.php +++ b/tests/OpenApiRouteTest.php @@ -27,10 +27,6 @@ public function openApiFileProvider() : array return [ 'json file' => [__DIR__ . '/testfiles/openapi.json'], 'yaml file' => [__DIR__ . '/testfiles/openapi.yaml'], - 'multiple versions' => [ - 'v1' => __DIR__ . '/testfiles/openapi.json', - 'v2' => __DIR__ . '/testfiles/openapi.yaml', - ], ]; } diff --git a/tests/SwaggerUiRouteTest.php b/tests/SwaggerUiRouteTest.php index 1eea693..a035f7a 100644 --- a/tests/SwaggerUiRouteTest.php +++ b/tests/SwaggerUiRouteTest.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Facades\Gate; +use NextApps\SwaggerUi\Http\Middleware\EnsureUserIsAuthorized; use NextApps\SwaggerUi\SwaggerUiServiceProvider; class SwaggerUiRouteTest extends TestCase @@ -28,7 +29,7 @@ public function it_provides_openapi_route_as_url() { $this->get('swagger') ->assertStatus(200) - ->assertSee('url: \'' . 'swagger/v1' . '\'', false); + ->assertSee('url: \'swagger/v1\'', false); } /** @test */ @@ -41,19 +42,19 @@ public function it_fills_oauth_client_id_and_secret_from_config() } /** @test */ - public function it_allows_multiple_routes() + public function it_supports_multiple_verions() { - $this->get('swagger2') + $this->get('swagger-with-versions') ->assertStatus(200) - ->assertSee('url: \'' . 'swagger2/v1' . '\'', false); + ->assertSee('url: \'swagger-with-versions/v1\'', false) + ->assertSee('url: \'swagger-with-versions/v2\'', false); } /** @test */ - public function it_supports_multiple_verions() + public function it_applies_middleware_from_config() { - $this->get('swagger-with-versions') - ->assertStatus(200) - ->assertSee('url: \'' . 'swagger-with-versions/v1' . '\'', false) - ->assertSee('url: \'' . 'swagger-with-versions/v2' . '\'', false); + $this->assertRouteUsesMiddleware('swagger.index', ['web', EnsureUserIsAuthorized::class]); + + $this->assertRouteUsesMiddleware('swagger-with-versions.index', ['web']); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 4501a0f..6ac2378 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,21 +2,21 @@ namespace NextApps\SwaggerUi\Tests; +use JMac\Testing\Traits\AdditionalAssertions; use Orchestra\Testbench\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { + use AdditionalAssertions; + protected function defineEnvironment($app) { $app['config']->set('swagger-ui.files.1', config('swagger-ui.files.0')); - $app['config']->set('swagger-ui.files.1.oauth', ['client_id' => 1, 'client_secret' => 'foobar']); - $app['config']->set('swagger-ui.files.1.path', 'swagger2'); - - $app['config']->set('swagger-ui.files.2', config('swagger-ui.files.0')); - $app['config']->set('swagger-ui.files.2.versions', [ + $app['config']->set('swagger-ui.files.1.versions', [ 'v1' => __DIR__ . '/testfiles/openapi.json', 'v2' => __DIR__ . '/testfiles/openapi-v2.json', ]); - $app['config']->set('swagger-ui.files.2.path', 'swagger-with-versions'); + $app['config']->set('swagger-ui.files.1.path', 'swagger-with-versions'); + $app['config']->set('swagger-ui.files.1.middleware', ['web']); } } From a1a17792e1d870b34f66d3994f55329e03da22d4 Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Tue, 28 Mar 2023 13:28:47 +0200 Subject: [PATCH 6/7] add try catch + extra tests --- .../Controllers/OpenApiJsonController.php | 18 ++++++++++++---- .../Controllers/SwaggerViewController.php | 11 +++++++--- tests/OpenApiRouteTest.php | 21 +++++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Http/Controllers/OpenApiJsonController.php b/src/Http/Controllers/OpenApiJsonController.php index eb4faf9..1664c95 100644 --- a/src/Http/Controllers/OpenApiJsonController.php +++ b/src/Http/Controllers/OpenApiJsonController.php @@ -2,8 +2,10 @@ namespace NextApps\SwaggerUi\Http\Controllers; +use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\ItemNotFoundException; use Illuminate\Support\Str; use RuntimeException; @@ -13,9 +15,13 @@ public function __invoke(Request $request, string $filename) : JsonResponse { $path = $request->segment(1); - $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($filename, $path) { - return isset($values['versions'][$filename]) && ltrim($values['path'], '/') === $path; - })->firstOrFail(); + try { + $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($filename, $path) { + return isset($values['versions'][$filename]) && ltrim($values['path'], '/') === $path; + })->firstOrFail(); + } catch (ItemNotFoundException $e) { + return abort(404); + } $json = $this->getJson($file['versions'][$filename]); @@ -27,7 +33,11 @@ public function __invoke(Request $request, string $filename) : JsonResponse protected function getJson(string $path) : array { - $content = file_get_contents($path); + try { + $content = file_get_contents($path); + } catch (Exception $e) { + throw new RuntimeException('OpenAPI file can not be read'); + } if (Str::endsWith($path, '.yaml')) { if (! extension_loaded('yaml')) { diff --git a/src/Http/Controllers/SwaggerViewController.php b/src/Http/Controllers/SwaggerViewController.php index 333af9a..c5b79c4 100644 --- a/src/Http/Controllers/SwaggerViewController.php +++ b/src/Http/Controllers/SwaggerViewController.php @@ -3,14 +3,19 @@ namespace NextApps\SwaggerUi\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\ItemNotFoundException; class SwaggerViewController { public function __invoke(Request $request) { - $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($request) { - return ltrim($values['path'], '/') === $request->path(); - })->firstOrFail(); + try { + $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($request) { + return ltrim($values['path'], '/') === $request->path(); + })->firstOrFail(); + } catch (ItemNotFoundException $e) { + return response()->json(['error' => 'File not found'], 404); + } return view('swagger-ui::index', ['data' => collect($file)]); } diff --git a/tests/OpenApiRouteTest.php b/tests/OpenApiRouteTest.php index ec8d4e6..f88f8e3 100644 --- a/tests/OpenApiRouteTest.php +++ b/tests/OpenApiRouteTest.php @@ -104,4 +104,25 @@ public function it_doesnt_sets_oauth_urls_by_combining_configured_paths_with_cur ->assertJsonPath('components.securitySchemes.Foobar.flows.authorizationCode.tokenUrl', 'http://localhost:3000/authorizationCode/tokenUrl') ->assertJsonPath('components.securitySchemes.Foobar.flows.authorizationCode.refreshUrl', 'http://localhost:3000/authorizationCode/refreshUrl'); } + + /** @test */ + public function it_returns_not_found_response_if_provided_version_does_not_exist_in_file() + { + $this->getJson('swagger/v4') + ->assertStatus(404); + } + + /** @test */ + public function it_returns_not_found_response_if_provided_file_does_not_exist() + { + $this->getJson('foo-bar/v1') + ->assertStatus(404); + } + + /** @test */ + public function it_returns_not_found_response_if_provided_route_does_not_exist() + { + $this->getJson('foo-bar') + ->assertStatus(404); + } } From b580322170716c6cc800b130d252d6932606e129 Mon Sep 17 00:00:00 2001 From: Philippe Damen Date: Thu, 30 Mar 2023 10:02:49 +0200 Subject: [PATCH 7/7] removed obsolete property in try catch + fix test name --- src/Http/Controllers/OpenApiJsonController.php | 2 +- src/Http/Controllers/SwaggerViewController.php | 4 ++-- tests/OpenApiRouteTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Http/Controllers/OpenApiJsonController.php b/src/Http/Controllers/OpenApiJsonController.php index 1664c95..f14a1d8 100644 --- a/src/Http/Controllers/OpenApiJsonController.php +++ b/src/Http/Controllers/OpenApiJsonController.php @@ -19,7 +19,7 @@ public function __invoke(Request $request, string $filename) : JsonResponse $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($filename, $path) { return isset($values['versions'][$filename]) && ltrim($values['path'], '/') === $path; })->firstOrFail(); - } catch (ItemNotFoundException $e) { + } catch (ItemNotFoundException) { return abort(404); } diff --git a/src/Http/Controllers/SwaggerViewController.php b/src/Http/Controllers/SwaggerViewController.php index c5b79c4..e06c240 100644 --- a/src/Http/Controllers/SwaggerViewController.php +++ b/src/Http/Controllers/SwaggerViewController.php @@ -13,8 +13,8 @@ public function __invoke(Request $request) $file = collect(config('swagger-ui.files'))->filter(function ($values) use ($request) { return ltrim($values['path'], '/') === $request->path(); })->firstOrFail(); - } catch (ItemNotFoundException $e) { - return response()->json(['error' => 'File not found'], 404); + } catch (ItemNotFoundException) { + return abort(404); } return view('swagger-ui::index', ['data' => collect($file)]); diff --git a/tests/OpenApiRouteTest.php b/tests/OpenApiRouteTest.php index f88f8e3..5f769e3 100644 --- a/tests/OpenApiRouteTest.php +++ b/tests/OpenApiRouteTest.php @@ -113,7 +113,7 @@ public function it_returns_not_found_response_if_provided_version_does_not_exist } /** @test */ - public function it_returns_not_found_response_if_provided_file_does_not_exist() + public function it_returns_not_found_response_if_provided_file_does_not_exist_even_when_provided_version_exists() { $this->getJson('foo-bar/v1') ->assertStatus(404);