feat(api-docs): document sunset middleware in OpenAPI
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
93cdb62dfe
commit
cba25cf9fc
3 changed files with 153 additions and 0 deletions
110
src/php/src/Api/Documentation/Extensions/SunsetExtension.php
Normal file
110
src/php/src/Api/Documentation/Extensions/SunsetExtension.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Api\Documentation\Extensions;
|
||||
|
||||
use Core\Api\Documentation\Extension;
|
||||
use Illuminate\Routing\Route;
|
||||
|
||||
/**
|
||||
* Sunset Extension.
|
||||
*
|
||||
* Documents endpoint deprecation and sunset metadata for routes using
|
||||
* the `api.sunset` middleware.
|
||||
*/
|
||||
class SunsetExtension implements Extension
|
||||
{
|
||||
/**
|
||||
* Extend the complete OpenAPI specification.
|
||||
*/
|
||||
public function extend(array $spec, array $config): array
|
||||
{
|
||||
$spec['components']['headers'] = $spec['components']['headers'] ?? [];
|
||||
|
||||
$spec['components']['headers']['deprecation'] = [
|
||||
'description' => 'Indicates that the endpoint is deprecated.',
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['true'],
|
||||
],
|
||||
];
|
||||
|
||||
$spec['components']['headers']['sunset'] = [
|
||||
'description' => 'The date and time after which the endpoint will no longer be supported.',
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
],
|
||||
];
|
||||
|
||||
$spec['components']['headers']['link'] = [
|
||||
'description' => 'Reference to the successor endpoint, when one is provided.',
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
|
||||
$spec['components']['headers']['xapiwarn'] = [
|
||||
'description' => 'Human-readable deprecation warning for clients.',
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
|
||||
return $spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend an individual operation.
|
||||
*/
|
||||
public function extendOperation(array $operation, Route $route, string $method, array $config): array
|
||||
{
|
||||
if (! $this->hasSunsetMiddleware($route)) {
|
||||
return $operation;
|
||||
}
|
||||
|
||||
$operation['deprecated'] = true;
|
||||
|
||||
foreach ($operation['responses'] as $status => &$response) {
|
||||
if (! is_numeric($status) || (int) $status < 200 || (int) $status >= 300) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response['headers'] = $response['headers'] ?? [];
|
||||
|
||||
$response['headers']['Deprecation'] = [
|
||||
'$ref' => '#/components/headers/deprecation',
|
||||
];
|
||||
$response['headers']['Sunset'] = [
|
||||
'$ref' => '#/components/headers/sunset',
|
||||
];
|
||||
$response['headers']['X-API-Warn'] = [
|
||||
'$ref' => '#/components/headers/xapiwarn',
|
||||
];
|
||||
|
||||
if (! isset($response['headers']['Link'])) {
|
||||
$response['headers']['Link'] = [
|
||||
'$ref' => '#/components/headers/link',
|
||||
];
|
||||
}
|
||||
}
|
||||
unset($response);
|
||||
|
||||
return $operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the route uses the sunset middleware.
|
||||
*/
|
||||
protected function hasSunsetMiddleware(Route $route): bool
|
||||
{
|
||||
foreach ($route->middleware() as $middleware) {
|
||||
if (str_starts_with($middleware, 'api.sunset') || str_contains($middleware, 'ApiSunset')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ use Core\Api\Documentation\Attributes\ApiSecurity;
|
|||
use Core\Api\Documentation\Attributes\ApiTag;
|
||||
use Core\Api\Documentation\Extensions\ApiKeyAuthExtension;
|
||||
use Core\Api\Documentation\Extensions\RateLimitExtension;
|
||||
use Core\Api\Documentation\Extensions\SunsetExtension;
|
||||
use Core\Api\Documentation\Extensions\WorkspaceHeaderExtension;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Routing\Route;
|
||||
|
|
@ -58,6 +59,7 @@ class OpenApiBuilder
|
|||
$this->extensions = [
|
||||
new WorkspaceHeaderExtension,
|
||||
new RateLimitExtension,
|
||||
new SunsetExtension,
|
||||
new ApiKeyAuthExtension,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Core\Api\Documentation\Attributes\ApiTag;
|
|||
use Core\Api\Documentation\Extension;
|
||||
use Core\Api\Documentation\Extensions\ApiKeyAuthExtension;
|
||||
use Core\Api\Documentation\Extensions\RateLimitExtension;
|
||||
use Core\Api\Documentation\Extensions\SunsetExtension;
|
||||
use Core\Api\Documentation\Extensions\WorkspaceHeaderExtension;
|
||||
use Core\Api\Documentation\OpenApiBuilder;
|
||||
use Core\Api\RateLimit\RateLimit;
|
||||
|
|
@ -786,6 +787,46 @@ describe('Error Response Documentation', function () {
|
|||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Sunset Documentation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Sunset Documentation', function () {
|
||||
it('registers deprecation headers in components', function () {
|
||||
$extension = new SunsetExtension;
|
||||
$spec = ['components' => []];
|
||||
|
||||
$result = $extension->extend($spec, []);
|
||||
|
||||
expect($result['components']['headers'])->toHaveKey('deprecation')
|
||||
->toHaveKey('sunset')
|
||||
->toHaveKey('link')
|
||||
->toHaveKey('xapiwarn');
|
||||
});
|
||||
|
||||
it('marks sunset routes as deprecated and documents their response headers', function () {
|
||||
RouteFacade::prefix('api')
|
||||
->middleware(['api', 'api.sunset:2025-06-01,/api/v2/legacy'])
|
||||
->group(function () {
|
||||
RouteFacade::get('/sunset-test/legacy', fn () => response()->json(['ok' => true]))
|
||||
->name('sunset-test.legacy');
|
||||
});
|
||||
|
||||
config(['api-docs.routes.include' => ['api/*']]);
|
||||
|
||||
$builder = new OpenApiBuilder;
|
||||
$spec = $builder->build();
|
||||
|
||||
$operation = $spec['paths']['/api/sunset-test/legacy']['get'];
|
||||
|
||||
expect($operation['deprecated'])->toBeTrue();
|
||||
expect($operation['responses']['200']['headers'])->toHaveKey('Deprecation')
|
||||
->toHaveKey('Sunset')
|
||||
->toHaveKey('X-API-Warn')
|
||||
->toHaveKey('Link');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Authentication Documentation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue