Skip to content

Commit d4a219d

Browse files
authored
feat(router): handle ConvertsToResponse outside of exception handler (#1320)
1 parent 82dadaf commit d4a219d

File tree

7 files changed

+50
-13
lines changed

7 files changed

+50
-13
lines changed

packages/router/src/Exceptions/HttpExceptionHandler.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public function handle(Throwable $throwable): void
3232

3333
$response = match (true) {
3434
$throwable instanceof ConvertsToResponse => $throwable->toResponse(),
35-
$throwable instanceof RouteBindingFailed => $this->renderErrorResponse(Status::NOT_FOUND),
3635
$throwable instanceof HttpRequestFailed => $this->renderErrorResponse($throwable->status, $throwable),
3736
default => $this->renderErrorResponse(Status::INTERNAL_SERVER_ERROR),
3837
};

packages/router/src/HandleRouteExceptionMiddleware.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Tempest\Http\Response;
99
use Tempest\Http\Responses\Invalid;
1010
use Tempest\Http\Responses\NotFound;
11+
use Tempest\Router\Exceptions\ConvertsToResponse;
1112
use Tempest\Router\Exceptions\RouteBindingFailed;
1213
use Tempest\Validation\Exceptions\ValidationFailed;
1314

@@ -21,7 +22,7 @@ public function __construct(
2122
public function __invoke(Request $request, HttpMiddlewareCallable $next): Response
2223
{
2324
if ($this->routeConfig->throwHttpExceptions === true) {
24-
$response = $next($request);
25+
$response = $this->forward($request, $next);
2526

2627
if ($response->status->isServerError() || $response->status->isClientError()) {
2728
throw new HttpRequestFailed(
@@ -33,8 +34,15 @@ public function __invoke(Request $request, HttpMiddlewareCallable $next): Respon
3334
return $response;
3435
}
3536

37+
return $this->forward($request, $next);
38+
}
39+
40+
private function forward(Request $request, HttpMiddlewareCallable $next): Response
41+
{
3642
try {
3743
return $next($request);
44+
} catch (ConvertsToResponse $convertsToResponse) {
45+
return $convertsToResponse->toResponse();
3846
} catch (RouteBindingFailed) {
3947
return new NotFound();
4048
} catch (ValidationFailed $validationException) {

src/Tempest/Framework/Testing/Http/TestResponseHelper.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,10 @@ public function assertNotSee(string $search): self
218218

219219
return $this;
220220
}
221+
222+
public function dd(): void
223+
{
224+
// @phpstan-ignore disallowed.function
225+
dd($this->response); // @mago-expect best-practices/no-debug-symbols
226+
}
221227
}

tests/Integration/Http/HttpExceptionHandlerTest.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,6 @@ public function test_exception_handler_returns_500_by_default(): void
115115
$this->assertStringContainsString('An unexpected server error occurred', $this->render($this->response->body));
116116
}
117117

118-
public function test_exception_handler_returns_404_for_router_not_found_execption(): void
119-
{
120-
$this->callExceptionHandler(function (): void {
121-
$handler = $this->container->get(HttpExceptionHandler::class);
122-
$handler->handle(new RouteBindingFailed());
123-
});
124-
125-
$this->assertSame(Status::NOT_FOUND, $this->response->status);
126-
$this->assertStringContainsString('This page could not be found on the server', $this->render($this->response->body));
127-
}
128-
129118
#[TestWith([Status::BAD_REQUEST])]
130119
#[TestWith([Status::INTERNAL_SERVER_ERROR])]
131120
#[TestWith([Status::NOT_FOUND])]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Route\Fixtures;
4+
5+
use Exception;
6+
use Tempest\Http\Response;
7+
use Tempest\Http\Responses\Redirect;
8+
use Tempest\Router\Exceptions\ConvertsToResponse;
9+
10+
/**
11+
* Used by RouterTest.
12+
*/
13+
final class ExceptionThatConvertsToRedirectResponse extends Exception implements ConvertsToResponse
14+
{
15+
public function toResponse(): Response
16+
{
17+
return new Redirect('https://tempestphp.com');
18+
}
19+
}

tests/Integration/Route/Fixtures/Http500Controller.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,10 @@ public function serverErrorWithBody(): ServerError
2525
{
2626
return new ServerError('custom error');
2727
}
28+
29+
#[Get('/returns-converts-to-response')]
30+
public function convertsToResponse(): ServerError
31+
{
32+
throw new ExceptionThatConvertsToRedirectResponse();
33+
}
2834
}

tests/Integration/Route/RouterTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,4 +324,14 @@ public function test_error_response_processor_does_not_throw_http_exceptions_if_
324324
->assertStatus(Status::INTERNAL_SERVER_ERROR)
325325
->assertSee('custom error');
326326
}
327+
328+
public function test_converts_to_response(): void
329+
{
330+
$this->registerRoute([Http500Controller::class, 'convertsToResponse']);
331+
332+
$this->http
333+
->get('/returns-converts-to-response')
334+
->assertStatus(Status::FOUND)
335+
->assertHeaderContains('Location', 'https://tempestphp.com');
336+
}
327337
}

0 commit comments

Comments
 (0)