8000 [Feature] Add support JSON Exception class · CodingSeo/laravel-json-api@c303c51 · GitHub
[go: up one dir, main page]

Skip to content

Commit c303c51

Browse files
committed
[Feature] Add support JSON Exception class
Closes cloudcreativity#538
1 parent 0acf13e commit c303c51

File tree

6 files changed

+215
-20
lines changed

6 files changed

+215
-20
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
All notable changes to this project will be documented in this file. This project adheres to
33
[Semantic Versioning](http://semver.org/) and [this changelog format](http://keepachangelog.com/).
44

5+
## Unreleased
6+
7+
### Added
8+
- [#538](https://github.com/cloudcreativity/laravel-json-api/issues/538)
9+
New JSON API exception class that accepts the new error objects from this package.
10+
It is recommended that you use `CloudCreativity\LaravelJsonApi\Exceptions\JsonApiException`
11+
combined with the `CloudCreativity\LaravelJsonApi\Document\Error\Error`. It is not
12+
recommended to use the `Neomerx\JsonApi\Exceptions\JsonApiException` class as support
13+
for this exception class will be removed in a future version.
14+
515
## [2.0.0] - 2020-06-17
616

717
### Added

docs/features/errors.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ how to return your own error responses.
1111

1212
## Creating Error Objects
1313

14-
Error objects can be constructed from array key/value pairs using the static `create` method on
14+
Error objects can be constructed from array key/value pairs using the static `fromArray` method on
1515
the package's error class. All the keys described in the specification's
1616
[error objects](http://jsonapi.org/format/#error-objects) chapter are supported.
1717

1818
For example:
1919

2020
```php
21-
use CloudCreativity\LaravelJsonApi\Document\Error;
21+
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
2222

23-
$error = Error::create([
23+
$error = Error::fromArray([
2424
'id' => '91053382-7c00-45eb-bdcc-8359d03debbb',
2525
'status' => '500',
2626
'code' => 'unexpected',
@@ -40,15 +40,15 @@ The `JsonApiController` has a responses factory that can create error responses.
4040
to return an error response in a controller hook, as demonstrated in the following example:
4141

4242
```php
43-
use CloudCreativity\LaravelJsonApi\Document\Error;
43+
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
4444

4545
class PaymentController extends JsonApiController
4646
{
4747

4848
protected function creating()
4949
{
5050
if (/** some condition */) {
51-
return $this->reply()->errors(Error::create([
51+
return $this->reply()->errors(Error::fromArray([
5252
'title' => 'Payment Required',
5353
'detail' => 'Your card has expired.',
5454
'status' => '402',
@@ -84,24 +84,36 @@ It is also possible to throw a `JsonApiException` from anywhere in your code. Th
8484
to a JSON API response. For example:
8585

8686
```php
87-
use Neomerx\JsonApi\Exceptions\JsonApiException;
88-
use CloudCreativity\LaravelJsonApi\Document\Error;
87+
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
88+
use CloudCreativity\LaravelJsonApi\Exceptions\JsonApiException;
8989

9090
try {
9191
dispatchNow(new ChargeCard($token));
9292
} catch (\App\PaymentException $ex) {
93-
$error = Error::create([
93+
$error = Error::fromArray([
9494
'title' => 'Payment Required',
9595
'detail' => $ex->getMessage(),
9696
'status' => '402',
9797
]);
9898

99-
throw new JsonApiException($error, 402, $ex);
99+
throw new JsonApiException($error, $ex);
100100
}
101101
```
102102

103103
The JSON API exception takes three arguments:
104104

105105
- An error object or an array of error objects.
106-
- The HTTP status code.
107-
- The previous exception.
106+
- The previous exception (optional)
107+
- Additional headers for the response (optional).
108+
109+
You can also fluently construct a JSON API exception with headers:
110+
111+
```php
112+
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
113+
use CloudCreativity\LaravelJsonApi\Exceptions\JsonApiException;
114+
115+
throw JsonApiException::make(Error::fromArray([
116+
'status' => '418',
117+
'title' => "I'm a Teapot"
118+
]))->withHeaders(['X-Foo' => 'Bar']);
119+
```

docs/installation.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ modify your `render()` method as follows:
7878
namespace App\Exceptions;
7979

8080
use CloudCreativity\LaravelJsonApi\Exceptions\HandlesErrors;
81-
use Exception;
8281
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
8382
use Neomerx\JsonApi\Exceptions\JsonApiException;
83+
use Throwable;
8484

8585
class Handler extends ExceptionHandler
8686
{
@@ -94,7 +94,7 @@ class Handler extends ExceptionHandler
9494

9595
// ...
9696

97-
public function render($request, Exception $e)
97+
public function render($request, Throwable $e)
9898
{
9999
if ($this->isJsonApi($request, $e)) {
100100
return $this->renderJsonApi($request, $e);
@@ -103,7 +103,7 @@ class Handler extends ExceptionHandler
103103
// do standard exception rendering here...
104104
}
105105

106-
protected function prepareException(Exception $e)
106+
protected function prepareException(Throwable $e)
107107
{
108108
if ($e instanceof JsonApiException) {
109109
return $this->prepareJsonApiException($e);

src/Document/Error/Errors.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ class Errors implements DocumentInterface, IteratorAggregate
3030
*/
3131
private $errors;
3232

33+
/**
34+
* @var array
35+
*/
36+
private $headers = [];
37+
38+
/**
39+
* @param Errors|Error $value
40+
* @return static
41+
*/
42+
public static function cast($value): self
43+
{
44+
if ($value instanceof self) {
45+
return $value;
46+
}
47+
48+
if ($value instanceof Error) {
49+
return new self($value);
50+
}
51+
52+
throw new \UnexpectedValueException('Expecting an errors collection or an error object.');
53+
}
54+
3355
/**
3456
* Errors constructor.
3557
*
@@ -69,6 +91,17 @@ public function getStatus(): ?int
6991
return $only4xx ? Response::HTTP_BAD_REQUEST : Response::HTTP_INTERNAL_SERVER_ERROR;
7092
}
7193

94+
/**
95+
* @param array $headers
96+
* @return $this
97+
*/
98+
public function withHeaders(array $headers): self
99+
{
100+
$this->headers = $headers;
101+
102+
return $this;
103+
}
104+
72105
/**
73106
* @inheritDoc
74107
*/
@@ -99,12 +132,13 @@ public function jsonSerialize()
99132

100133
/**
101134
* @inheritDoc
102-
* @todo pass through headers.
103135
*/
104136
public function toResponse($request)
105137
{
106138
return json_api()->response()->errors(
107-
$this->errors
139+
$this->errors,
140+
null,
141+
$this->headers
108142
);
109143
}
110144

src/Exceptions/JsonApiException.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* Copyright 2020 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace CloudCreativity\LaravelJsonApi\Exceptions;
21+
22+
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
23+
use CloudCreativity\LaravelJsonApi\Document\Error\Errors;
24+
use Exception;
25+
use Illuminate\Contracts\Support\Responsable;
26+
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
27+
use Throwable;
28+
29+
class JsonApiException extends Exception implements HttpExceptionInterface, Responsable
30+
{
31+
32+
/**
33+
* @var Errors
34+
*/
35+
private $errors;
36+
37+
/**
38+
* @var array
39+
*/
40+
private $headers;
41+
42+
/**
43+
* Fluent constructor.
44+
*
45+
* @param Errors|Error $errors
46+
* @param Throwable|null $previous
47+
* @return static
48+
*/
49+
public static function make($errors, Throwable $previous = null): self
50+
{
51+
return new self($errors, $previous);
52+
}
53+
54+
/**
55+
* JsonApiException constructor.
56+
*
57+
* @param Errors|Error $errors
58+
* @param Throwable|null $previous
59+
* @param array $headers
60+
*/
61+
public function __construct($errors, Throwable $previous = null, array $headers = [])
62+
{
63+
parent::__construct('JSON API error', 0, $previous);
64+
$this->errors = Errors::cast($errors);
65+
$this->headers = $headers;
66+
}
67+
68+
/**
69+
* @inheritDoc
70+
*/
71+
public function getStatusCode()
72+
{
73+
return $this->errors->getStatus();
74+
}
75+
76+
/**
77+
* @param array $headers
78+
* @return $this
79+
*/
80+
public function withHeaders(array $headers): self
81+
{
82+
$this->headers = $headers;
83+
84+
return $this;
85+
}
86+
87+
/**
88+
* @inheritDoc
89+
*/
90+
public function getHeaders()
91+
{
92+
return $this->headers;
93+
}
94+
95+
/**
96+
* @inheritDoc
97+
*/
98+
public function toResponse($request)
99+
{
100+
return $this->errors
101+
->withHeaders($this->headers)
102+
->toResponse($request);
103+
}
104+
105+
}

tests/lib/Integration/ErrorsTest.php

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
namespace CloudCreativity\LaravelJsonApi\Tests\Integration;
1919

2020
use Carbon\Carbon;
21+
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
2122
use CloudCreativity\LaravelJsonApi\Exceptions\DocumentRequiredException;
2223
use CloudCreativity\LaravelJsonApi\Exceptions\InvalidJsonException;
24+
use CloudCreativity\LaravelJsonApi\Exceptions\JsonApiException;
2325
use CloudCreativity\LaravelJsonApi\Exceptions\ResourceNotFoundException;
2426
use DummyApp\Post;
2527
use Illuminate\Contracts\Validation\Validator;
@@ -28,8 +30,8 @@
2830
use Illuminate\Support\Facades\Route;
2931
use Illuminate\Support\MessageBag;
3032
use Illuminate\Validation\ValidationException;
31-
use Neomerx\JsonApi\Document\Error;
32-
use Neomerx\JsonApi\Exceptions\JsonApiException;
33+
use Neomerx\JsonApi\Document\Error as NeomerxError;
34+
use Neomerx\JsonApi\Exceptions\JsonApiException as NeomerxException;
3335
use Symfony\Component\HttpKernel\Exception\HttpException;
3436

3537
class ErrorsTest extends TestCase
@@ -239,19 +241,51 @@ public function testAcceptAny()
239241
*
240242
* @see https://github.com/cloudcreativity/laravel-json-api/issues/329
241243
*/
242-
public function testUnexpectedJsonApiException()
244+
public function testNeomerxJsonApiException()
243245
{
244246
config()->set('app.debug', true);
245247

246248
Route::get('/test', function () {
247-
throw new JsonApiException(new Error(null, null, 422, null, null, 'My foobar error message.'), 418);
249+
throw new NeomerxException(new NeomerxError(
250+
null,
251+
null,
252+
422,
253+
null,
254+
null,
255+
'My foobar error message.'
256+
), 418);
248257
});
249258

250259
$this->get('/test', ['Accept' => '*/*'])
251260
->assertStatus(418)
252261
->assertSee('My foobar error message.');
253262
}
254263

264+
public function testJsonApiException(): void
265+
{
266+
Route::get('/test', function () {
267+
throw JsonApiException::make(Error::fromArray([
268+
'status' => '418',
269+
'detail' => "Hello, I'm a teapot.",
270+
]))->withHeaders(['X-Foo' => 'Bar']);
271+
});
272+
273+
$expected = [
274+
'errors' => [
275+
[
276+
'status' => '418',
277+
'detail' => "Hello, I'm a teapot.",
278+
],
279+
],
280+
];
281+
282+
$this->get('/test')
283+
->assertStatus(418)
284+
->assertHeader('Content-Type', 'application/vnd.api+json')
285+
->assertHeader('X-Foo', 'Bar')
286+
->assertExactJson($expected);
287+
}
288+
255289
public function testMaintenanceMode()
256290
{
257291
$ex = new MaintenanceModeException(Carbon::now()->getTimestamp(), 60, "We'll be back soon.");

0 commit comments

Comments
 (0)
0