From 6fb63e7db6caf35170f3efe488c6a851d66d4ee2 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 21 Jun 2026 08:44:25 +0900 Subject: [PATCH 1/3] Handle JsonSchemaRequestException as 400 Bad Request in ThrowableHandler Raise the minimum bear/resource requirement to ^1.33 which introduces JsonSchemaRequestException / JsonSchemaResponseException. In ThrowableHandler, normalise JsonSchemaRequestException into BadRequestException (code 400) so VndError emits the correct 4xx status instead of falling through to 500. JsonSchemaResponseException (server-side schema mismatch) is unchanged and continues to produce a 500. Co-Authored-By: Claude Sonnet 4.6 --- composer.json | 2 +- src/Provide/Error/ThrowableHandler.php | 6 ++++++ tests/Provide/Error/ThrowableHandlerTest.php | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2d2aad21..fdf7f1e9 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "require": { "php": "^8.2", - "bear/resource": "^1.16", + "bear/resource": "^1.33", "psr/log": "^1.1 || ^2.0 || ^3.0", "ray/aop": "^2.12.3", "ray/di": "^2.13", diff --git a/src/Provide/Error/ThrowableHandler.php b/src/Provide/Error/ThrowableHandler.php index 51dcaf05..e244bf21 100644 --- a/src/Provide/Error/ThrowableHandler.php +++ b/src/Provide/Error/ThrowableHandler.php @@ -4,6 +4,8 @@ namespace BEAR\Sunday\Provide\Error; +use BEAR\Resource\Exception\BadRequestException; +use BEAR\Resource\Exception\JsonSchemaRequestException; use BEAR\Sunday\Extension\Error\ErrorInterface; use BEAR\Sunday\Extension\Error\ThrowableHandlerInterface; use BEAR\Sunday\Extension\Router\RouterMatch as Request; @@ -29,6 +31,10 @@ public function handle(Throwable $e, Request $request): ThrowableHandlerInterfac $e = new ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine(), $e); } + if ($e instanceof JsonSchemaRequestException) { + $e = new BadRequestException($e->getMessage(), $e->getCode(), $e); + } + /** @var Exception $e */ $this->error->handle($e, $request); diff --git a/tests/Provide/Error/ThrowableHandlerTest.php b/tests/Provide/Error/ThrowableHandlerTest.php index 505b6c4a..47c719f5 100644 --- a/tests/Provide/Error/ThrowableHandlerTest.php +++ b/tests/Provide/Error/ThrowableHandlerTest.php @@ -4,6 +4,8 @@ namespace BEAR\Sunday\Provide\Error; +use BEAR\Resource\Exception\JsonSchemaRequestException; +use BEAR\Resource\Exception\JsonSchemaResponseException; use BEAR\Resource\Exception\ResourceNotFoundException; use BEAR\Sunday\Extension\Router\RouterMatch; use BEAR\Sunday\Provide\Transfer\ConditionalResponse; @@ -36,6 +38,22 @@ public function testException(): void $this->assertSame('{"message":"Not Found"}', FakeHttpResponder::$body); } + public function testJsonSchemaRequestException(): void + { + $e = new JsonSchemaRequestException('title is required'); + $this->throableHandler->handle($e, new RouterMatch())->transfer(); + $this->assertSame(400, FakeHttpResponder::$code); + $this->assertSame([['Content-Type: application/vnd.error+json', false]], FakeHttpResponder::$headers); + $this->assertSame('{"message":"Bad Request"}', FakeHttpResponder::$body); + } + + public function testJsonSchemaResponseException(): void + { + $e = new JsonSchemaResponseException('response does not match schema'); + $this->throableHandler->handle($e, new RouterMatch())->transfer(); + $this->assertSame(500, FakeHttpResponder::$code); + } + public function testError(): void { $e = null; From 5d8a6a2845ac646f0c537e295799a24497457b38 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 23 Aug 2026 08:43:04 +0900 Subject: [PATCH 2/3] Drop stale @phpstan-ignore-line on catch --- tests/Provide/Error/ThrowableHandlerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Provide/Error/ThrowableHandlerTest.php b/tests/Provide/Error/ThrowableHandlerTest.php index 47c719f5..fb6645ac 100644 --- a/tests/Provide/Error/ThrowableHandlerTest.php +++ b/tests/Provide/Error/ThrowableHandlerTest.php @@ -59,7 +59,7 @@ public function testError(): void $e = null; try { echo HELLO; // @phpstan-ignore-line - } catch (Throwable $e) { // @phpstan-ignore-line create $e + } catch (Throwable $e) { } $this->throableHandler->handle($e, new RouterMatch())->transfer(); // @phpstan-ignore-line From 7632ccd4d06642c55ecea8562376bf49bffe3fc6 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 23 Aug 2026 09:01:31 +0900 Subject: [PATCH 3/3] Add 4xx code propagation test and assert response body --- tests/Provide/Error/ThrowableHandlerTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Provide/Error/ThrowableHandlerTest.php b/tests/Provide/Error/ThrowableHandlerTest.php index fb6645ac..ae4b03cf 100644 --- a/tests/Provide/Error/ThrowableHandlerTest.php +++ b/tests/Provide/Error/ThrowableHandlerTest.php @@ -4,6 +4,7 @@ namespace BEAR\Sunday\Provide\Error; +use BEAR\Resource\Code; use BEAR\Resource\Exception\JsonSchemaRequestException; use BEAR\Resource\Exception\JsonSchemaResponseException; use BEAR\Resource\Exception\ResourceNotFoundException; @@ -47,11 +48,22 @@ public function testJsonSchemaRequestException(): void $this->assertSame('{"message":"Bad Request"}', FakeHttpResponder::$body); } + public function testJsonSchemaRequestExceptionWithCustomCode(): void + { + $e = new JsonSchemaRequestException('forbidden', Code::FORBIDDEN); + $this->throableHandler->handle($e, new RouterMatch())->transfer(); + $this->assertSame(403, FakeHttpResponder::$code); + $this->assertSame([['Content-Type: application/vnd.error+json', false]], FakeHttpResponder::$headers); + $this->assertSame('{"message":"Forbidden"}', FakeHttpResponder::$body); + } + public function testJsonSchemaResponseException(): void { $e = new JsonSchemaResponseException('response does not match schema'); $this->throableHandler->handle($e, new RouterMatch())->transfer(); $this->assertSame(500, FakeHttpResponder::$code); + $this->assertSame([['Content-Type: application/vnd.error+json', false]], FakeHttpResponder::$headers); + $this->assertSame('{"message":"500 Server Error"}', FakeHttpResponder::$body); } public function testError(): void