Skip to content

Commit 626594a

Browse files
committed
Merge tag 'v2.0.1' into develop
Use HTTP status from validation exception.
2 parents dc68b2d + b17451a commit 626594a

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

Diff for: CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
All notable changes to this project will be documented in this file. This project adheres to
44
[Semantic Versioning](http://semver.org/) and [this changelog format](http://keepachangelog.com/).
55

6+
## [2.0.1] - 2023-07-29
7+
8+
### Fixed
9+
10+
- [#3](https://github.com/laravel-json-api/exceptions/issues/3) Ensure HTTP status is correctly set when the status on a
11+
validation exception is not `422`.
12+
613
## [2.0.0] - 2023-02-14
714

815
### Changed

Diff for: src/Pipes/Concerns/SetsHttpTitle.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@
2424

2525
trait SetsHttpTitle
2626
{
27-
2827
/**
29-
* @var Translator
28+
* @var Translator|null
3029
*/
31-
private Translator $translator;
30+
private ?Translator $translator = null;
3231

3332
/**
3433
* @param int|null $status
@@ -37,7 +36,8 @@ trait SetsHttpTitle
3736
private function getTitle(?int $status): ?string
3837
{
3938
if ($status && isset(Response::$statusTexts[$status])) {
40-
return $this->translator->get(Response::$statusTexts[$status]);
39+
$title = Response::$statusTexts[$status];
40+
return $this->translator?->get($title) ?? $title;
4141
}
4242

4343
return null;

Diff for: src/Pipes/ValidationExceptionHandler.php

+48-12
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,33 @@
2020
namespace LaravelJsonApi\Exceptions\Pipes;
2121

2222
use Closure;
23+
use Illuminate\Contracts\Translation\Translator;
2324
use Illuminate\Validation\ValidationException;
2425
use LaravelJsonApi\Contracts\ErrorProvider;
26+
use LaravelJsonApi\Core\Document\Error;
27+
use LaravelJsonApi\Core\Document\ErrorList;
2528
use LaravelJsonApi\Core\Responses\ErrorResponse;
29+
use LaravelJsonApi\Exceptions\Pipes\Concerns\SetsHttpTitle;
2630
use LaravelJsonApi\Validation\Factory;
31+
use Symfony\Component\HttpFoundation\Response;
2732
use Throwable;
2833

2934
class ValidationExceptionHandler
3035
{
31-
32-
/**
33-
* @var Factory
34-
*/
35-
private Factory $factory;
36+
use SetsHttpTitle;
3637

3738
/**
3839
* ValidationExceptionHandler constructor.
3940
*
4041
* @param Factory $factory
42+
* @param Translator|null $translator
43+
* @TODO next major version, make translator compulsory.
4144
*/
42-
public function __construct(Factory $factory)
43-
{
44-
$this->factory = $factory;
45+
public function __construct(
46+
private readonly Factory $factory,
47+
Translator $translator = null,
48+
) {
49+
$this->translator = $translator;
4550
}
4651

4752
/**
@@ -55,7 +60,7 @@ public function handle(Throwable $ex, Closure $next): ErrorResponse
5560
{
5661
if ($ex instanceof ValidationException) {
5762
return new ErrorResponse(
58-
$this->toErrors($ex)
63+
$this->toErrors($ex),
5964
);
6065
}
6166

@@ -64,12 +69,43 @@ public function handle(Throwable $ex, Closure $next): ErrorResponse
6469

6570
/**
6671
* @param ValidationException $ex
67-
* @return ErrorProvider
72+
* @return ErrorProvider|ErrorList
6873
*/
69-
private function toErrors(ValidationException $ex): ErrorProvider
74+
private function toErrors(ValidationException $ex): ErrorProvider|ErrorList
7075
{
71-
return $this->factory->createErrors(
76+
$errors = $this->factory->createErrors(
7277
$ex->validator
7378
);
79+
80+
if (Response::HTTP_UNPROCESSABLE_ENTITY !== $ex->status) {
81+
$errors = $errors->toErrors();
82+
$this->withStatus($ex->status, $errors);
83+
}
84+
85+
return $errors;
86+
}
87+
88+
/**
89+
* Override the status and title of the provided error list.
90+
*
91+
* As the validation exception can have a custom HTTP status, we sometimes need to override
92+
* the HTTP status and title on each JSON:API error.
93+
*
94+
* This could be improved by allowing the status and title to be overridden on the
95+
* `ValidatorErrorIterator` class (in the validation package).
96+
*
97+
* @param int $status
98+
* @param ErrorList $errors
99+
* @return void
100+
*/
101+
private function withStatus(int $status, ErrorList $errors): void
102+
{
103+
$title = $this->getTitle($status);
104+
105+
/** @var Error $error */
106+
foreach ($errors as $error) {
107+
/** This works as error is mutable; note a future version might make error immutable. */
108+
$error->setStatus($status)->setTitle($title);
109+
}
74110
}
75111
}

Diff for: tests/Integration/ExceptionsTest.php

+62
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,68 @@ public function testValidationException(): void
455455
->assertExactJson($expected);
456456
}
457457

458+
/**
459+
* If the validator has a status set, we ensure the response and JSON:API errors have that status.
460+
*
461+
* @see https://github.com/laravel-json-api/exceptions/issues/3
462+
*/
463+
public function testValidationExceptionWithStatus(): void
464+
{
465+
$this->ex = ValidationException::withMessages([
466+
'data.email' => 'Hello [email protected]',
467+
])->status(418);
468+
469+
$expected = [
470+
'errors' => [
471+
[
472+
'detail' => 'Hello [email protected]',
473+
'source' => ['pointer' => '/data/email'],
474+
'status' => '418',
475+
'title' => "I'm a teapot",
476+
],
477+
],
478+
'jsonapi' => [
479+
'version' => '1.0',
480+
],
481+
];
482+
483+
$this
484+
->get('/test', ['Accept' => 'application/vnd.api+json'])
485+
->assertStatus(418)
486+
->assertHeader('Content-Type', 'application/vnd.api+json')
487+
->assertExactJson($expected);
488+
}
489+
490+
/**
491+
* If the validator has a status set, we ensure the response and JSON:API errors have that status.
492+
*
493+
* @see https://github.com/laravel-json-api/exceptions/issues/3
494+
*/
495+
public function testValidationExceptionWithStatusThatDoesNotHaveTitle(): void
496+
{
497+
$this->ex = ValidationException::withMessages([
498+
'data.email' => 'Too many attempts',
499+
])->status(419);
500+
501+
$expected = [
502+
'errors' => [
503+
[
504+
'detail' => 'Too many attempts',
505+
'source' => ['pointer' => '/data/email'],
506+
'status' => '419',
507+
],
508+
],
509+
'jsonapi' => [
510+
'version' => '1.0',
511+
],
512+
];
513+
514+
$this->get('/test', ['Accept' => 'application/vnd.api+json'])
515+
->assertStatus(419)
516+
->assertHeader('Content-Type', 'application/vnd.api+json')
517+
->assertExactJson($expected);
518+
}
519+
458520
public function testDefaultExceptionWithoutDebug(): void
459521
{
460522
config()->set('app.debug', false);

0 commit comments

Comments
 (0)