8000 Merge branch '2.x' into develop · nelson6e65/laravel-json-api@b2c6aa7 · GitHub
[go: up one dir, main page]

Skip to content

Commit b2c6aa7

Browse files
committed
Merge branch '2.x' into develop
2 parents b439c8a + 9cbf4ca commit b2c6aa7

File tree

7 files changed

+232
-35
lines changed

7 files changed

+232
-35
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ for the soft delete attribute now defaults to the camel-case version of the mode
2222
column `deleted_at` previously defaulted to the JSON API field `deleted-at`, whereas now it will
2323
default to `deletedAt`. To continue to use dash-case, set the `softDeleteField` property on your adapter.
2424

25+
## [2.1.0] - 2020-09-04
26+
27+
### Added
28+
- [#538](https://github.com/cloudcreativity/laravel-json-api/issues/538)
29+
New JSON API exception class that accepts the new error objects from this package.
30+
It is recommended that you use `CloudCreativity\LaravelJsonApi\Exceptions\JsonApiException`
31+
combined with the `CloudCreativity\LaravelJsonApi\Document\Error\Error`. It is not
32+
recommended to use the `Neomerx\JsonApi\Exceptions\JsonApiException` class as support
33+
for this exception class will be removed in a future version.
34+
2535
## [2.0.0] - 2020-06-17
2636

2737
### Added

docs/features/errors.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ 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

@@ -85,8 +85,8 @@ It is also possible to throw a `JsonApiException` from anywhere in your code. Th
8585
to a JSON API response. For example:
8686

8787
```php
88-
use Neomerx\JsonApi\Exceptions\JsonApiException;
8988
use CloudCreativity\LaravelJsonApi\Document\Error\Error;
89+
use CloudCreativity\LaravelJsonApi\Exceptions\JsonApiException;
9090

9191
try {
9292
dispatchNow(new ChargeCard($token));
@@ -97,12 +97,24 @@ try {
9797
'status' => '402',
9898
]);
9999

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

104104
The JSON API exception takes three arguments:
105105

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

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);

phpunit.xml

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit backupGlobals="false"
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
backupGlobals="false"
34
backupStaticAttributes="false"
4-
beStrictAboutTestsThatDoNotTestAnything="false"
5+
beStrictAboutTestsThatDoNotTestAnything="true"
56
bootstrap="vendor/autoload.php"
67
colors="true"
78
convertErrorsToExceptions="true"
@@ -11,24 +12,25 @@
1112
stopOnError="false"
1213
stopOnFailure="false"
1314
verbose="true"
15+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
1416
>
15-
<testsuites>
16-
<testsuite name="Unit">
17-
<directory suffix="Test.php">./tests/lib/Unit/</directory>
18-
</testsuite>
19-
<testsuite name="Integration">
20-
<directory suffix="Test.php">./tests/lib/Integration/</directory>
21-
</testsuite>
22-
<testsuite name="Acceptance">
23-
<directory suffix="Test.php">./tests/dummy/tests/</directory>
24-
</testsuite>
25-
</testsuites>
26-
<filter>
27-
<whitelist>
28-
<directory suffix=".php">src/</directory>
29-
</whitelist>
30-
</filter>
31-
<php>
32-
<env name="APP_KEY" value="base64:BMfTqJC1cFk6A/jTPsjQgC+cROx7TDaEeGIAat6CuqY="/>
33-
</php>
17+
<coverage>
18+
<include>
19+
<directory suffix=".php">src/</directory>
20+
</include>
21+
</coverage>
22+
<testsuites>
23+
<testsuite name="Unit">
24+
<directory suffix="Test.php">./tests/lib/Unit/</directory>
25+
</testsuite>
26+
<testsuite name="Integration">
27+
<directory suffix="Test.php">./tests/lib/Integration/</directory>
28+
</testsuite>
29+
<testsuite name="Acceptance">
30+
<directory suffix="Test.php">./tests/dummy/tests/</directory>
31+
</testsuite>
32+
</testsuites>
33+
<php>
34+
<env name="APP_KEY" value="base64:BMfTqJC1cFk6A/jTPsjQgC+cROx7TDaEeGIAat6CuqY="/>
35+
</php>
3436
</phpunit>

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