Skip to content

Commit 5606442

Browse files
committed
updated sunset mechanism
Signed-off-by: bidi47 <bidi@apidemia.com>
1 parent 5a64329 commit 5606442

2 files changed

Lines changed: 136 additions & 11 deletions

File tree

‎src/App/src/Middleware/DeprecationMiddleware.php‎

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
namespace Api\App\Middleware;
66

77
use Api\App\Attribute\ResourceDeprecation;
8-
use Api\App\Exception\ConflictException;
8+
use Api\App\Exception\ExpiredException;
99
use Api\App\Exception\RuntimeException;
1010
use Api\App\Service\HandlerService;
11+
use DateTimeImmutable;
1112
use Dot\DependencyInjection\Attribute\Inject;
1213
use Psr\Http\Message\ResponseInterface;
1314
use Psr\Http\Message\ServerRequestInterface;
@@ -39,44 +40,72 @@ public function __construct(
3940
}
4041

4142
/**
42-
* @throws ConflictException
43+
* @throws ExpiredException
4344
* @throws ReflectionException
4445
* @throws RuntimeException
4546
*/
4647
public function process(
4748
ServerRequestInterface $request,
4849
RequestHandlerInterface $handler
4950
): ResponseInterface {
50-
$response = $handler->handle($request);
51-
5251
$reflectionHandler = HandlerService::getReflectionHandler($request);
5352
if (! $reflectionHandler instanceof ReflectionClass) {
54-
return $response;
53+
return $handler->handle($request);
5554
}
5655

5756
$attributes = $this->getReflectionAttributes($reflectionHandler);
5857
if (empty($attributes)) {
59-
return $response;
58+
return $handler->handle($request);
6059
}
6160

6261
$attribute = $this->getAttribute($attributes);
6362
if (null === $attribute) {
64-
return $response;
63+
return $handler->handle($request);
64+
}
65+
66+
$documentationUrl = $this->getDocumentationUrl($attribute);
67+
68+
if ($this->isSunsetReached($attribute['sunset'])) {
69+
throw ExpiredException::create(
70+
detail: sprintf('This resource is no longer available (sunset on %s).', $attribute['sunset']),
71+
type: $documentationUrl ?? '',
72+
);
6573
}
6674

75+
$response = $handler->handle($request);
76+
6777
if (! empty($attribute['sunset'])) {
6878
$response = $response->withHeader('sunset', $attribute['sunset']);
6979
}
7080

71-
/** @var non-empty-string $baseUrl */
72-
$baseUrl = $attribute['link'] ?? $this->config['documentation_url'] ?? null;
73-
if (is_string($baseUrl)) {
74-
$response = $response->withHeader('link', $this->formatLink($baseUrl, $attribute));
81+
if (null !== $documentationUrl) {
82+
$response = $response->withHeader('link', $this->formatLink($documentationUrl, $attribute));
7583
}
7684

7785
return $response;
7886
}
7987

88+
/**
89+
* @param array<non-empty-string, mixed> $attribute
90+
* @return non-empty-string|null
91+
*/
92+
private function getDocumentationUrl(array $attribute): ?string
93+
{
94+
/** @var non-empty-string|null $url */
95+
$url = $attribute['link'] ?? $this->config['documentation_url'] ?? null;
96+
97+
return is_string($url) ? $url : null;
98+
}
99+
100+
private function isSunsetReached(?string $sunset): bool
101+
{
102+
if (empty($sunset)) {
103+
return false;
104+
}
105+
106+
return new DateTimeImmutable() >= new DateTimeImmutable($sunset);
107+
}
108+
80109
/**
81110
* @param array<int, mixed> $attributes
82111
* @return array<non-empty-string, mixed>|null

‎test/Unit/App/Middleware/DeprecationMiddlewareTest.php‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
use Api\App\Attribute\ResourceDeprecation;
88
use Api\App\Exception\ConflictException;
9+
use Api\App\Exception\ExpiredException;
910
use Api\App\Middleware\DeprecationMiddleware;
1011
use Fig\Http\Message\RequestMethodInterface;
12+
use Fig\Http\Message\StatusCodeInterface;
1113
use Laminas\Diactoros\Response\EmptyResponse;
1214
use Laminas\Stratigility\MiddlewarePipe;
1315
use Mezzio\Middleware\LazyLoadingMiddleware;
@@ -272,6 +274,100 @@ public function handle(ServerRequestInterface $request): ResponseInterface
272274
$this->assertSame('2038-01-01', $response->getHeader('sunset')[0]);
273275
}
274276

277+
/**
278+
* @throws Exception
279+
* @throws ReflectionException
280+
*/
281+
public function testPastSunsetThrowsExpiredExceptionWithoutRunningHandler(): void
282+
{
283+
$deprecated = new #[ResourceDeprecation(sunset: '2000-01-01', link: 'test-link')]
284+
class implements RequestHandlerInterface {
285+
public function handle(ServerRequestInterface $request): ResponseInterface
286+
{
287+
return new EmptyResponse();
288+
}
289+
};
290+
291+
$this->routeRequestTo($deprecated);
292+
293+
$handler = $this->createMock(RequestHandlerInterface::class);
294+
$handler->expects($this->never())->method('handle');
295+
296+
$this->expectException(ExpiredException::class);
297+
298+
$this->deprecationMiddleware->process($this->request, $handler);
299+
}
300+
301+
/**
302+
* @throws Exception
303+
* @throws ReflectionException
304+
*/
305+
public function testExpiredExceptionCarriesStatusMessageAndLink(): void
306+
{
307+
$deprecated = new #[ResourceDeprecation(sunset: '2000-01-01', link: 'test-link')]
308+
class implements RequestHandlerInterface {
309+
public function handle(ServerRequestInterface $request): ResponseInterface
310+
{
311+
return new EmptyResponse();
312+
}
313+
};
314+
315+
$this->routeRequestTo($deprecated);
316+
317+
try {
318+
$this->deprecationMiddleware->process($this->request, $this->handler);
319+
$this->fail('ExpiredException was not thrown');
320+
} catch (ExpiredException $exception) {
321+
$this->assertSame(StatusCodeInterface::STATUS_GONE, $exception->getStatus());
322+
$this->assertSame('This resource is no longer available (sunset on 2000-01-01).', $exception->getDetail());
323+
$this->assertSame('test-link', $exception->getType());
324+
}
325+
}
326+
327+
/**
328+
* @throws Exception
329+
* @throws ReflectionException
330+
*/
331+
public function testExpiredExceptionFallsBackToConfiguredDocumentationUrl(): void
332+
{
333+
$deprecated = new #[ResourceDeprecation(sunset: '2000-01-01')]
334+
class implements RequestHandlerInterface {
335+
public function handle(ServerRequestInterface $request): ResponseInterface
336+
{
337+
return new EmptyResponse();
338+
}
339+
};
340+
341+
$this->routeRequestTo($deprecated);
342+
343+
try {
344+
$this->deprecationMiddleware->process($this->request, $this->handler);
345+
$this->fail('ExpiredException was not thrown');
346+
} catch (ExpiredException $exception) {
347+
$this->assertSame(self::VERSIONING_CONFIG['documentation_url'], $exception->getType());
348+
}
349+
}
350+
351+
/**
352+
* @throws Exception
353+
*/
354+
private function routeRequestTo(RequestHandlerInterface $deprecated): void
355+
{
356+
$routeResult = $this->createStub(RouteResult::class);
357+
$route = $this->createStub(Route::class);
358+
$lazyLoadingMiddleware = new LazyLoadingMiddleware(
359+
$this->createStub(MiddlewareContainer::class),
360+
$deprecated::class,
361+
);
362+
363+
$route->method('getMiddleware')->willReturn($lazyLoadingMiddleware);
364+
$routeResult->method('isFailure')->willReturn(false);
365+
$routeResult->method('getMatchedRoute')->willReturn($route);
366+
$this->request->method('getAttribute')->willReturn($routeResult);
367+
$this->request->method('getMethod')->willReturn(RequestMethodInterface::METHOD_GET);
368+
$this->handler->method('handle')->willReturn($this->response);
369+
}
370+
275371
/**
276372
* @param non-empty-string $baseLink
277373
* @param array<non-empty-string, mixed> $attribute

0 commit comments

Comments
 (0)