Skip to content

Commit 8cf34f2

Browse files
committed
Don't deserialize typed attribute values
Fixes #70
1 parent 3bdac86 commit 8cf34f2

17 files changed

+423
-36
lines changed

src/Schema/Field/Boolean.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@ public function __construct(string $name)
1010

1111
$this->serialize(static fn($value) => (bool) $value);
1212

13-
$this->deserialize(static fn($value) => (bool) $value);
14-
1513
$this->validate(static function (mixed $value, callable $fail): void {
1614
if (!is_bool($value)) {
17-
$fail('must be an boolean');
15+
$fail('must be a boolean');
1816
}
1917
});
2018
}

src/Schema/Field/Date.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ public function __construct(string $name)
1818
: $value,
1919
);
2020

21-
$this->deserialize(
22-
static fn($value) => is_string($value)
23-
? \DateTime::createFromFormat(static::FORMAT, $value)->setTime(0, 0)
24-
: $value,
25-
);
21+
$this->deserialize(static function ($value) {
22+
if (
23+
is_string($value) &&
24+
($date = \DateTime::createFromFormat(static::FORMAT, $value))
25+
) {
26+
return $date->setTime(0, 0);
27+
}
28+
return $value;
29+
});
2630

2731
$this->validate(static function (mixed $value, callable $fail): void {
28-
if (!\DateTime::createFromFormat(static::FORMAT, $value)) {
32+
if (!$value instanceof \DateTime) {
2933
$fail('must be a date');
3034
}
3135
});

src/Schema/Field/DateTime.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ public function __construct(string $name)
1616
: $value,
1717
);
1818

19-
$this->deserialize(
20-
static fn($value) => is_string($value)
21-
? \DateTime::createFromFormat(DateTimeInterface::RFC3339, $value)
22-
: $value,
23-
);
19+
$this->deserialize(static function ($value) {
20+
if (
21+
is_string($value) &&
22+
($date = \DateTime::createFromFormat(DateTimeInterface::RFC3339, $value))
23+
) {
24+
return $date;
25+
}
26+
return $value;
27+
});
2428

2529
$this->validate(static function (mixed $value, callable $fail): void {
26-
if (!\DateTime::createFromFormat(DateTimeInterface::RFC3339, $value)) {
30+
if (!$value instanceof \DateTime) {
2731
$fail('must be a date-time');
2832
}
2933
});

src/Schema/Field/Integer.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ public function __construct(string $name)
1212

1313
$this->serialize(static fn($value) => (int) $value);
1414

15-
$this->deserialize(static fn($value) => (int) $value);
16-
1715
$this->validate(static function (mixed $value, Closure $fail): void {
1816
if (!is_int($value)) {
1917
$fail('must be an integer');

src/Schema/Field/Number.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ public function __construct(string $name)
1919

2020
$this->serialize(static fn($value) => (float) $value);
2121

22-
$this->deserialize(static fn($value) => (float) $value);
23-
2422
$this->validate(function (mixed $value, Closure $fail): void {
2523
if (!is_numeric($value)) {
2624
$fail('must be numeric');

src/Schema/Field/Str.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ public function __construct(string $name)
1515

1616
$this->serialize(static fn($value) => (string) $value);
1717

18-
$this->deserialize(static fn($value) => (string) $value);
19-
2018
$this->validate(function (mixed $value, callable $fail): void {
2119
if (!is_string($value)) {
2220
$fail('must be a string');

tests/MockResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class MockResource extends Resource implements
2525
{
2626
public function __construct(
2727
private readonly string $type,
28-
private array $models = [],
28+
public array $models = [],
2929
private readonly array $endpoints = [],
3030
private readonly array $fields = [],
3131
private readonly array $meta = [],

tests/feature/BooleanTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Tobyz\Tests\JsonApiServer\feature;
4+
5+
use Tobyz\JsonApiServer\Endpoint\Create;
6+
use Tobyz\JsonApiServer\Endpoint\Show;
7+
use Tobyz\JsonApiServer\Exception\UnprocessableEntityException;
8+
use Tobyz\JsonApiServer\JsonApi;
9+
use Tobyz\JsonApiServer\Schema\Field\Boolean;
10+
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
11+
use Tobyz\Tests\JsonApiServer\MockResource;
12+
13+
class BooleanTest extends AbstractTestCase
14+
{
15+
private JsonApi $api;
16+
17+
public function setUp(): void
18+
{
19+
$this->api = new JsonApi();
20+
}
21+
22+
public function test_serializes_value_to_boolean()
23+
{
24+
$this->api->resource(
25+
new MockResource(
26+
'users',
27+
models: [(object) ['id' => '1', 'name' => 'hello']],
28+
endpoints: [Show::make()],
29+
fields: [Boolean::make('name')],
30+
),
31+
);
32+
33+
$response = $this->api->handle($this->buildRequest('GET', '/users/1'));
34+
35+
$this->assertJsonApiDocumentSubset(
36+
['data' => ['attributes' => ['name' => true]]],
37+
$response->getBody(),
38+
true,
39+
);
40+
}
41+
42+
public function test_validates_boolean()
43+
{
44+
$this->api->resource(
45+
new MockResource(
46+
'users',
47+
endpoints: [Create::make()],
48+
fields: [Boolean::make('name')->writable()],
49+
),
50+
);
51+
52+
$this->expectException(UnprocessableEntityException::class);
53+
54+
$response = $this->api->handle(
55+
$this->buildRequest('POST', '/users')->withParsedBody([
56+
'data' => ['type' => 'users', 'attributes' => ['name' => 'hello']],
57+
]),
58+
);
59+
}
60+
}

tests/feature/DateTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Tobyz\Tests\JsonApiServer\feature;
4+
5+
use Tobyz\JsonApiServer\Endpoint\Create;
6+
use Tobyz\JsonApiServer\Endpoint\Show;
7+
use Tobyz\JsonApiServer\Exception\UnprocessableEntityException;
8+
use Tobyz\JsonApiServer\JsonApi;
9+
use Tobyz\JsonApiServer\Schema\Field\Date;
10+
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
11+
use Tobyz\Tests\JsonApiServer\MockResource;
12+
13+
class DateTest extends AbstractTestCase
14+
{
15+
private JsonApi $api;
16+
17+
public function setUp(): void
18+
{
19+
$this->api = new JsonApi();
20+
}
21+
22+
public function test_serializes_value_to_date()
23+
{
24+
$this->api->resource(
25+
new MockResource(
26+
'users',
27+
models: [(object) ['id' => '1', 'dob' => new \DateTime('2023-01-01')]],
28+
endpoints: [Show::make()],
29+
fields: [Date::make('dob')],
30+
),
31+
);
32+
33+
$response = $this->api->handle($this->buildRequest('GET', '/users/1'));
34+
35+
$this->assertJsonApiDocumentSubset(
36+
['data' => ['attributes' => ['dob' => '2023-01-01']]],
37+
$response->getBody(),
38+
);
39+
}
40+
41+
public function test_deserializes_input_to_datetime()
42+
{
43+
$this->api->resource(
44+
$resource = new MockResource(
45+
'users',
46+
endpoints: [Create::make()],
47+
fields: [Date::make('dob')->writable()],
48+
),
49+
);
50+
51+
$this->api->handle(
52+
$this->buildRequest('POST', '/users')->withParsedBody([
53+
'data' => ['type' => 'users', 'attributes' => ['dob' => '2023-01-01']],
54+
]),
55+
);
56+
57+
$this->assertInstanceOf(\DateTime::class, $resource->models[0]->dob);
58+
}
59+
60+
public function test_validates_date()
61+
{
62+
$this->api->resource(
63+
new MockResource(
64+
'users',
65+
endpoints: [Create::make()],
66+
fields: [Date::make('dob')->writable()],
67+
),
68+
);
69+
70+
$this->expectException(UnprocessableEntityException::class);
71+
72+
$response = $this->api->handle(
73+
$this->buildRequest('POST', '/users')->withParsedBody([
74+
'data' => ['type' => 'users', 'attributes' => ['dob' => 'hello']],
75+
]),
76+
);
77+
}
78+
}

tests/feature/DateTimeTest.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Tobyz\Tests\JsonApiServer\feature;
4+
5+
use Tobyz\JsonApiServer\Endpoint\Create;
6+
use Tobyz\JsonApiServer\Endpoint\Show;
7+
use Tobyz\JsonApiServer\Exception\UnprocessableEntityException;
8+
use Tobyz\JsonApiServer\JsonApi;
9+
use Tobyz\JsonApiServer\Schema\Field\DateTime;
10+
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
11+
use Tobyz\Tests\JsonApiServer\MockResource;
12+
13+
class DateTimeTest extends AbstractTestCase
14+
{
15+
private JsonApi $api;
16+
17+
public function setUp(): void
18+
{
19+
$this->api = new JsonApi();
20+
}
21+
22+
public function test_serializes_value_to_date()
23+
{
24+
$this->api->resource(
25+
new MockResource(
26+
'users',
27+
models: [(object) ['id' => '1', 'dob' => new \DateTime('2023-01-01')]],
28+
endpoints: [Show::make()],
29+
fields: [DateTime::make('dob')],
30+
),
31+
);
32+
33+
$response = $this->api->handle($this->buildRequest('GET', '/users/1'));
34+
35+
$this->assertJsonApiDocumentSubset(
36+
['data' => ['attributes' => ['dob' => '2023-01-01T00:00:00+00:00']]],
37+
$response->getBody(),
38+
);
39+
}
40+
41+
public function test_deserializes_input_to_datetime()
42+
{
43+
$this->api->resource(
44+
$resource = new MockResource(
45+
'users',
46+
endpoints: [Create::make()],
47+
fields: [DateTime::make('dob')->writable()],
48+
),
49+
);
50+
51+
$this->api->handle(
52+
$this->buildRequest('POST', '/users')->withParsedBody([
53+
'data' => [
54+
'type' => 'users',
55+
'attributes' => ['dob' => '2023-01-01T00:00:00+00:00'],
56+
],
57+
]),
58+
);
59+
60+
$this->assertInstanceOf(\DateTime::class, $resource->models[0]->dob);
61+
}
62+
63+
public function test_validates_datetime()
64+
{
65+
$this->api->resource(
66+
new MockResource(
67+
'users',
68+
endpoints: [Create::make()],
69+
fields: [DateTime::make('dob')->writable()],
70+
),
71+
);
72+
73+
$this->expectException(UnprocessableEntityException::class);
74+
75+
$response = $this->api->handle(
76+
$this->buildRequest('POST', '/users')->withParsedBody([
77+
'data' => ['type' => 'users', 'attributes' => ['dob' => 'hello']],
78+
]),
79+
);
80+
}
81+
}

0 commit comments

Comments
 (0)