Skip to content

Commit 5ead661

Browse files
committed
Merge branch 'release/2.4.0'
2 parents 5da3145 + d209814 commit 5ead661

38 files changed

+724
-70
lines changed

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@
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.4.0] - 2022-06-25
7+
8+
### Added
9+
10+
- The `JsonApiException` class now has a `context()` method. Laravel's exception handler uses this to add log context
11+
when the exception is logged. This means logging of JSON:API exceptions will now include the HTTP status code and the
12+
JSON:API errors.
13+
- Moved the default `406 Not Acceptable` and `415 Unsupported Media Type` messages to the following two new exception
14+
classes:
15+
- `Exceptions\HttpNotAcceptableException`
16+
- `Exceptions\HttpUnsupportedMediaTypeException`
17+
18+
### Fixed
19+
20+
- [#184](https://github.com/laravel-json-api/laravel/issues/184) Ensure that an `Accept` header with the media type
21+
`application/json` is rejected with a `406 Not Acceptable` response. Previously this media type worked, which is
22+
incorrect as the JSON:API specification requires the media type `application/vnd.api+json`.
23+
- [#197](https://github.com/laravel-json-api/laravel/pull/197) Fix sending `null` for a to-one relationship update.
24+
625
## [2.3.0] - 2022-04-11
726

827
### Added

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
"require": {
2626
"php": "^7.4|^8.0",
2727
"ext-json": "*",
28-
"laravel-json-api/core": "^2.2",
28+
"laravel-json-api/core": "^2.3",
2929
"laravel-json-api/eloquent": "^2.1.1",
30-
"laravel-json-api/encoder-neomerx": "^2.0",
30+
"laravel-json-api/encoder-neomerx": "^2.0.1",
3131
"laravel-json-api/exceptions": "^1.1",
3232
"laravel-json-api/spec": "^1.2",
3333
"laravel-json-api/validation": "^2.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
/*
3+
* Copyright 2022 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 LaravelJsonApi\Laravel\Exceptions;
21+
22+
use Illuminate\Http\Response;
23+
use Symfony\Component\HttpKernel\Exception\HttpException;
24+
use Throwable;
25+
26+
class HttpNotAcceptableException extends HttpException
27+
{
28+
/**
29+
* HttpNotAcceptableException constructor.
30+
*
31+
* @param string|null $message
32+
* @param Throwable|null $previous
33+
* @param array $headers
34+
* @param int $code
35+
*/
36+
public function __construct(
37+
string $message = null,
38+
Throwable $previous = null,
39+
array $headers = [],
40+
int $code = 0
41+
) {
42+
if (null === $message) {
43+
$message = __("The requested resource is capable of generating only content not acceptable "
44+
. "according to the Accept headers sent in the request.");
45+
}
46+
47+
parent::__construct(Response::HTTP_NOT_ACCEPTABLE, $message, $previous, $headers, $code);
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
/*
3+
* Copyright 2022 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 LaravelJsonApi\Laravel\Exceptions;
21+
22+
use Illuminate\Http\Response;
23+
use Symfony\Component\HttpKernel\Exception\HttpException;
24+
use Throwable;
25+
26+
class HttpUnsupportedMediaTypeException extends HttpException
27+
{
28+
/**
29+
* HttpUnsupportedMediaTypeException constructor.
30+
*
31+
* @param string|null $message
32+
* @param Throwable|null $previous
33+
* @param array $headers
34+
* @param int $code
35+
*/
36+
public function __construct(string $message = null, Throwable $previous = null, array $headers = [], int $code = 0)
37+
{
38+
if (null === $message) {
39+
$message = __('The request entity has a media type which the server or resource does not support.');
40+
}
41+
42+
parent::__construct(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, $message, $previous, $headers, $code);
43+
}
44+
}

src/Http/Requests/ResourceQuery.php

+19-10
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,15 @@
2121

2222
use Illuminate\Contracts\Validation\Validator;
2323
use Illuminate\Database\Eloquent\Model;
24-
use Illuminate\Http\Response;
2524
use LaravelJsonApi\Contracts\Auth\Authorizer;
2625
use LaravelJsonApi\Contracts\Query\QueryParameters;
2726
use LaravelJsonApi\Core\Exceptions\JsonApiException;
2827
use LaravelJsonApi\Core\Query\FieldSets;
2928
use LaravelJsonApi\Core\Query\FilterParameters;
3029
use LaravelJsonApi\Core\Query\IncludePaths;
3130
use LaravelJsonApi\Core\Query\SortFields;
31+
use LaravelJsonApi\Laravel\Exceptions\HttpNotAcceptableException;
3232
use LogicException;
33-
use Symfony\Component\HttpKernel\Exception\HttpException;
3433
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
3534
use function array_key_exists;
3635

@@ -48,11 +47,11 @@ class ResourceQuery extends FormRequest implements QueryParameters
4847
private static $queryOneResolver;
4948

5049
/**
50+
* The media types the resource accepts, in addition to JSON:API.
51+
*
5152
* @var string[]
5253
*/
53-
protected array $mediaTypes = [
54-
self::JSON_API_MEDIA_TYPE,
55-
];
54+
protected array $mediaTypes = [];
5655

5756
/**
5857
* The include paths to use if the client provides none.
@@ -304,10 +303,24 @@ protected function failedValidation(Validator $validator)
304303
*/
305304
protected function isAcceptableMediaType(): bool
306305
{
306+
/**
307+
* We expect the JSON:API media type to exactly match.
308+
*/
309+
foreach ($this->getAcceptableContentTypes() as $contentType) {
310+
if (self::JSON_API_MEDIA_TYPE === $contentType) {
311+
return true;
312+
}
313+
}
314+
315+
/**
316+
* Otherwise we check if any additional media types match.
317+
*/
307318
return $this->accepts($this->mediaTypes());
308319
}
309320

310321
/**
322+
* Get the media types the resource accepts, in addition to JSON:API.
323+
*
311324
* @return string[]
312325
*/
313326
protected function mediaTypes(): array
@@ -322,10 +335,6 @@ protected function mediaTypes(): array
322335
*/
323336
protected function notAcceptable(): HttpExceptionInterface
324337
{
325-
return new HttpException(
326-
Response::HTTP_NOT_ACCEPTABLE,
327-
__("The requested resource is capable of generating only content not acceptable "
328-
. "according to the Accept headers sent in the request.")
329-
);
338+
return new HttpNotAcceptableException();
330339
}
331340
}

src/Http/Requests/ResourceRequest.php

+3-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
2323
use Illuminate\Contracts\Validation\Validator;
2424
use Illuminate\Database\Eloquent\Model;
25-
use Illuminate\Http\Response;
2625
use Illuminate\Support\Collection;
2726
use LaravelJsonApi\Contracts\Auth\Authorizer;
2827
use LaravelJsonApi\Contracts\Schema\Relation;
@@ -31,10 +30,10 @@
3130
use LaravelJsonApi\Core\Query\IncludePaths;
3231
use LaravelJsonApi\Core\Store\LazyRelation;
3332
use LaravelJsonApi\Core\Support\Str;
33+
use LaravelJsonApi\Laravel\Exceptions\HttpUnsupportedMediaTypeException;
3434
use LaravelJsonApi\Spec\RelationBuilder;
3535
use LaravelJsonApi\Spec\ResourceBuilder;
3636
use LogicException;
37-
use Symfony\Component\HttpKernel\Exception\HttpException;
3837
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
3938
use function array_key_exists;
4039

@@ -205,7 +204,7 @@ public function document(): array
205204
{
206205
$document = $this->json()->all();
207206

208-
if (!is_array($document) || !isset($document['data']) || !is_array($document['data'])) {
207+
if (!is_array($document) || !array_key_exists('data', $document) || !(is_array($document['data']) || is_null($document['data']))) {
209208
throw new LogicException('Expecting JSON API specification compliance to have been run.');
210209
}
211210

@@ -349,10 +348,7 @@ protected function isSupportedMediaType(): bool
349348
*/
350349
protected function unsupportedMediaType(): HttpExceptionInterface
351350
{
352-
return new HttpException(
353-
Response::HTTP_UNSUPPORTED_MEDIA_TYPE,
354-
__('The request entity has a media type which the server or resource does not support.')
355-
);
351+
return new HttpUnsupportedMediaTypeException();
356352
}
357353

358354
/**

tests/dummy/app/Http/Controllers/Api/V1/UserController.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626

2727
class UserController extends Controller
2828
{
29-
3029
use Actions\FetchOne;
30+
use Actions\FetchRelated;
31+
use Actions\FetchRelationship;
32+
use Actions\UpdateRelationship;
3133

3234
/**
3335
* Return the current user.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
/*
3+
* Copyright 2022 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 App\JsonApi\V1\Phones;
21+
22+
use App\Models\Phone;
23+
use LaravelJsonApi\Eloquent\Fields\DateTime;
24+
use LaravelJsonApi\Eloquent\Fields\ID;
25+
use LaravelJsonApi\Eloquent\Fields\Str;
26+
use LaravelJsonApi\Eloquent\Schema;
27+
28+
class PhoneSchema extends Schema
29+
{
30+
/**
31+
* The model the schema corresponds to.
32+
*
33+
* @var string
34+
*/
35+
public static string $model = Phone::class;
36+
37+
/**
38+
* @inheritDoc
39+
*/
40+
public function fields(): iterable
41+
{
42+
return [
43+
ID::make(),
44+
DateTime::make('createdAt')->readOnly(),
45+
Str::make('number'),
46+
DateTime::make('updatedAt')->readOnly(),
47+
];
48+
}
49+
}

tests/dummy/app/JsonApi/V1/Server.php

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ protected function allSchemas(): array
6565
return [
6666
Comments\CommentSchema::class,
6767
Images\ImageSchema::class,
68+
Phones\PhoneSchema::class,
6869
Posts\PostSchema::class,
6970
Tags\TagSchema::class,
7071
Users\UserSchema::class,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
/*
3+
* Copyright 2022 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 App\JsonApi\V1\Users;
21+
22+
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
23+
use LaravelJsonApi\Validation\Rule as JsonApiRule;
24+
25+
class UserRequest extends ResourceRequest
26+
{
27+
/**
28+
* @return array
29+
*/
30+
public function rules(): array
31+
{
32+
return [
33+
'phone' => JsonApiRule::toOne(),
34+
];
35+
}
36+
}

tests/dummy/app/JsonApi/V1/Users/UserSchema.php

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use App\Models\User;
2323
use LaravelJsonApi\Eloquent\Fields\DateTime;
2424
use LaravelJsonApi\Eloquent\Fields\ID;
25+
use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
2526
use LaravelJsonApi\Eloquent\Fields\Str;
2627
use LaravelJsonApi\Eloquent\Filters\Where;
2728
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
@@ -47,6 +48,7 @@ public function fields(): array
4748
ID::make(),
4849
DateTime::make('createdAt')->readOnly(),
4950
Str::make('name'),
51+
HasOne::make('phone')->deleteDetachedModel(),
5052
DateTime::make('updatedAt')->readOnly(),
5153
];
5254
}

0 commit comments

Comments
 (0)