Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ public function register(): void
/** @var ConfigRepository */
$config = $app['config'];

return new SwaggerUiProvider($app->make(ReadProvider::class), $app->make(OpenApiFactoryInterface::class), $config->get('api-platform.swagger_ui.enabled', false));
return new SwaggerUiProvider($app->make(ReadProvider::class), $app->make(OpenApiFactoryInterface::class), $config->get('api-platform.swagger_ui.enabled', false), $config->get('api-platform.scalar.enabled', false));
});

$this->app->singleton(DeserializeProvider::class, static function (Application $app) {
Expand Down Expand Up @@ -746,6 +746,8 @@ public function register(): void
oauthClientId: $config->get('api-platform.swagger_ui.oauth.clientId'),
oauthClientSecret: $config->get('api-platform.swagger_ui.oauth.clientSecret'),
oauthPkce: $config->get('api-platform.swagger_ui.oauth.pkce', false),
scalarEnabled: $config->get('api-platform.scalar.enabled', false),
scalarExtraConfiguration: $config->get('api-platform.scalar.extra_configuration', []),
);
});

Expand All @@ -759,7 +761,7 @@ public function register(): void
/** @var ConfigRepository */
$config = $app['config'];

return new DocumentationController($app->make(ResourceNameCollectionFactoryInterface::class), $config->get('api-platform.title') ?? '', $config->get('api-platform.description') ?? '', $config->get('api-platform.version') ?? '', $app->make(OpenApiFactoryInterface::class), $app->make(ProviderInterface::class), $app->make(ProcessorInterface::class), $app->make(Negotiator::class), $config->get('api-platform.docs_formats'), $config->get('api-platform.swagger_ui.enabled', false));
return new DocumentationController($app->make(ResourceNameCollectionFactoryInterface::class), $config->get('api-platform.title') ?? '', $config->get('api-platform.description') ?? '', $config->get('api-platform.version') ?? '', $app->make(OpenApiFactoryInterface::class), $app->make(ProviderInterface::class), $app->make(ProcessorInterface::class), $app->make(Negotiator::class), $config->get('api-platform.docs_formats'), $config->get('api-platform.swagger_ui.enabled', false), $config->get('api-platform.scalar.enabled', false));
});

$this->app->singleton(EntrypointController::class, static function (Application $app) {
Expand Down
3 changes: 2 additions & 1 deletion src/Laravel/Controller/DocumentationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function __construct(
?Negotiator $negotiator = null,
private readonly array $documentationFormats = [OpenApiNormalizer::JSON_FORMAT => ['application/vnd.openapi+json'], OpenApiNormalizer::FORMAT => ['application/json']],
private readonly bool $swaggerUiEnabled = true,
private readonly bool $scalarEnabled = true,
) {
$this->negotiator = $negotiator ?? new Negotiator();
}
Expand Down Expand Up @@ -94,7 +95,7 @@ class: OpenApi::class,
outputFormats: $this->documentationFormats
);

if ('html' === $format && $this->swaggerUiEnabled) {
if ('html' === $format && ($this->swaggerUiEnabled || $this->scalarEnabled)) {
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true);
}

Expand Down
7 changes: 6 additions & 1 deletion src/Laravel/State/SwaggerUiProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ final class SwaggerUiProcessor implements ProcessorInterface

/**
* @param array<string, string[]> $formats
* @param array<string, mixed> $scalarExtraConfiguration
*/
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
Expand All @@ -43,6 +44,8 @@ public function __construct(
private readonly ?string $oauthClientId = null,
private readonly ?string $oauthClientSecret = null,
private readonly bool $oauthPkce = false,
private readonly bool $scalarEnabled = false,
private readonly array $scalarExtraConfiguration = [],
) {
}

Expand Down Expand Up @@ -92,7 +95,9 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable
$status = $requestedOperation->getStatus() ?? $status;
}

return new Response(view('api-platform::swagger-ui', $swaggerContext + ['swagger_data' => $swaggerData]), 200);
$swaggerData['scalarExtraConfiguration'] = $this->scalarExtraConfiguration;

return new Response(view('api-platform::swagger-ui', $swaggerContext + ['swagger_data' => $swaggerData, 'scalar_enabled' => $this->scalarEnabled]), 200);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Laravel/State/SwaggerUiProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function __construct(
private readonly ProviderInterface $decorated,
private readonly OpenApiFactoryInterface $openApiFactory,
private readonly bool $swaggerUiEnabled = true,
private readonly bool $scalarEnabled = false,
) {
}

Expand All @@ -52,7 +53,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
!($operation instanceof HttpOperation)
|| !($request = $context['request'] ?? null)
|| 'html' !== $request->getRequestFormat()
|| !$this->swaggerUiEnabled
|| (!$this->swaggerUiEnabled && !$this->scalarEnabled)
|| true === ($operation->getExtraProperties()['_api_disable_swagger_provider'] ?? false)
) {
return $this->decorated->provide($operation, $uriVariables, $context);
Expand Down
5 changes: 5 additions & 0 deletions src/Laravel/config/api-platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
AuthorizationException::class => 403,
],

'scalar' => [
'enabled' => true,
'extra_configuration' => [],
],

'swagger_ui' => [
'enabled' => true,
// 'apiKeys' => [
Expand Down
11 changes: 8 additions & 3 deletions src/Laravel/resources/views/swagger-ui.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,13 @@
@endif

<div id="swagger-ui" class="api-platform"></div>
<script src="/vendor/api-platform/swagger-ui/swagger-ui-bundle.js"></script>
<script src="/vendor/api-platform/swagger-ui/swagger-ui-standalone-preset.js"></script>
<script src="/vendor/api-platform/init-swagger-ui.js"></script>
@if (($scalar_enabled ?? false) && request()->query('ui') === 'scalar')
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
<script src="/vendor/api-platform/init-scalar-ui.js"></script>
@else
<script src="/vendor/api-platform/swagger-ui/swagger-ui-bundle.js"></script>
<script src="/vendor/api-platform/swagger-ui/swagger-ui-standalone-preset.js"></script>
<script src="/vendor/api-platform/init-swagger-ui.js"></script>
@endif
</body>
</html>
7 changes: 4 additions & 3 deletions src/Symfony/Action/DocumentationAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function __construct(
private readonly bool $swaggerUiEnabled = true,
private readonly bool $docsEnabled = true,
private readonly bool $reDocEnabled = true,
private readonly bool $scalarEnabled = true,
) {
$this->negotiator = $negotiator ?? new Negotiator();
}
Expand Down Expand Up @@ -91,8 +92,8 @@ public function __invoke(?Request $request = null)
*/
private function getOpenApiDocumentation(array $context, string $format, Request $request): OpenApi|Response
{
if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled) {
throw new NotFoundHttpException('Swagger UI and ReDoc are disabled.');
if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled && !$this->scalarEnabled) {
throw new NotFoundHttpException('Swagger UI, ReDoc and Scalar are disabled.');
}

if ($this->provider && $this->processor) {
Expand All @@ -105,7 +106,7 @@ class: OpenApi::class,
outputFormats: $this->documentationFormats
);

if ('html' === $format && ($this->swaggerUiEnabled || $this->reDocEnabled)) {
if ('html' === $format && ($this->swaggerUiEnabled || $this->reDocEnabled || $this->scalarEnabled)) {
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public function load(array $configs, ContainerBuilder $container): void
// to prevent HTML documentation from being served on resource endpoints.
$config['enable_swagger_ui'] = false;
$config['enable_re_doc'] = false;
$config['enable_scalar'] = false;
}
$jsonSchemaFormats = $config['jsonschema_formats'];

Expand Down Expand Up @@ -647,6 +648,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
if (!$config['enable_swagger']) {
$container->setParameter('api_platform.enable_swagger_ui', false);
$container->setParameter('api_platform.enable_re_doc', false);
$container->setParameter('api_platform.enable_scalar', false);

return;
}
Expand All @@ -657,7 +659,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
$loader->load('openapi/yaml.php');
}

if ($config['enable_swagger_ui'] || $config['enable_re_doc']) {
if ($config['enable_swagger_ui'] || $config['enable_re_doc'] || $config['enable_scalar']) {
$loader->load('swagger_ui.php');

if ($config['use_symfony_listeners']) {
Expand All @@ -667,20 +669,22 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
$loader->load('state/swagger_ui.php');
}

if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
if (!$config['enable_swagger_ui'] && !$config['enable_re_doc'] && !$config['enable_scalar']) {
// Remove the listener but keep the controller to allow customizing the path of the UI
$container->removeDefinition('api_platform.swagger.listener.ui');
}

$container->setParameter('api_platform.enable_swagger_ui', $config['enable_swagger_ui']);
$container->setParameter('api_platform.enable_re_doc', $config['enable_re_doc']);
$container->setParameter('api_platform.enable_scalar', $config['enable_scalar']);
$container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']);
$container->setParameter('api_platform.swagger.persist_authorization', $config['swagger']['persist_authorization']);
$container->setParameter('api_platform.swagger.http_auth', $config['swagger']['http_auth']);
if ($config['openapi']['swagger_ui_extra_configuration'] && $config['swagger']['swagger_ui_extra_configuration']) {
throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.');
}
$container->setParameter('api_platform.swagger_ui.extra_configuration', $config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']);
$container->setParameter('api_platform.scalar.extra_configuration', $config['openapi']['scalar_extra_configuration']);
}

private function registerJsonApiConfiguration(ContainerBuilder $container, array $formats, PhpFileLoader $loader, array $config): void
Expand Down
9 changes: 9 additions & 0 deletions src/Symfony/Bundle/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->booleanNode('enable_json_streamer')->defaultValue(class_exists(ControllerHelper::class) && class_exists(JsonStreamWriter::class))->info('Enable json streamer.')->end()
->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger UI')->end()
->booleanNode('enable_re_doc')->defaultValue(class_exists(TwigBundle::class))->info('Enable ReDoc')->end()
->booleanNode('enable_scalar')->defaultValue(class_exists(TwigBundle::class))->info('Enable Scalar API Reference')->end()
->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end()
->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end()
->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end()
Expand Down Expand Up @@ -590,6 +591,14 @@ private function addOpenApiSection(ArrayNodeDefinition $rootNode): void
->end()
->info('To pass extra configuration to Swagger UI, like docExpansion or filter.')
->end()
->variableNode('scalar_extra_configuration')
->defaultValue([])
->validate()
->ifTrue(static fn ($v): bool => false === \is_array($v))
->thenInvalid('The scalar_extra_configuration parameter must be an array.')
->end()
->info('To pass extra configuration to Scalar API Reference, like theme or darkMode.')
->end()
->booleanNode('overrideResponses')->defaultTrue()->info('Whether API Platform adds automatic responses to the OpenAPI documentation.')->end()
->scalarNode('error_resource_class')->defaultNull()->info('The class used to represent errors in the OpenAPI documentation.')->end()
->scalarNode('validation_error_resource_class')->defaultNull()->info('The class used to represent validation errors in the OpenAPI documentation.')->end()
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/Resources/config/swagger_ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
'%api_platform.graphql.graphiql.enabled%',
'%api_platform.asset_package%',
'%api_platform.swagger_ui.extra_configuration%',
'%api_platform.enable_scalar%',
'%api_platform.scalar.extra_configuration%',
]);

$services->set('api_platform.swagger_ui.processor', SwaggerUiProcessor::class)
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bundle/Resources/config/symfony/controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@
'%api_platform.enable_swagger_ui%',
'%api_platform.enable_docs%',
'%api_platform.enable_re_doc%',
'%api_platform.enable_scalar%',
]);
};
1 change: 1 addition & 0 deletions src/Symfony/Bundle/Resources/config/symfony/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
'%api_platform.enable_swagger_ui%',
'%api_platform.enable_docs%',
'%api_platform.enable_re_doc%',
'%api_platform.enable_scalar%',
]);

$services->set('api_platform.action.placeholder', PlaceholderAction::class)
Expand Down
12 changes: 12 additions & 0 deletions src/Symfony/Bundle/Resources/public/init-scalar-ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

window.onload = function() {
var data = JSON.parse(document.getElementById('swagger-data').innerText);

var config = Object.assign({
content: data.spec,
theme: 'default',
}, data.scalarExtraConfiguration || {});

Scalar.createApiReference('#swagger-ui', config);
};
29 changes: 22 additions & 7 deletions src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
<head>
{% block head_metas %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% endblock %}

{% block title %}
<title>{% if title %}{{ title }} - {% endif %}API Platform</title>
{% endblock %}

{% set active_ui = app.request.query.get('ui', 'swagger_ui') %}
{% set is_scalar = (scalarEnabled and not swaggerUiEnabled and not reDocEnabled) or (scalarEnabled and 'scalar' == active_ui) %}

{% block stylesheet %}
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/fonts/open-sans/400.css', assetPackage) }}">
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/fonts/open-sans/700.css', assetPackage) }}">
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/swagger-ui/swagger-ui.css', assetPackage) }}">
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/style.css', assetPackage) }}">
{% if not is_scalar %}
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/fonts/open-sans/400.css', assetPackage) }}">
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/fonts/open-sans/700.css', assetPackage) }}">
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/swagger-ui/swagger-ui.css', assetPackage) }}">
<link rel="stylesheet" href="{{ asset('bundles/apiplatform/style.css', assetPackage) }}">
{% endif %}
{% endblock %}

{% set oauth_data = {'oauth': swagger_data.oauth|merge({'redirectUrl' : absolute_url(asset('bundles/apiplatform/swagger-ui/oauth2-redirect.html', assetPackage)) })} %}
Expand All @@ -25,6 +31,7 @@
</head>

<body>
{% if not is_scalar %}
<svg xmlns="http://www.w3.org/2000/svg" class="svg-icons">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
Expand Down Expand Up @@ -69,9 +76,11 @@
<div class="web"><img src="{{ asset('bundles/apiplatform/web.png', assetPackage) }}"></div>
<div class="webby"><img src="{{ asset('bundles/apiplatform/webby.png', assetPackage) }}"></div>
{% endif %}
{% endif %}

<div id="swagger-ui" class="api-platform"></div>

{% if not is_scalar %}
<div class="swagger-ui" id="formats">
<div class="information-container wrapper">
<div class="info">
Expand All @@ -81,24 +90,30 @@
{% endfor %}
<br>
Other API docs:
{% set active_ui = app.request.query.get('ui', 'swagger_ui') %}
{% if swaggerUiEnabled and active_ui != 'swagger_ui' %}<a href="{{ path(originalRoute, originalRouteParams) }}">Swagger UI</a>{% endif %}
{% if reDocEnabled and active_ui != 're_doc' %}<a href="{{ path(originalRoute, originalRouteParams|merge({'ui': 're_doc'})) }}">ReDoc</a>{% endif %}
{% if scalarEnabled and active_ui != 'scalar' %}<a href="{{ path(originalRoute, originalRouteParams|merge({'ui': 'scalar'})) }}">Scalar</a>{% endif %}
{% if not graphQlEnabled or graphiQlEnabled %}<a {% if graphiQlEnabled %}href="{{ path('api_graphql_graphiql') }}"{% endif %} class="graphiql-link">GraphiQL</a>{% endif %}
</div>
</div>
</div>
{% endif %}

{% block javascript %}
{% if (reDocEnabled and not swaggerUiEnabled) or (reDocEnabled and 're_doc' == active_ui) %}
{% if is_scalar %}
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
<script src="{{ asset('bundles/apiplatform/init-scalar-ui.js', assetPackage) }}"></script>
{% elseif (reDocEnabled and not swaggerUiEnabled) or (reDocEnabled and 're_doc' == active_ui) %}
<script src="{{ asset('bundles/apiplatform/redoc/redoc.standalone.js', assetPackage) }}"></script>
<script src="{{ asset('bundles/apiplatform/init-redoc-ui.js', assetPackage) }}"></script>
{% else %}
<script src="{{ asset('bundles/apiplatform/swagger-ui/swagger-ui-bundle.js', assetPackage) }}"></script>
<script src="{{ asset('bundles/apiplatform/swagger-ui/swagger-ui-standalone-preset.js', assetPackage) }}"></script>
<script src="{{ asset('bundles/apiplatform/init-swagger-ui.js', assetPackage) }}"></script>
{% endif %}
<script src="{{ asset('bundles/apiplatform/init-common-ui.js', assetPackage) }}" defer></script>
{% if not is_scalar %}
<script src="{{ asset('bundles/apiplatform/init-common-ui.js', assetPackage) }}" defer></script>
{% endif %}
{% endblock %}

</body>
Expand Down
12 changes: 11 additions & 1 deletion src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class SwaggerUiContext
/**
* @param string|null $assetPackage
*/
public function __construct(private readonly bool $swaggerUiEnabled = false, private readonly bool $showWebby = true, private readonly bool $reDocEnabled = false, private readonly bool $graphQlEnabled = false, private readonly bool $graphiQlEnabled = false, private $assetPackage = null, private readonly array $extraConfiguration = [])
public function __construct(private readonly bool $swaggerUiEnabled = false, private readonly bool $showWebby = true, private readonly bool $reDocEnabled = false, private readonly bool $graphQlEnabled = false, private readonly bool $graphiQlEnabled = false, private $assetPackage = null, private readonly array $extraConfiguration = [], private readonly bool $scalarEnabled = false, private readonly array $scalarExtraConfiguration = [])
{
}

Expand Down Expand Up @@ -56,4 +56,14 @@ public function getExtraConfiguration(): array
{
return $this->extraConfiguration;
}

public function isScalarEnabled(): bool
{
return $this->scalarEnabled;
}

public function getScalarExtraConfiguration(): array
{
return $this->scalarExtraConfiguration;
}
}
Loading
Loading