Skip to content

Commit 94f3c7f

Browse files
soyukaclaude
andauthored
feat(openapi): Scalar API Reference documentation support (#7817)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2682fc5 commit 94f3c7f

File tree

19 files changed

+151
-28
lines changed

19 files changed

+151
-28
lines changed

src/Laravel/ApiPlatformProvider.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ public function register(): void
405405
/** @var ConfigRepository */
406406
$config = $app['config'];
407407

408-
return new SwaggerUiProvider($app->make(ReadProvider::class), $app->make(OpenApiFactoryInterface::class), $config->get('api-platform.swagger_ui.enabled', false));
408+
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));
409409
});
410410

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

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

762-
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));
764+
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));
763765
});
764766

765767
$this->app->singleton(EntrypointController::class, static function (Application $app) {

src/Laravel/Controller/DocumentationController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public function __construct(
5353
?Negotiator $negotiator = null,
5454
private readonly array $documentationFormats = [OpenApiNormalizer::JSON_FORMAT => ['application/vnd.openapi+json'], OpenApiNormalizer::FORMAT => ['application/json']],
5555
private readonly bool $swaggerUiEnabled = true,
56+
private readonly bool $scalarEnabled = true,
5657
) {
5758
$this->negotiator = $negotiator ?? new Negotiator();
5859
}
@@ -94,7 +95,7 @@ class: OpenApi::class,
9495
outputFormats: $this->documentationFormats
9596
);
9697

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

src/Laravel/State/SwaggerUiProcessor.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ final class SwaggerUiProcessor implements ProcessorInterface
3434

3535
/**
3636
* @param array<string, string[]> $formats
37+
* @param array<string, mixed> $scalarExtraConfiguration
3738
*/
3839
public function __construct(
3940
private readonly UrlGeneratorInterface $urlGenerator,
@@ -43,6 +44,8 @@ public function __construct(
4344
private readonly ?string $oauthClientId = null,
4445
private readonly ?string $oauthClientSecret = null,
4546
private readonly bool $oauthPkce = false,
47+
private readonly bool $scalarEnabled = false,
48+
private readonly array $scalarExtraConfiguration = [],
4649
) {
4750
}
4851

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

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

98103
/**

src/Laravel/State/SwaggerUiProvider.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public function __construct(
3838
private readonly ProviderInterface $decorated,
3939
private readonly OpenApiFactoryInterface $openApiFactory,
4040
private readonly bool $swaggerUiEnabled = true,
41+
private readonly bool $scalarEnabled = false,
4142
) {
4243
}
4344

@@ -52,7 +53,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5253
!($operation instanceof HttpOperation)
5354
|| !($request = $context['request'] ?? null)
5455
|| 'html' !== $request->getRequestFormat()
55-
|| !$this->swaggerUiEnabled
56+
|| (!$this->swaggerUiEnabled && !$this->scalarEnabled)
5657
|| true === ($operation->getExtraProperties()['_api_disable_swagger_provider'] ?? false)
5758
) {
5859
return $this->decorated->provide($operation, $uriVariables, $context);

src/Laravel/config/api-platform.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@
9797
AuthorizationException::class => 403,
9898
],
9999

100+
'scalar' => [
101+
'enabled' => true,
102+
'extra_configuration' => [],
103+
],
104+
100105
'swagger_ui' => [
101106
'enabled' => true,
102107
// 'apiKeys' => [

src/Laravel/resources/views/swagger-ui.blade.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,13 @@
213213
@endif
214214

215215
<div id="swagger-ui" class="api-platform"></div>
216-
<script src="/vendor/api-platform/swagger-ui/swagger-ui-bundle.js"></script>
217-
<script src="/vendor/api-platform/swagger-ui/swagger-ui-standalone-preset.js"></script>
218-
<script src="/vendor/api-platform/init-swagger-ui.js"></script>
216+
@if (($scalar_enabled ?? false) && request()->query('ui') === 'scalar')
217+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
218+
<script src="/vendor/api-platform/init-scalar-ui.js"></script>
219+
@else
220+
<script src="/vendor/api-platform/swagger-ui/swagger-ui-bundle.js"></script>
221+
<script src="/vendor/api-platform/swagger-ui/swagger-ui-standalone-preset.js"></script>
222+
<script src="/vendor/api-platform/init-swagger-ui.js"></script>
223+
@endif
219224
</body>
220225
</html>

src/Symfony/Action/DocumentationAction.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public function __construct(
5252
private readonly bool $swaggerUiEnabled = true,
5353
private readonly bool $docsEnabled = true,
5454
private readonly bool $reDocEnabled = true,
55+
private readonly bool $scalarEnabled = true,
5556
) {
5657
$this->negotiator = $negotiator ?? new Negotiator();
5758
}
@@ -91,8 +92,8 @@ public function __invoke(?Request $request = null)
9192
*/
9293
private function getOpenApiDocumentation(array $context, string $format, Request $request): OpenApi|Response
9394
{
94-
if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled) {
95-
throw new NotFoundHttpException('Swagger UI and ReDoc are disabled.');
95+
if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled && !$this->scalarEnabled) {
96+
throw new NotFoundHttpException('Swagger UI, ReDoc and Scalar are disabled.');
9697
}
9798

9899
if ($this->provider && $this->processor) {
@@ -105,7 +106,7 @@ class: OpenApi::class,
105106
outputFormats: $this->documentationFormats
106107
);
107108

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

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public function load(array $configs, ContainerBuilder $container): void
151151
// to prevent HTML documentation from being served on resource endpoints.
152152
$config['enable_swagger_ui'] = false;
153153
$config['enable_re_doc'] = false;
154+
$config['enable_scalar'] = false;
154155
}
155156
$jsonSchemaFormats = $config['jsonschema_formats'];
156157

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

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

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

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

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

675677
$container->setParameter('api_platform.enable_swagger_ui', $config['enable_swagger_ui']);
676678
$container->setParameter('api_platform.enable_re_doc', $config['enable_re_doc']);
679+
$container->setParameter('api_platform.enable_scalar', $config['enable_scalar']);
677680
$container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']);
678681
$container->setParameter('api_platform.swagger.persist_authorization', $config['swagger']['persist_authorization']);
679682
$container->setParameter('api_platform.swagger.http_auth', $config['swagger']['http_auth']);
680683
if ($config['openapi']['swagger_ui_extra_configuration'] && $config['swagger']['swagger_ui_extra_configuration']) {
681684
throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.');
682685
}
683686
$container->setParameter('api_platform.swagger_ui.extra_configuration', $config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']);
687+
$container->setParameter('api_platform.scalar.extra_configuration', $config['openapi']['scalar_extra_configuration']);
684688
}
685689

686690
private function registerJsonApiConfiguration(ContainerBuilder $container, array $formats, PhpFileLoader $loader, array $config): void

src/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ public function getConfigTreeBuilder(): TreeBuilder
124124
->booleanNode('enable_json_streamer')->defaultValue(class_exists(ControllerHelper::class) && class_exists(JsonStreamWriter::class))->info('Enable json streamer.')->end()
125125
->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger UI')->end()
126126
->booleanNode('enable_re_doc')->defaultValue(class_exists(TwigBundle::class))->info('Enable ReDoc')->end()
127+
->booleanNode('enable_scalar')->defaultValue(class_exists(TwigBundle::class))->info('Enable Scalar API Reference')->end()
127128
->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end()
128129
->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end()
129130
->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end()
@@ -590,6 +591,14 @@ private function addOpenApiSection(ArrayNodeDefinition $rootNode): void
590591
->end()
591592
->info('To pass extra configuration to Swagger UI, like docExpansion or filter.')
592593
->end()
594+
->variableNode('scalar_extra_configuration')
595+
->defaultValue([])
596+
->validate()
597+
->ifTrue(static fn ($v): bool => false === \is_array($v))
598+
->thenInvalid('The scalar_extra_configuration parameter must be an array.')
599+
->end()
600+
->info('To pass extra configuration to Scalar API Reference, like theme or darkMode.')
601+
->end()
593602
->booleanNode('overrideResponses')->defaultTrue()->info('Whether API Platform adds automatic responses to the OpenAPI documentation.')->end()
594603
->scalarNode('error_resource_class')->defaultNull()->info('The class used to represent errors in the OpenAPI documentation.')->end()
595604
->scalarNode('validation_error_resource_class')->defaultNull()->info('The class used to represent validation errors in the OpenAPI documentation.')->end()

src/Symfony/Bundle/Resources/config/swagger_ui.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
'%api_platform.graphql.graphiql.enabled%',
2929
'%api_platform.asset_package%',
3030
'%api_platform.swagger_ui.extra_configuration%',
31+
'%api_platform.enable_scalar%',
32+
'%api_platform.scalar.extra_configuration%',
3133
]);
3234

3335
$services->set('api_platform.swagger_ui.processor', SwaggerUiProcessor::class)

0 commit comments

Comments
 (0)