Skip to content

Commit 5938c2b

Browse files
committed
fix(jsonapi): handle missing attributes in ErrorNormalizer
Fixes undefined array key warning when normalizing exceptions that don't have attributes in their normalized structure. This typically occurs with ItemNotFoundException when invalid resource identifiers are provided.
1 parent 17415f7 commit 5938c2b

File tree

4 files changed

+92
-0
lines changed

4 files changed

+92
-0
lines changed

features/jsonapi/errors.feature

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,13 @@ Feature: JSON API error handling
6161
And the JSON node "errors[0].status" should be equal to 404
6262
And the JSON node "errors[0].detail" should exist
6363
And the JSON node "errors[0].type" should exist
64+
65+
Scenario: Get a proper error when ItemNotFoundException is thrown from a provider
66+
When I send a "GET" request to "/jsonapi_error_test/nonexistent"
67+
Then the response status code should be 404
68+
And the response should be in JSON
69+
And the header "Content-Type" should be equal to "application/vnd.api+json; charset=utf-8"
70+
And the JSON node "errors" should exist
71+
And the JSON node "errors[0].status" should exist
72+
And the JSON node "errors[0].title" should exist
73+
And the JSON node "errors[0].id" should exist

src/JsonApi/Serializer/ErrorNormalizer.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ public function __construct(private ?NormalizerInterface $itemNormalizer = null)
3535
public function normalize(mixed $object, ?string $format = null, array $context = []): array
3636
{
3737
$jsonApiObject = $this->itemNormalizer->normalize($object, $format, $context);
38+
39+
if (!isset($jsonApiObject['data']['attributes'])) {
40+
return ['errors' => [[
41+
'id' => $jsonApiObject['data']['id'] ?? uniqid('error_', true),
42+
'status' => (string) (method_exists($object, 'getStatusCode') ? $object->getStatusCode() : 500),
43+
'title' => method_exists($object, 'getMessage') ? $object->getMessage() : 'An error occurred',
44+
]]];
45+
}
46+
3847
$error = $jsonApiObject['data']['attributes'];
3948
$error['id'] = $jsonApiObject['data']['id'];
4049
if (isset($error['type'])) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\State\JsonApiErrorTestProvider;
20+
21+
#[ApiResource(
22+
operations: [
23+
new Get(
24+
uriTemplate: '/jsonapi_error_test/{id}',
25+
provider: JsonApiErrorTestProvider::class,
26+
),
27+
],
28+
formats: ['jsonapi' => ['application/vnd.api+json']],
29+
)]
30+
class JsonApiErrorTestResource
31+
{
32+
#[ApiProperty(identifier: true)]
33+
public string $id;
34+
35+
public string $name;
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\State;
15+
16+
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
17+
use ApiPlatform\Metadata\Operation;
18+
use ApiPlatform\State\ProviderInterface;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\JsonApiErrorTestResource;
20+
21+
class JsonApiErrorTestProvider implements ProviderInterface
22+
{
23+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
24+
{
25+
$id = $uriVariables['id'] ?? null;
26+
27+
if ('existing' === $id) {
28+
$resource = new JsonApiErrorTestResource();
29+
$resource->id = $id;
30+
$resource->name = 'Existing Resource';
31+
32+
return $resource;
33+
}
34+
35+
throw new ItemNotFoundException(\sprintf('Resource "%s" not found.', $id));
36+
}
37+
}

0 commit comments

Comments
 (0)