-
-
Notifications
You must be signed in to change notification settings - Fork 895
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(laravel): docs _format and open swagger ui
- Loading branch information
Showing
13 changed files
with
288 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the API Platform project. | ||
* | ||
* (c) Kévin Dunglas <dunglas@gmail.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ApiPlatform\Laravel\Controller; | ||
|
||
use ApiPlatform\Documentation\Documentation; | ||
use ApiPlatform\Metadata\Get; | ||
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; | ||
use ApiPlatform\Metadata\Util\ContentNegotiationTrait; | ||
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface; | ||
use ApiPlatform\OpenApi\OpenApi; | ||
use ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer; | ||
use ApiPlatform\OpenApi\Serializer\LegacyOpenApiNormalizer; | ||
use ApiPlatform\OpenApi\Serializer\OpenApiNormalizer; | ||
use ApiPlatform\State\ProcessorInterface; | ||
use ApiPlatform\State\ProviderInterface; | ||
use Negotiation\Negotiator; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
/** | ||
* Generates the API documentation. | ||
* | ||
* @author Amrouche Hamza <hamza.simperfit@gmail.com> | ||
*/ | ||
final class DocumentationController | ||
{ | ||
use ContentNegotiationTrait; | ||
|
||
/** | ||
* @param array<string, string[]> $documentationFormats | ||
* @param ProviderInterface<object> $provider | ||
* @param ProcessorInterface<mixed, Response> $processor | ||
*/ | ||
public function __construct( | ||
private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, | ||
private readonly string $title = '', | ||
private readonly string $description = '', | ||
private readonly string $version = '', | ||
private readonly ?OpenApiFactoryInterface $openApiFactory = null, | ||
private readonly ?ProviderInterface $provider = null, | ||
private readonly ?ProcessorInterface $processor = null, | ||
?Negotiator $negotiator = null, | ||
private readonly array $documentationFormats = [OpenApiNormalizer::JSON_FORMAT => ['application/vnd.openapi+json'], OpenApiNormalizer::FORMAT => ['application/json']], | ||
private readonly bool $swaggerUiEnabled = true, | ||
) { | ||
$this->negotiator = $negotiator ?? new Negotiator(); | ||
} | ||
|
||
public function __invoke(Request $request): Response | ||
{ | ||
$context = [ | ||
'api_gateway' => $request->query->getBoolean(ApiGatewayNormalizer::API_GATEWAY), | ||
'base_url' => $request->getBaseUrl(), | ||
'spec_version' => (string) $request->query->get(LegacyOpenApiNormalizer::SPEC_VERSION), | ||
]; | ||
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context); | ||
// We want to find the format early on, this code is also executed later on by the ContentNegotiationProvider. | ||
$this->addRequestFormats($request, $this->documentationFormats); | ||
$format = $this->getRequestFormat($request, $this->documentationFormats); | ||
|
||
if ('html' === $format || OpenApiNormalizer::FORMAT === $format || OpenApiNormalizer::JSON_FORMAT === $format || OpenApiNormalizer::YAML_FORMAT === $format) { | ||
return $this->getOpenApiDocumentation($context, $format, $request); | ||
} | ||
|
||
return $this->getHydraDocumentation($context, $request); | ||
} | ||
|
||
/** | ||
* @param array<string,mixed> $context | ||
*/ | ||
private function getOpenApiDocumentation(array $context, string $format, Request $request): Response | ||
{ | ||
$context['request'] = $request; | ||
$operation = new Get( | ||
class: OpenApi::class, | ||
read: true, | ||
serialize: true, | ||
provider: fn () => $this->openApiFactory->__invoke($context), | ||
normalizationContext: [ | ||
ApiGatewayNormalizer::API_GATEWAY => $context['api_gateway'] ?? null, | ||
LegacyOpenApiNormalizer::SPEC_VERSION => $context['spec_version'] ?? null, | ||
], | ||
outputFormats: $this->documentationFormats | ||
); | ||
|
||
if ('html' === $format && $this->swaggerUiEnabled) { | ||
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true); | ||
} | ||
|
||
return $this->processor->process($this->provider->provide($operation, [], $context), $operation, [], $context); | ||
} | ||
|
||
/** | ||
* TODO: the logic behind the Hydra Documentation is done in a ApiPlatform\Hydra\Serializer\DocumentationNormalizer. | ||
* We should transform this to a provider, it'd improve performances also by a bit. | ||
* | ||
* @param array<string,mixed> $context | ||
*/ | ||
private function getHydraDocumentation(array $context, Request $request): Response | ||
{ | ||
$context['request'] = $request; | ||
$operation = new Get( | ||
class: Documentation::class, | ||
read: true, | ||
serialize: true, | ||
provider: fn () => new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version) | ||
); | ||
|
||
return $this->processor->process($this->provider->provide($operation, [], $context), $operation, [], $context); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the API Platform project. | ||
* | ||
* (c) Kévin Dunglas <dunglas@gmail.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ApiPlatform\Laravel\Controller; | ||
|
||
use ApiPlatform\Documentation\Entrypoint; | ||
use ApiPlatform\Metadata\Get; | ||
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; | ||
use ApiPlatform\Metadata\Resource\ResourceNameCollection; | ||
use ApiPlatform\OpenApi\Serializer\LegacyOpenApiNormalizer; | ||
use ApiPlatform\State\ProcessorInterface; | ||
use ApiPlatform\State\ProviderInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
/** | ||
* Generates the API entrypoint. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
final class EntrypointController | ||
{ | ||
private static ResourceNameCollection $resourceNameCollection; | ||
|
||
/** | ||
* @param array<string, string[]> $documentationFormats | ||
* @param ProviderInterface<object> $provider | ||
* @param ProcessorInterface<mixed, Response> $processor | ||
*/ | ||
public function __construct( | ||
private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, | ||
private readonly ProviderInterface $provider, | ||
private readonly ProcessorInterface $processor, | ||
private readonly array $documentationFormats = [], | ||
) { | ||
} | ||
|
||
public function __invoke(Request $request): Response | ||
{ | ||
self::$resourceNameCollection = $this->resourceNameCollectionFactory->create(); | ||
$context = [ | ||
'request' => $request, | ||
'spec_version' => (string) $request->query->get(LegacyOpenApiNormalizer::SPEC_VERSION), | ||
]; | ||
$request->attributes->set('_api_platform_disable_listeners', true); | ||
$operation = new Get( | ||
outputFormats: $this->documentationFormats, | ||
read: true, | ||
serialize: true, | ||
class: Entrypoint::class, | ||
provider: [self::class, 'provide'] | ||
); | ||
$request->attributes->set('_api_operation', $operation); | ||
$body = $this->provider->provide($operation, [], $context); | ||
$operation = $request->attributes->get('_api_operation'); | ||
|
||
return $this->processor->process($body, $operation, [], $context); | ||
} | ||
|
||
public static function provide(): Entrypoint | ||
{ | ||
return new Entrypoint(self::$resourceNameCollection); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the API Platform project. | ||
* | ||
* (c) Kévin Dunglas <dunglas@gmail.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ApiPlatform\Laravel\Tests; | ||
|
||
use ApiPlatform\Laravel\Test\ApiTestAssertionsTrait; | ||
use Orchestra\Testbench\Concerns\WithWorkbench; | ||
use Orchestra\Testbench\TestCase; | ||
|
||
class DocsTest extends TestCase | ||
{ | ||
use ApiTestAssertionsTrait; | ||
use WithWorkbench; | ||
|
||
public function testOpenApi(): void | ||
{ | ||
$res = $this->get('/api/docs.jsonopenapi'); | ||
$this->assertArrayHasKey('openapi', $res->json()); | ||
$this->assertSame('application/vnd.openapi+json; charset=utf-8', $res->headers->get('content-type')); | ||
} | ||
|
||
public function testOpenApiAccept(): void | ||
{ | ||
$res = $this->get('/api/docs', headers: ['accept' => 'application/vnd.openapi+json']); | ||
$this->assertArrayHasKey('openapi', $res->json()); | ||
$this->assertSame('application/vnd.openapi+json; charset=utf-8', $res->headers->get('content-type')); | ||
} | ||
|
||
public function testJsonLd(): void | ||
{ | ||
$res = $this->get('/api/docs.jsonld'); | ||
$this->assertArrayHasKey('@context', $res->json()); | ||
$this->assertSame('application/ld+json; charset=utf-8', $res->headers->get('content-type')); | ||
} | ||
|
||
public function testJsonLdAccept(): void | ||
{ | ||
$res = $this->get('/api/docs', headers: ['accept' => 'application/ld+json']); | ||
$this->assertArrayHasKey('@context', $res->json()); | ||
$this->assertSame('application/ld+json; charset=utf-8', $res->headers->get('content-type')); | ||
} | ||
} |
Oops, something went wrong.