Skip to content

Commit 1f4da7a

Browse files
authored
fix: update validation rules to use ValidationRule interface and improve error handling (#32)
1 parent f86b8fa commit 1f4da7a

File tree

8 files changed

+128
-72
lines changed

8 files changed

+128
-72
lines changed

.github/workflows/php.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
matrix:
1919
php_version: ['8.1', '8.2', '8.3', '8.4']
20-
laravel_version: ['^9.0', '^10.0', '^11.0', '^12.0']
20+
laravel_version: ['^10.0', '^11.0', '^12.0']
2121
exclude:
2222
- php_version: '8.1'
2323
laravel_version: '^11.0'

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.idea
22
.phpunit.cache
3+
.phpunit.result.cache
4+
coverage.xml
35
/vendor/
46
composer.lock

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
"type": "library",
1212
"require": {
1313
"php": "^8.1",
14-
"illuminate/collections": "^9.0|^10.0|^11.0|^12.0",
15-
"illuminate/http": "^9.0|^10.0|^11.0|^12.0",
16-
"illuminate/support": "^9.0|^10.0|^11.0|^12.0"
14+
"illuminate/collections": "^10.0|^11.0|^12.0",
15+
"illuminate/http": "^10.0|^11.0|^12.0",
16+
"illuminate/support": "^10.0|^11.0|^12.0"
1717
},
1818
"require-dev": {
1919
"phpunit/phpunit": "^9.0|^10.0|^11.0|^12.0",

src/Requests/Rules/Fields.php

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
use Ark4ne\JsonApi\Requests\Rules\Traits\UseTrans;
66
use Ark4ne\JsonApi\Resources\Skeleton;
77
use Ark4ne\JsonApi\Support\Fields as SupportFields;
8-
use Illuminate\Contracts\Validation\Rule;
8+
use Illuminate\Contracts\Validation\ValidationRule;
99

1010
/**
1111
* @template T as \Ark4ne\JsonApi\Resources\JsonApiResource
1212
*/
13-
class Fields implements Rule
13+
class Fields implements ValidationRule
1414
{
1515
use UseTrans;
1616

17+
private const BASE = 'validation.custom.jsonapi.fields';
18+
1719
/**
1820
* @var array<int, array{":resource": string, ":fields": ?string}>>
1921
*/
@@ -27,38 +29,46 @@ public function __construct(
2729
) {
2830
}
2931

30-
public function passes($attribute, $value): bool
32+
public function validate(string $attribute, mixed $value, \Closure $fail): void
3133
{
3234
if (!is_array($value)) {
33-
return false;
35+
$fail($this->trans(
36+
sprintf('%s.invalid', self::BASE),
37+
'The selected :attribute is invalid.'
38+
));
39+
return;
3440
}
3541

3642
$desired = SupportFields::parse($value);
3743
$schema = $this->resource::schema();
3844

39-
return $this->assert($schema, $desired);
45+
if (!$this->assert($schema, $desired)) {
46+
47+
foreach ($this->message() as $message) {
48+
$fail($message);
49+
}
50+
}
4051
}
4152

4253
/**
4354
* @return array<string>
4455
*/
45-
public function message(): array
56+
private function message(): array
4657
{
47-
$base = 'validation.custom.jsonapi.fields';
4858
$message = $this->trans(
49-
"$base.invalid",
59+
sprintf('%s.invalid', self::BASE),
5060
'The selected :attribute is invalid.'
5161
);
5262

53-
return array_merge($message, ...array_map(
54-
fn($failure) => isset($failure[':fields'])
63+
return array_merge([$message], array_map(
64+
fn($failure) => isset($failure[':fields'])
5565
? $this->trans(
56-
"$base.invalid_fields",
66+
sprintf('%s.invalid_fields', self::BASE),
5767
'":resource" doesn\'t have fields ":fields".',
5868
$failure
5969
)
6070
: $this->trans(
61-
"$base.invalid_resource",
71+
sprintf('%s.invalid_resource', self::BASE),
6272
'":resource" doesn\'t exists.',
6373
$failure
6474
),

src/Requests/Rules/Includes.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66
use Ark4ne\JsonApi\Resources\Skeleton;
77
use Ark4ne\JsonApi\Support\Includes as SupportIncludes;
88
use Illuminate\Contracts\Validation\Rule;
9+
use Illuminate\Contracts\Validation\ValidationRule;
910

1011
/**
1112
* @template T as \Ark4ne\JsonApi\Resources\JsonApiResource
1213
*/
13-
class Includes implements Rule
14+
class Includes implements ValidationRule
1415
{
1516
use UseTrans;
1617

18+
private const BASE = 'validation.custom.jsonapi.fields';
19+
1720
/**
1821
* @var array<int, array{":include": string, ":relation": string}>>
1922
*/
@@ -27,32 +30,39 @@ public function __construct(
2730
) {
2831
}
2932

30-
public function passes($attribute, $value): bool
33+
public function validate(string $attribute, mixed $value, \Closure $fail): void
3134
{
3235
if (!is_string($value)) {
33-
return false;
36+
$fail($this->trans(
37+
sprintf('%s.invalid', self::BASE),
38+
'The selected :attribute is invalid.'
39+
));
40+
return;
3441
}
3542

3643
$desired = SupportIncludes::parse($value);
3744
$schema = $this->resource::schema();
3845

39-
return $this->assert($schema, $desired);
46+
if (!$this->assert($schema, $desired)) {
47+
foreach ($this->message() as $message) {
48+
$fail($message);
49+
}
50+
}
4051
}
4152

4253
/**
4354
* @return array<string>
4455
*/
45-
public function message(): array
56+
private function message(): array
4657
{
47-
$base = 'validation.custom.jsonapi.includes';
4858
$message = $this->trans(
49-
"$base.invalid",
59+
sprintf('%s.invalid', self::BASE),
5060
'The selected :attribute is invalid.'
5161
);
5262

53-
return array_merge($message, ...array_map(
63+
return array_merge([$message], array_map(
5464
fn($failure) => $this->trans(
55-
"$base.invalid_includes",
65+
sprintf('%s.invalid_includes', self::BASE),
5666
'":include" doesn\'t have relationship ":relation".',
5767
$failure
5868
),

src/Requests/Rules/Traits/UseTrans.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@ trait UseTrans
1313
* @param string $default
1414
* @param array<string, string> $replace
1515
*
16-
* @return array<string>
16+
* @return string
1717
*/
18-
protected function trans(?string $key, string $default, array $replace = []): array
18+
protected function trans(?string $key, string $default, array $replace = []): string
1919
{
2020
$message = trans($key);
2121

22-
return array_map(
23-
static fn($msg) => Str::replace(array_keys($replace), array_values($replace), $msg),
24-
$message === $key ? [$default] : (array)$message
25-
);
22+
return Str::replace(array_keys($replace), array_values($replace), $message === $key ? $default : $message);
2623
}
2724
}

tests/Unit/Requests/Rules/FieldsTest.php

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,52 @@
33
namespace Test\Unit\Requests\Rules;
44

55
use Ark4ne\JsonApi\Requests\Rules\Fields;
6+
use PHPUnit\Framework\Attributes\DataProvider;
67
use Test\app\Http\Resources\UserResource;
78
use Test\TestCase;
89

910
class FieldsTest extends TestCase
1011
{
11-
public function testPasses()
12+
/**
13+
* @dataProvider validationDataProvider
14+
*/
15+
#[DataProvider('validationDataProvider')]
16+
public function testPasses(mixed $values, array $expected)
1217
{
1318
$rule = new Fields(UserResource::class);
19+
$errors = [];
1420

15-
$this->assertFalse($rule->passes(null, 'user,post'));
16-
17-
$this->assertTrue($rule->passes(null, [
18-
'user' => 'name,email',
19-
'post' => 'content',
20-
]));
21-
22-
$this->assertFalse($rule->passes(null, [
23-
'user' => 'name,email',
24-
'unknown' => 'content',
25-
]));
26-
27-
$this->assertFalse($rule->passes(null, [
28-
'user' => 'name,unknown',
29-
'post' => 'content',
30-
]));
21+
$rule->validate('fields', $values, function($message) use (&$errors) {
22+
$errors[] = $message;
23+
});
24+
$this->assertEquals($expected, $errors);
25+
}
3126

32-
$this->assertFalse($rule->passes(null, [
33-
'user' => 'name,email',
34-
'post' => 'unknown',
35-
]));
27+
public static function validationDataProvider()
28+
{
29+
return [
30+
// not expected type
31+
['string', ['The selected :attribute is invalid.']],
32+
[123, ['The selected :attribute is invalid.']],
33+
[null, ['The selected :attribute is invalid.']],
34+
35+
// valid cases
36+
[['user' => 'name,email'], []],
37+
[['user' => 'name,email', 'post' => 'content'], []],
38+
39+
// invalid cases
40+
[['user' => 'name,email', 'unknown' => 'content'], [
41+
'The selected :attribute is invalid.',
42+
'"unknown" doesn\'t exists.',
43+
]],
44+
[['user' => 'name,unknown', 'post' => 'content'], [
45+
'The selected :attribute is invalid.',
46+
'"user" doesn\'t have fields "unknown".'
47+
]],
48+
[['user' => 'name,email', 'post' => 'unknown'], [
49+
'The selected :attribute is invalid.',
50+
'"post" doesn\'t have fields "unknown".'
51+
]],
52+
];
3653
}
3754
}

tests/Unit/Requests/Rules/IncludesTest.php

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,56 @@
33
namespace Test\Unit\Requests\Rules;
44

55
use Ark4ne\JsonApi\Requests\Rules\Includes;
6+
use PHPUnit\Framework\Attributes\DataProvider;
67
use Test\app\Http\Resources\UserResource;
78
use Test\TestCase;
89

910
class IncludesTest extends TestCase
1011
{
11-
public function testPasses()
12+
/**
13+
* @dataProvider validationDataProvider
14+
*/
15+
#[DataProvider('validationDataProvider')]
16+
public function testPasses(mixed $values, array $expected)
1217
{
1318
$rule = new Includes(UserResource::class);
19+
$errors = [];
1420

15-
$this->assertFalse($rule->passes(null, [
16-
'posts',
17-
'posts.user',
18-
'posts.user.comments',
19-
'posts.user.posts'
20-
]));
21+
$rule->validate('fields', $values, function($message) use (&$errors) {
22+
$errors[] = $message;
23+
});
24+
$this->assertEquals($expected, $errors);
25+
}
2126

22-
$this->assertTrue($rule->passes(null, implode(',', [
23-
'posts',
24-
'posts.user',
25-
'posts.user.comments',
26-
'posts.user.posts'
27-
])));
27+
public static function validationDataProvider()
28+
{
29+
return [
30+
// not expected type
31+
[123, ['The selected :attribute is invalid.']],
32+
[null, ['The selected :attribute is invalid.']],
2833

29-
$this->assertFalse($rule->passes(null, implode(',', [
30-
'posts',
31-
'unknown',
32-
])));
34+
// valid cases
35+
[implode(',', [
36+
'posts',
37+
'posts.user',
38+
'posts.user.comments',
39+
'posts.user.posts'
40+
]), []],
3341

34-
$this->assertFalse($rule->passes(null, implode(',', [
35-
'posts.unknown',
36-
])));
42+
// invalid cases
43+
[implode(',', [
44+
'posts',
45+
'unknown',
46+
]), [
47+
'The selected :attribute is invalid.',
48+
'"user" doesn\'t have relationship "unknown".',
49+
]],
50+
[implode(',', [
51+
'posts.unknown',
52+
]), [
53+
'The selected :attribute is invalid.',
54+
'"posts" doesn\'t have relationship "unknown".'
55+
]],
56+
];
3757
}
3858
}

0 commit comments

Comments
 (0)