Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
96.23% |
51 / 53 |
|
77.78% |
7 / 9 |
CRAP | |
0.00% |
0 / 1 |
| Validators | |
96.23% |
51 / 53 |
|
77.78% |
7 / 9 |
12 | |
0.00% |
0 / 1 |
| registry | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
| messages | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
1 | |||
| in | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| escape | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| minItems | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
| maxItems | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
| color | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| rfc3339 | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| timezone | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Cosray\Validation; |
| 6 | |
| 7 | use Celema\Sire\Contract\NamedArguments; |
| 8 | use Celema\Sire\Contract\Rule; |
| 9 | use Celema\Sire\Contract\ValidatesEmpty; |
| 10 | use Celema\Sire\Contract\Validation as ValidationContract; |
| 11 | use Celema\Sire\Contract\Value; |
| 12 | use Celema\Sire\RuleRegistry; |
| 13 | use Celema\Sire\Validation; |
| 14 | use Cosray\DateTime\Codec; |
| 15 | use Cosray\Value\Color; |
| 16 | use Override; |
| 17 | |
| 18 | final class Validators |
| 19 | { |
| 20 | public static function registry(): RuleRegistry |
| 21 | { |
| 22 | return RuleRegistry::withDefaults()->withMany([ |
| 23 | 'color' => self::color(), |
| 24 | 'minitems' => self::minItems(), |
| 25 | 'maxitems' => self::maxItems(), |
| 26 | 'rfc3339' => self::rfc3339(), |
| 27 | 'timezone' => self::timezone(), |
| 28 | ]); |
| 29 | } |
| 30 | |
| 31 | /** |
| 32 | * Translations of the messages Sire produces itself, by message key. |
| 33 | * Cosray's own rules translate their messages. |
| 34 | * |
| 35 | * @return array<string, string> |
| 36 | */ |
| 37 | public static function messages(): array |
| 38 | { |
| 39 | $messages = [ |
| 40 | 'extra' => __('validation:extra'), |
| 41 | 'missing' => __('validation:missing'), |
| 42 | 'null' => __('validation:null'), |
| 43 | 'rule.email' => __('validation:email'), |
| 44 | 'rule.in' => __('validation:in'), |
| 45 | 'rule.max' => __('validation:max'), |
| 46 | 'rule.maxlen' => __('validation:maxlen'), |
| 47 | 'rule.min' => __('validation:min'), |
| 48 | 'rule.minlen' => __('validation:minlen'), |
| 49 | 'rule.regex' => __('validation:regex'), |
| 50 | 'rule.required' => __('validation:required'), |
| 51 | 'type.bool' => __('validation:type-bool'), |
| 52 | 'type.float' => __('validation:type-float'), |
| 53 | 'type.int' => __('validation:type-int'), |
| 54 | 'type.list' => __('validation:type-list'), |
| 55 | 'type.number' => __('validation:type-number'), |
| 56 | 'type.shape' => __('validation:type-shape'), |
| 57 | 'type.string' => __('validation:type-string'), |
| 58 | ]; |
| 59 | |
| 60 | // Without a translation, __() returns the id; Sire's English reads better. |
| 61 | return array_filter($messages, static fn(string $message): bool => !str_starts_with($message, 'validation:')); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * An `in:` rule accepting exactly the given values, which may contain |
| 66 | * commas, colons, quotes and backslashes. Sire parses the definition |
| 67 | * twice (the rule parser splits on `:`, the rule on `,`) and each pass |
| 68 | * consumes one level of backslash escapes, so values are escaped twice. |
| 69 | * |
| 70 | * @param list<scalar> $values |
| 71 | */ |
| 72 | public static function in(array $values): string |
| 73 | { |
| 74 | $list = implode(',', array_map( |
| 75 | static fn(mixed $value): string => self::escape((string) $value, ','), |
| 76 | $values, |
| 77 | )); |
| 78 | |
| 79 | return 'in:' . self::escape($list, ':'); |
| 80 | } |
| 81 | |
| 82 | private static function escape(string $value, string $delimiter): string |
| 83 | { |
| 84 | return addcslashes($value, '\\"\'' . $delimiter); |
| 85 | } |
| 86 | |
| 87 | private static function minItems(): Rule |
| 88 | { |
| 89 | return new class implements Rule, ValidatesEmpty, NamedArguments { |
| 90 | #[Override] |
| 91 | public string $message { |
| 92 | get => __('validation:min-items'); |
| 93 | } |
| 94 | |
| 95 | #[Override] |
| 96 | public array $arguments { |
| 97 | get => ['min']; |
| 98 | } |
| 99 | |
| 100 | #[Override] |
| 101 | public function validate(Value $value, string ...$args): ValidationContract |
| 102 | { |
| 103 | if (!is_array($value->value)) { |
| 104 | return Validation::invalid(); |
| 105 | } |
| 106 | |
| 107 | return Validation::from(count($value->value) >= (int) ($args[0] ?? 0)); |
| 108 | } |
| 109 | }; |
| 110 | } |
| 111 | |
| 112 | private static function maxItems(): Rule |
| 113 | { |
| 114 | return new class implements Rule, NamedArguments { |
| 115 | #[Override] |
| 116 | public string $message { |
| 117 | get => __('validation:max-items'); |
| 118 | } |
| 119 | |
| 120 | #[Override] |
| 121 | public array $arguments { |
| 122 | get => ['max']; |
| 123 | } |
| 124 | |
| 125 | #[Override] |
| 126 | public function validate(Value $value, string ...$args): ValidationContract |
| 127 | { |
| 128 | if (!is_array($value->value)) { |
| 129 | return Validation::invalid(); |
| 130 | } |
| 131 | |
| 132 | return Validation::from(count($value->value) <= (int) ($args[0] ?? 0)); |
| 133 | } |
| 134 | }; |
| 135 | } |
| 136 | |
| 137 | private static function color(): Rule |
| 138 | { |
| 139 | return new class implements Rule { |
| 140 | #[Override] |
| 141 | public string $message { |
| 142 | get => __('validation:color'); |
| 143 | } |
| 144 | |
| 145 | #[Override] |
| 146 | public function validate(Value $value, string ...$args): ValidationContract |
| 147 | { |
| 148 | return Validation::from(Color::normalize($value->value) !== null); |
| 149 | } |
| 150 | }; |
| 151 | } |
| 152 | |
| 153 | private static function rfc3339(): Rule |
| 154 | { |
| 155 | return new class implements Rule { |
| 156 | #[Override] |
| 157 | public string $message { |
| 158 | get => __('validation:rfc3339'); |
| 159 | } |
| 160 | |
| 161 | #[Override] |
| 162 | public function validate(Value $value, string ...$args): ValidationContract |
| 163 | { |
| 164 | return Validation::from(is_string($value->value) && Codec::parse($value->value) !== null); |
| 165 | } |
| 166 | }; |
| 167 | } |
| 168 | |
| 169 | private static function timezone(): Rule |
| 170 | { |
| 171 | return new class implements Rule { |
| 172 | #[Override] |
| 173 | public string $message { |
| 174 | get => __('validation:timezone'); |
| 175 | } |
| 176 | |
| 177 | #[Override] |
| 178 | public function validate(Value $value, string ...$args): ValidationContract |
| 179 | { |
| 180 | return Validation::from(Codec::timezone($value->value) !== null); |
| 181 | } |
| 182 | }; |
| 183 | } |
| 184 | } |