Skip to content

Commit 396f3f1

Browse files
authored
Merge pull request #42 from tuyakhov/error_formatting
Improve errors formatting
2 parents a42e222 + 32052e1 commit 396f3f1

File tree

5 files changed

+113
-1
lines changed

5 files changed

+113
-1
lines changed

src/JsonApiResponseFormatter.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,29 @@
88
use yii\base\Component;
99
use yii\helpers\ArrayHelper;
1010
use yii\helpers\Json;
11+
use yii\web\ErrorHandler;
1112
use yii\web\Response;
1213
use yii\web\ResponseFormatterInterface;
1314

1415
class JsonApiResponseFormatter extends Component implements ResponseFormatterInterface
1516
{
17+
/**
18+
* Mapping between the error handler component and JSON API error object
19+
* @see ErrorHandler::convertExceptionToArray()
20+
*/
21+
const ERROR_EXCEPTION_MAPPING = [
22+
'title' => 'name',
23+
'detail' => 'message',
24+
'code' => 'code',
25+
'status' => 'status'
26+
];
27+
/**
28+
* An error object MAY have the following members
29+
* @link http://jsonapi.org/format/#error-objects
30+
*/
31+
const ERROR_ALLOWED_MEMBERS = [
32+
'id', 'links', 'status', 'code', 'title', 'detail', 'source', 'meta'
33+
];
1634
/**
1735
* @var integer the encoding options passed to [[Json::encode()]]. For more details please refer to
1836
* <http://www.php.net/manual/en/function.json-encode.php>.
@@ -44,7 +62,19 @@ public function format($response)
4462
if (ArrayHelper::isAssociative($response->data)) {
4563
$response->data = [$response->data];
4664
}
47-
$apiDocument = ['errors' => $response->data];
65+
$formattedErrors = [];
66+
foreach ($response->data as $error) {
67+
$formattedError = array_intersect_key($error, array_flip(static::ERROR_ALLOWED_MEMBERS));
68+
foreach (static::ERROR_EXCEPTION_MAPPING as $member => $key) {
69+
if (isset($error[$key])) {
70+
$formattedError[$member] = (string) $error[$key];
71+
}
72+
}
73+
if (!empty($formattedError)) {
74+
$formattedErrors[] = $formattedError;
75+
}
76+
}
77+
$apiDocument = ['errors' => $formattedErrors];
4878
}
4979

5080
$response->content = Json::encode($apiDocument, $options);

src/Serializer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ protected function serializeModelErrors($model)
326326
$result[] = [
327327
'source' => ['pointer' => "/data/attributes/{$memberName}"],
328328
'detail' => $message,
329+
'status' => '422'
329330
];
330331
}
331332

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* @author Anton Tuyakhov <atuyakhov@gmail.com>
4+
*/
5+
namespace tuyakhov\jsonapi\tests;
6+
7+
8+
use tuyakhov\jsonapi\JsonApiResponseFormatter;
9+
use tuyakhov\jsonapi\Serializer;
10+
use tuyakhov\jsonapi\tests\data\ResourceModel;
11+
use yii\helpers\Json;
12+
use yii\web\Response;
13+
use yii\web\ServerErrorHttpException;
14+
15+
class JsonApiResponseFormatterTest extends TestCase
16+
{
17+
public function testFormatException()
18+
{
19+
$formatter = new JsonApiResponseFormatter();
20+
$exception = new ServerErrorHttpException('Server error');
21+
$response = new Response();
22+
$response->setStatusCode($exception->statusCode);
23+
$response->data = [
24+
'name' => $exception->getName(),
25+
'message' => $exception->getMessage(),
26+
'code' => $exception->getCode(),
27+
'status' => $exception->statusCode
28+
];
29+
$formatter->format($response);
30+
$this->assertJson($response->content);
31+
$this->assertSame(Json::encode([
32+
'errors' => [
33+
[
34+
'code' => '0',
35+
'status' => '500',
36+
'title' => Response::$httpStatuses[500],
37+
'detail' => 'Server error',
38+
]
39+
]
40+
]), $response->content);
41+
}
42+
43+
public function testFormModelError()
44+
{
45+
$formatter = new JsonApiResponseFormatter();
46+
$exception = new ServerErrorHttpException('Server error');
47+
$response = new Response();
48+
$response->setStatusCode($exception->statusCode);
49+
$serializer = new Serializer();
50+
$model = new ResourceModel();
51+
$model->addError('field1', 'Error');
52+
$model->addError('field2', 'Test Error');
53+
$response->data = $serializer->serialize($model);
54+
$formatter->format($response);
55+
$this->assertJson($response->content);
56+
$this->assertSame(Json::encode([
57+
'errors' => [
58+
[
59+
'source' => ['pointer' => "/data/attributes/field1"],
60+
'detail' => 'Error',
61+
'status' => '422'
62+
],
63+
[
64+
'source' => ['pointer' => "/data/attributes/field2"],
65+
'detail' => 'Test Error',
66+
'status' => '422'
67+
]
68+
]
69+
]), $response->content);
70+
}
71+
}

tests/SerializerTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,17 @@ public function testSerializeModelErrors()
4040
[
4141
'source' => ['pointer' => "/data/attributes/field1"],
4242
'detail' => 'Test error',
43+
'status' => '422'
4344
],
4445
[
4546
'source' => ['pointer' => "/data/attributes/field2"],
4647
'detail' => 'Multiple error 1',
48+
'status' => '422'
4749
],
4850
[
4951
'source' => ['pointer' => "/data/attributes/first-name"],
5052
'detail' => 'Member name check',
53+
'status' => '422'
5154
]
5255
], $serializer->serialize($model));
5356
}

tests/TestCase.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace tuyakhov\jsonapi\tests;
66

77
use \yii\helpers\ArrayHelper;
8+
use yii\web\Response;
89

910
class TestCase extends \PHPUnit_Framework_TestCase
1011
{
@@ -37,6 +38,12 @@ protected function mockApplication($config = [], $appClass = '\yii\web\Applicati
3738
'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq',
3839
'scriptFile' => __DIR__ .'/index.php',
3940
'scriptUrl' => '/index.php',
41+
],
42+
'response' => [
43+
'format' => Response::FORMAT_JSON,
44+
'formatters' => [
45+
Response::FORMAT_JSON => 'tuyakhov\jsonapi\JsonApiResponseFormatter'
46+
]
4047
]
4148
],
4249
'vendorPath' => $this->getVendorPath(),

0 commit comments

Comments
 (0)