Skip to content

Commit f412b10

Browse files
committed
Skip custom validators after type failures
1 parent 443c765 commit f412b10

5 files changed

Lines changed: 130 additions & 72 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Schema\Concerns;
4+
5+
use Tobyz\JsonApiServer\Context;
6+
use Tobyz\JsonApiServer\SchemaContext;
7+
8+
trait AppliesType
9+
{
10+
public function serializeValue(mixed $value, Context $context): mixed
11+
{
12+
if ($this->nullable && $value === null) {
13+
return null;
14+
}
15+
16+
$value = parent::serializeValue($value, $context);
17+
18+
if ($this->type) {
19+
$value = $this->type->serialize($value);
20+
}
21+
22+
return $value;
23+
}
24+
25+
public function deserializeValue(mixed $value, Context $context): mixed
26+
{
27+
if ($this->nullable && $value === null) {
28+
return null;
29+
}
30+
31+
if ($this->type) {
32+
$value = $this->type->deserialize($value);
33+
}
34+
35+
return parent::deserializeValue($value, $context);
36+
}
37+
38+
public function validateValue(mixed $value, callable $fail, Context $context): void
39+
{
40+
if ($value !== null && $this->type) {
41+
$valid = true;
42+
43+
$this->type->validate($value, function ($error = []) use ($fail, &$valid) {
44+
$valid = false;
45+
$fail($error);
46+
});
47+
48+
if (!$valid) {
49+
return;
50+
}
51+
}
52+
53+
parent::validateValue($value, $fail, $context);
54+
}
55+
56+
public function getSchema(SchemaContext $context): array
57+
{
58+
return parent::getSchema($context) + ($this->type?->schema() ?: []);
59+
}
60+
}

src/Schema/Concerns/HasType.php

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
namespace Tobyz\JsonApiServer\Schema\Concerns;
44

5-
use Tobyz\JsonApiServer\Context;
65
use Tobyz\JsonApiServer\Schema\Type\Type;
7-
use Tobyz\JsonApiServer\SchemaContext;
86

97
trait HasType
108
{
9+
use AppliesType;
10+
1111
public ?Type $type = null;
1212

1313
public function type(?Type $type): static
@@ -16,46 +16,4 @@ public function type(?Type $type): static
1616

1717
return $this;
1818
}
19-
20-
public function serializeValue(mixed $value, Context $context): mixed
21-
{
22-
if ($this->nullable && $value === null) {
23-
return null;
24-
}
25-
26-
$value = parent::serializeValue($value, $context);
27-
28-
if ($this->type) {
29-
$value = $this->type->serialize($value);
30-
}
31-
32-
return $value;
33-
}
34-
35-
public function deserializeValue(mixed $value, Context $context): mixed
36-
{
37-
if ($this->nullable && $value === null) {
38-
return null;
39-
}
40-
41-
if ($this->type) {
42-
$value = $this->type->deserialize($value);
43-
}
44-
45-
return parent::deserializeValue($value, $context);
46-
}
47-
48-
public function validateValue(mixed $value, callable $fail, Context $context): void
49-
{
50-
if ($value !== null && $this->type) {
51-
$this->type->validate($value, $fail);
52-
}
53-
54-
parent::validateValue($value, $fail, $context);
55-
}
56-
57-
public function getSchema(SchemaContext $context): array
58-
{
59-
return parent::getSchema($context) + ($this->type?->schema() ?: []);
60-
}
6119
}

src/Schema/Concerns/SetsValue.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ public function validateValue(mixed $value, callable $fail, Context $context): v
145145
{
146146
if ($value === null && !$this->nullable) {
147147
$fail(new NullViolationException());
148+
return;
148149
}
149150

150151
foreach ($this->validators as $validator) {

src/Schema/Id.php

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
use Closure;
66
use DomainException;
7-
use Tobyz\JsonApiServer\Context;
7+
use Tobyz\JsonApiServer\Schema\Concerns\AppliesType;
88
use Tobyz\JsonApiServer\Schema\Field\Field;
99
use Tobyz\JsonApiServer\Schema\Type\Str;
10-
use Tobyz\JsonApiServer\SchemaContext;
1110

1211
class Id extends Field
1312
{
13+
use AppliesType;
14+
1415
public Str $type;
1516

1617
public function __construct()
@@ -33,32 +34,6 @@ public function type(Str $type): static
3334
return $this;
3435
}
3536

36-
public function serializeValue(mixed $value, Context $context): mixed
37-
{
38-
$value = parent::serializeValue($value, $context);
39-
40-
return $this->type->serialize($value);
41-
}
42-
43-
public function deserializeValue(mixed $value, Context $context): mixed
44-
{
45-
$value = $this->type->deserialize($value);
46-
47-
return parent::deserializeValue($value, $context);
48-
}
49-
50-
public function validateValue(mixed $value, callable $fail, Context $context): void
51-
{
52-
$this->type->validate($value, $fail);
53-
54-
parent::validateValue($value, $fail, $context);
55-
}
56-
57-
public function getSchema(SchemaContext $context): array
58-
{
59-
return parent::getSchema($context) + ($this->type->schema() ?: []);
60-
}
61-
6237
public function writable(?Closure $condition = null): static
6338
{
6439
throw new DomainException('ID cannot be writable. Use writeableOnCreate() instead.');

tests/feature/FieldValidationTest.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Tobyz\JsonApiServer\Exception\JsonApiErrorsException;
88
use Tobyz\JsonApiServer\JsonApi;
99
use Tobyz\JsonApiServer\Schema\Field\Attribute;
10+
use Tobyz\JsonApiServer\Schema\Type\Arr;
11+
use Tobyz\JsonApiServer\Schema\Type\Str;
1012
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
1113
use Tobyz\Tests\JsonApiServer\MockResource;
1214

@@ -65,4 +67,66 @@ public function test_validate_on_update()
6567
]),
6668
);
6769
}
70+
71+
public function test_validate_is_not_run_when_type_validation_fails()
72+
{
73+
$called = false;
74+
75+
$this->api->resource(
76+
new MockResource(
77+
'users',
78+
endpoints: [Create::make()],
79+
fields: [
80+
Attribute::make('aliases')
81+
->type(Arr::make())
82+
->writable()
83+
->validate(function (array $value, callable $fail) use (&$called) {
84+
$called = true;
85+
}),
86+
],
87+
),
88+
);
89+
90+
try {
91+
$this->api->handle(
92+
$this->buildRequest('POST', '/users')->withParsedBody([
93+
'data' => ['type' => 'users', 'attributes' => ['aliases' => 1]],
94+
]),
95+
);
96+
$this->fail('Expected validation to fail.');
97+
} catch (JsonApiErrorsException) {
98+
$this->assertFalse($called);
99+
}
100+
}
101+
102+
public function test_validate_is_not_run_when_non_nullable_value_is_null()
103+
{
104+
$called = false;
105+
106+
$this->api->resource(
107+
new MockResource(
108+
'users',
109+
endpoints: [Create::make()],
110+
fields: [
111+
Attribute::make('name')
112+
->type(Str::make())
113+
->writable()
114+
->validate(function (string $value, callable $fail) use (&$called) {
115+
$called = true;
116+
}),
117+
],
118+
),
119+
);
120+
121+
try {
122+
$this->api->handle(
123+
$this->buildRequest('POST', '/users')->withParsedBody([
124+
'data' => ['type' => 'users', 'attributes' => ['name' => null]],
125+
]),
126+
);
127+
$this->fail('Expected validation to fail.');
128+
} catch (JsonApiErrorsException) {
129+
$this->assertFalse($called);
130+
}
131+
}
68132
}

0 commit comments

Comments
 (0)