From c9975667199acc8422e0bdd8fd7db510314c7269 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 09:40:42 -0500 Subject: [PATCH 01/35] feat: add message method to validation rules for custom error messages --- src/Validation/Contracts/Rule.php | 2 ++ src/Validation/Rules/Between.php | 19 +++++++++++++++++++ src/Validation/Rules/Confirmed.php | 8 ++++++++ src/Validation/Rules/Dates/After.php | 5 +++++ src/Validation/Rules/Dates/AfterOrEqual.php | 5 +++++ src/Validation/Rules/Dates/AfterOrEqualTo.php | 8 ++++++++ src/Validation/Rules/Dates/AfterTo.php | 8 ++++++++ src/Validation/Rules/Dates/Before.php | 5 +++++ src/Validation/Rules/Dates/BeforeOrEqual.php | 5 +++++ .../Rules/Dates/BeforeOrEqualTo.php | 8 ++++++++ src/Validation/Rules/Dates/BeforeTo.php | 8 ++++++++ src/Validation/Rules/Dates/Equal.php | 5 +++++ src/Validation/Rules/Dates/EqualTo.php | 8 ++++++++ src/Validation/Rules/Dates/Format.php | 5 +++++ src/Validation/Rules/Dates/IsDate.php | 5 +++++ src/Validation/Rules/DoesNotEndWith.php | 8 ++++++++ src/Validation/Rules/DoesNotStartWith.php | 8 ++++++++ src/Validation/Rules/EndsWith.php | 8 ++++++++ src/Validation/Rules/Exists.php | 7 +++++++ src/Validation/Rules/In.php | 8 ++++++++ src/Validation/Rules/IsArray.php | 5 +++++ src/Validation/Rules/IsBool.php | 5 +++++ src/Validation/Rules/IsCollection.php | 5 +++++ src/Validation/Rules/IsDictionary.php | 5 +++++ src/Validation/Rules/IsEmail.php | 5 +++++ src/Validation/Rules/IsFile.php | 5 +++++ src/Validation/Rules/IsList.php | 5 +++++ src/Validation/Rules/IsString.php | 5 +++++ src/Validation/Rules/IsUrl.php | 5 +++++ src/Validation/Rules/Max.php | 18 ++++++++++++++++++ src/Validation/Rules/Mimes.php | 8 ++++++++ src/Validation/Rules/Min.php | 18 ++++++++++++++++++ src/Validation/Rules/NotIn.php | 8 ++++++++ src/Validation/Rules/Nullable.php | 6 ++++++ src/Validation/Rules/Optional.php | 5 +++++ src/Validation/Rules/RegEx.php | 7 +++++++ src/Validation/Rules/Required.php | 5 +++++ src/Validation/Rules/Rule.php | 5 +++++ src/Validation/Rules/Size.php | 18 ++++++++++++++++++ src/Validation/Rules/StartsWith.php | 8 ++++++++ src/Validation/Rules/Ulid.php | 5 +++++ src/Validation/Rules/Unique.php | 7 +++++++ src/Validation/Rules/Uuid.php | 5 +++++ 43 files changed, 311 insertions(+) diff --git a/src/Validation/Contracts/Rule.php b/src/Validation/Contracts/Rule.php index ef90281e..f52a7761 100644 --- a/src/Validation/Contracts/Rule.php +++ b/src/Validation/Contracts/Rule.php @@ -13,4 +13,6 @@ public function setField(string $field): self; public function setData(Dot|array $data): self; public function passes(): bool; + + public function message(): string|null; } diff --git a/src/Validation/Rules/Between.php b/src/Validation/Rules/Between.php index 3cbfbc5a..b1c11038 100644 --- a/src/Validation/Rules/Between.php +++ b/src/Validation/Rules/Between.php @@ -21,4 +21,23 @@ public function passes(): bool return $value >= $this->min && $value <= $this->max; } + + public function message(): string|null + { + $value = $this->data->get($this->field) ?? null; + $type = gettype($value); + + $key = match ($type) { + 'string' => 'validation.between.string', + 'array' => 'validation.between.array', + 'object' => 'validation.between.file', + default => 'validation.between.numeric', + }; + + return trans($key, [ + 'field' => $this->field, + 'min' => $this->min, + 'max' => $this->max, + ]); + } } diff --git a/src/Validation/Rules/Confirmed.php b/src/Validation/Rules/Confirmed.php index 99455a9d..f84f502a 100644 --- a/src/Validation/Rules/Confirmed.php +++ b/src/Validation/Rules/Confirmed.php @@ -20,4 +20,12 @@ public function passes(): bool && $confirmation !== null && $original === $confirmation; } + + public function message(): string|null + { + return trans('validation.confirmed', [ + 'field' => $this->field, + 'other' => $this->confirmationField, + ]); + } } diff --git a/src/Validation/Rules/Dates/After.php b/src/Validation/Rules/Dates/After.php index 24270a43..0ab6689b 100644 --- a/src/Validation/Rules/Dates/After.php +++ b/src/Validation/Rules/Dates/After.php @@ -12,4 +12,9 @@ public function passes(): bool { return Date::parse($this->getValue())->greaterThan($this->date); } + + public function message(): string|null + { + return trans('validation.date.after', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Dates/AfterOrEqual.php b/src/Validation/Rules/Dates/AfterOrEqual.php index 5fb598e2..110633ea 100644 --- a/src/Validation/Rules/Dates/AfterOrEqual.php +++ b/src/Validation/Rules/Dates/AfterOrEqual.php @@ -12,4 +12,9 @@ public function passes(): bool { return Date::parse($this->getValue())->greaterThanOrEqualTo($this->date); } + + public function message(): string|null + { + return trans('validation.date.after_or_equal', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Dates/AfterOrEqualTo.php b/src/Validation/Rules/Dates/AfterOrEqualTo.php index d8b4f182..d6a91f8d 100644 --- a/src/Validation/Rules/Dates/AfterOrEqualTo.php +++ b/src/Validation/Rules/Dates/AfterOrEqualTo.php @@ -15,4 +15,12 @@ public function passes(): bool return Date::parse($date)->greaterThanOrEqualTo($relatedDate); } + + public function message(): string|null + { + return trans('validation.date.after_or_equal_to', [ + 'field' => $this->field, + 'other' => $this->relatedField, + ]); + } } diff --git a/src/Validation/Rules/Dates/AfterTo.php b/src/Validation/Rules/Dates/AfterTo.php index 927bedf6..fe6bcac1 100644 --- a/src/Validation/Rules/Dates/AfterTo.php +++ b/src/Validation/Rules/Dates/AfterTo.php @@ -15,4 +15,12 @@ public function passes(): bool return Date::parse($date)->greaterThan($relatedDate); } + + public function message(): string|null + { + return trans('validation.date.after_to', [ + 'field' => $this->field, + 'other' => $this->relatedField, + ]); + } } diff --git a/src/Validation/Rules/Dates/Before.php b/src/Validation/Rules/Dates/Before.php index 9449ab85..377747de 100644 --- a/src/Validation/Rules/Dates/Before.php +++ b/src/Validation/Rules/Dates/Before.php @@ -12,4 +12,9 @@ public function passes(): bool { return Date::parse($this->getValue())->lessThan($this->date); } + + public function message(): string|null + { + return trans('validation.date.before', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Dates/BeforeOrEqual.php b/src/Validation/Rules/Dates/BeforeOrEqual.php index 489fb328..f7f98d3d 100644 --- a/src/Validation/Rules/Dates/BeforeOrEqual.php +++ b/src/Validation/Rules/Dates/BeforeOrEqual.php @@ -12,4 +12,9 @@ public function passes(): bool { return Date::parse($this->getValue())->lessThanOrEqualTo($this->date); } + + public function message(): string|null + { + return trans('validation.date.before_or_equal', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Dates/BeforeOrEqualTo.php b/src/Validation/Rules/Dates/BeforeOrEqualTo.php index 4267155d..047e8201 100644 --- a/src/Validation/Rules/Dates/BeforeOrEqualTo.php +++ b/src/Validation/Rules/Dates/BeforeOrEqualTo.php @@ -15,4 +15,12 @@ public function passes(): bool return Date::parse($date)->lessThanOrEqualTo($relatedDate); } + + public function message(): string|null + { + return trans('validation.date.before_or_equal_to', [ + 'field' => $this->field, + 'other' => $this->relatedField, + ]); + } } diff --git a/src/Validation/Rules/Dates/BeforeTo.php b/src/Validation/Rules/Dates/BeforeTo.php index b00d2710..a16200c5 100644 --- a/src/Validation/Rules/Dates/BeforeTo.php +++ b/src/Validation/Rules/Dates/BeforeTo.php @@ -15,4 +15,12 @@ public function passes(): bool return Date::parse($date)->lessThan($relatedDate); } + + public function message(): string|null + { + return trans('validation.date.before_to', [ + 'field' => $this->field, + 'other' => $this->relatedField, + ]); + } } diff --git a/src/Validation/Rules/Dates/Equal.php b/src/Validation/Rules/Dates/Equal.php index 3ee43b0f..31b2e9d5 100644 --- a/src/Validation/Rules/Dates/Equal.php +++ b/src/Validation/Rules/Dates/Equal.php @@ -21,4 +21,9 @@ public function passes(): bool { return Date::parse($this->getValue())->equalTo($this->date); } + + public function message(): string|null + { + return trans('validation.date.equal', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Dates/EqualTo.php b/src/Validation/Rules/Dates/EqualTo.php index ca496c16..af3740a3 100644 --- a/src/Validation/Rules/Dates/EqualTo.php +++ b/src/Validation/Rules/Dates/EqualTo.php @@ -15,4 +15,12 @@ public function passes(): bool return $date->equalTo($relatedDate); } + + public function message(): string|null + { + return trans('validation.date.equal_to', [ + 'field' => $this->field, + 'other' => $this->relatedField, + ]); + } } diff --git a/src/Validation/Rules/Dates/Format.php b/src/Validation/Rules/Dates/Format.php index 45bd3593..7deb00cb 100644 --- a/src/Validation/Rules/Dates/Format.php +++ b/src/Validation/Rules/Dates/Format.php @@ -20,4 +20,9 @@ public function passes(): bool return $dateTime instanceof DateTime; } + + public function message(): string|null + { + return trans('validation.date.format', ['field' => $this->field, 'format' => $this->format]); + } } diff --git a/src/Validation/Rules/Dates/IsDate.php b/src/Validation/Rules/Dates/IsDate.php index 54590677..dbe07188 100644 --- a/src/Validation/Rules/Dates/IsDate.php +++ b/src/Validation/Rules/Dates/IsDate.php @@ -27,4 +27,9 @@ public function passes(): bool } } + + public function message(): string|null + { + return trans('validation.date.is_date', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/DoesNotEndWith.php b/src/Validation/Rules/DoesNotEndWith.php index cbe194a4..99820817 100644 --- a/src/Validation/Rules/DoesNotEndWith.php +++ b/src/Validation/Rules/DoesNotEndWith.php @@ -10,4 +10,12 @@ public function passes(): bool { return ! parent::passes(); } + + public function message(): string|null + { + return trans('validation.does_not_end_with', [ + 'field' => $this->field, + 'values' => $this->needle, + ]); + } } diff --git a/src/Validation/Rules/DoesNotStartWith.php b/src/Validation/Rules/DoesNotStartWith.php index fd605ebc..96351993 100644 --- a/src/Validation/Rules/DoesNotStartWith.php +++ b/src/Validation/Rules/DoesNotStartWith.php @@ -10,4 +10,12 @@ public function passes(): bool { return ! parent::passes(); } + + public function message(): string|null + { + return trans('validation.does_not_start_with', [ + 'field' => $this->field, + 'values' => $this->needle, + ]); + } } diff --git a/src/Validation/Rules/EndsWith.php b/src/Validation/Rules/EndsWith.php index f94a2ad3..3d96fb52 100644 --- a/src/Validation/Rules/EndsWith.php +++ b/src/Validation/Rules/EndsWith.php @@ -10,4 +10,12 @@ public function passes(): bool { return str_ends_with($this->getValue(), $this->needle); } + + public function message(): string|null + { + return trans('validation.ends_with', [ + 'field' => $this->field, + 'values' => $this->needle, + ]); + } } diff --git a/src/Validation/Rules/Exists.php b/src/Validation/Rules/Exists.php index 2f081467..92c80df4 100644 --- a/src/Validation/Rules/Exists.php +++ b/src/Validation/Rules/Exists.php @@ -20,4 +20,11 @@ public function passes(): bool ->whereEqual($this->column ?? $this->field, $this->getValue()) ->exists(); } + + public function message(): string|null + { + return trans('validation.exists', [ + 'field' => $this->field, + ]); + } } diff --git a/src/Validation/Rules/In.php b/src/Validation/Rules/In.php index a268a854..6eeba192 100644 --- a/src/Validation/Rules/In.php +++ b/src/Validation/Rules/In.php @@ -17,4 +17,12 @@ public function passes(): bool { return in_array($this->getValue(), $this->haystack, true); } + + public function message(): string|null + { + return trans('validation.in', [ + 'field' => $this->field, + 'values' => implode(', ', $this->haystack), + ]); + } } diff --git a/src/Validation/Rules/IsArray.php b/src/Validation/Rules/IsArray.php index be7353e7..c4ecd03f 100644 --- a/src/Validation/Rules/IsArray.php +++ b/src/Validation/Rules/IsArray.php @@ -12,4 +12,9 @@ public function passes(): bool { return is_array($this->getValue()); } + + public function message(): string|null + { + return trans('validation.array', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsBool.php b/src/Validation/Rules/IsBool.php index 71752a74..88951b74 100644 --- a/src/Validation/Rules/IsBool.php +++ b/src/Validation/Rules/IsBool.php @@ -12,4 +12,9 @@ public function passes(): bool { return in_array($this->getValue(), [true, false, 'true', 'false', 1, 0, '1', '0'], true); } + + public function message(): string|null + { + return trans('validation.boolean', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsCollection.php b/src/Validation/Rules/IsCollection.php index 7e3363c7..5451ca18 100644 --- a/src/Validation/Rules/IsCollection.php +++ b/src/Validation/Rules/IsCollection.php @@ -17,4 +17,9 @@ public function passes(): bool && array_is_list($value) && ! $this->isScalar($value); } + + public function message(): string|null + { + return trans('validation.collection', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsDictionary.php b/src/Validation/Rules/IsDictionary.php index f7b26395..09691526 100644 --- a/src/Validation/Rules/IsDictionary.php +++ b/src/Validation/Rules/IsDictionary.php @@ -17,4 +17,9 @@ public function passes(): bool && ! array_is_list($value) && $this->isScalar($value); } + + public function message(): string|null + { + return trans('validation.dictionary', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsEmail.php b/src/Validation/Rules/IsEmail.php index 3f8a1d65..1b887b3c 100644 --- a/src/Validation/Rules/IsEmail.php +++ b/src/Validation/Rules/IsEmail.php @@ -35,4 +35,9 @@ public function pusValidation(EmailValidation $emailValidation): self return $this; } + + public function message(): string|null + { + return trans('validation.email', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsFile.php b/src/Validation/Rules/IsFile.php index 301c576e..4efd2b2e 100644 --- a/src/Validation/Rules/IsFile.php +++ b/src/Validation/Rules/IsFile.php @@ -14,4 +14,9 @@ public function passes(): bool return $value instanceof BufferedFile; } + + public function message(): string|null + { + return trans('validation.file', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsList.php b/src/Validation/Rules/IsList.php index 25b5ecc7..7c6e2b3b 100644 --- a/src/Validation/Rules/IsList.php +++ b/src/Validation/Rules/IsList.php @@ -28,4 +28,9 @@ protected function isScalar(array $data): bool return true; } + + public function message(): string|null + { + return trans('validation.list', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsString.php b/src/Validation/Rules/IsString.php index e462505b..b6a2e023 100644 --- a/src/Validation/Rules/IsString.php +++ b/src/Validation/Rules/IsString.php @@ -12,4 +12,9 @@ public function passes(): bool { return is_string($this->getValue()); } + + public function message(): string|null + { + return trans('validation.string', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/IsUrl.php b/src/Validation/Rules/IsUrl.php index 14948010..3a9bcdce 100644 --- a/src/Validation/Rules/IsUrl.php +++ b/src/Validation/Rules/IsUrl.php @@ -11,4 +11,9 @@ public function passes(): bool return parent::passes() && filter_var($this->getValue(), FILTER_VALIDATE_URL) !== false; } + + public function message(): string|null + { + return trans('validation.url', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Max.php b/src/Validation/Rules/Max.php index 63a4ab98..c4a5739f 100644 --- a/src/Validation/Rules/Max.php +++ b/src/Validation/Rules/Max.php @@ -10,4 +10,22 @@ public function passes(): bool { return $this->getValue() <= $this->limit; } + + public function message(): string|null + { + $value = $this->data->get($this->field) ?? null; + $type = gettype($value); + + $key = match ($type) { + 'string' => 'validation.max.string', + 'array' => 'validation.max.array', + 'object' => 'validation.max.file', + default => 'validation.max.numeric', + }; + + return trans($key, [ + 'field' => $this->field, + 'max' => $this->limit, + ]); + } } diff --git a/src/Validation/Rules/Mimes.php b/src/Validation/Rules/Mimes.php index 311c7ede..e426ae3b 100644 --- a/src/Validation/Rules/Mimes.php +++ b/src/Validation/Rules/Mimes.php @@ -17,4 +17,12 @@ public function passes(): bool { return in_array($this->getValue()->getMimeType(), $this->haystack, true); } + + public function message(): string|null + { + return trans('validation.mimes', [ + 'field' => $this->field, + 'values' => implode(', ', $this->haystack), + ]); + } } diff --git a/src/Validation/Rules/Min.php b/src/Validation/Rules/Min.php index 94b45e86..e02c64ba 100644 --- a/src/Validation/Rules/Min.php +++ b/src/Validation/Rules/Min.php @@ -10,4 +10,22 @@ public function passes(): bool { return $this->getValue() >= $this->limit; } + + public function message(): string|null + { + $value = $this->data->get($this->field) ?? null; + $type = gettype($value); + + $key = match ($type) { + 'string' => 'validation.min.string', + 'array' => 'validation.min.array', + 'object' => 'validation.min.file', + default => 'validation.min.numeric', + }; + + return trans($key, [ + 'field' => $this->field, + 'min' => $this->limit, + ]); + } } diff --git a/src/Validation/Rules/NotIn.php b/src/Validation/Rules/NotIn.php index 14968f7d..1abb0b4b 100644 --- a/src/Validation/Rules/NotIn.php +++ b/src/Validation/Rules/NotIn.php @@ -10,4 +10,12 @@ public function passes(): bool { return ! parent::passes(); } + + public function message(): string|null + { + return trans('validation.not_in', [ + 'field' => $this->field, + 'values' => implode(', ', $this->haystack), + ]); + } } diff --git a/src/Validation/Rules/Nullable.php b/src/Validation/Rules/Nullable.php index 1955939c..982f183b 100644 --- a/src/Validation/Rules/Nullable.php +++ b/src/Validation/Rules/Nullable.php @@ -25,4 +25,10 @@ public function skip(): bool { return is_null($this->getValue()); } + + public function message(): string|null + { + // Nullable itself doesn't produce an error message; defer to Required if fails + return null; + } } diff --git a/src/Validation/Rules/Optional.php b/src/Validation/Rules/Optional.php index 1cfb7383..572bac12 100644 --- a/src/Validation/Rules/Optional.php +++ b/src/Validation/Rules/Optional.php @@ -19,4 +19,9 @@ public function skip(): bool { return ! $this->data->has($this->field); } + + public function message(): string|null + { + return null; // Optional never triggers its own message + } } diff --git a/src/Validation/Rules/RegEx.php b/src/Validation/Rules/RegEx.php index 9b2c3a90..56bc9285 100644 --- a/src/Validation/Rules/RegEx.php +++ b/src/Validation/Rules/RegEx.php @@ -15,4 +15,11 @@ public function passes(): bool { return preg_match($this->regEx, $this->getValue()) > 0; } + + public function message(): string|null + { + return trans('validation.regex', [ + 'field' => $this->field, + ]); + } } diff --git a/src/Validation/Rules/Required.php b/src/Validation/Rules/Required.php index baa1cdaf..8761b5c9 100644 --- a/src/Validation/Rules/Required.php +++ b/src/Validation/Rules/Required.php @@ -32,4 +32,9 @@ public function skip(): bool { return false; } + + public function message(): string|null + { + return trans('validation.required', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Rule.php b/src/Validation/Rules/Rule.php index 179c63f8..cc1794e7 100644 --- a/src/Validation/Rules/Rule.php +++ b/src/Validation/Rules/Rule.php @@ -51,4 +51,9 @@ protected function getValueType(): string { return gettype($this->data->get($this->field) ?? null); } + + public function message(): string|null + { + return trans('validation.rule', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Size.php b/src/Validation/Rules/Size.php index 4420ab5f..bea1ce3a 100644 --- a/src/Validation/Rules/Size.php +++ b/src/Validation/Rules/Size.php @@ -48,4 +48,22 @@ private function resolveCountableObject(object $value): float|int return $count; } + + public function message(): string|null + { + $value = $this->data->get($this->field) ?? null; + $type = gettype($value); + + $key = match ($type) { + 'string' => 'validation.size.string', + 'array' => 'validation.size.array', + 'object' => 'validation.size.file', // treat countable / file objects as file + default => 'validation.size.numeric', + }; + + return trans($key, [ + 'field' => $this->field, + 'size' => $this->limit, + ]); + } } diff --git a/src/Validation/Rules/StartsWith.php b/src/Validation/Rules/StartsWith.php index d3e9d5b4..b46fd0ac 100644 --- a/src/Validation/Rules/StartsWith.php +++ b/src/Validation/Rules/StartsWith.php @@ -15,4 +15,12 @@ public function passes(): bool { return str_starts_with($this->getValue(), $this->needle); } + + public function message(): string|null + { + return trans('validation.starts_with', [ + 'field' => $this->field, + 'values' => $this->needle, + ]); + } } diff --git a/src/Validation/Rules/Ulid.php b/src/Validation/Rules/Ulid.php index f2cca00a..c2e37208 100644 --- a/src/Validation/Rules/Ulid.php +++ b/src/Validation/Rules/Ulid.php @@ -13,4 +13,9 @@ public function passes(): bool return Str::isUlid($this->getValue()); } + + public function message(): string|null + { + return trans('validation.ulid', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Unique.php b/src/Validation/Rules/Unique.php index 344cc1f7..0fc715ab 100644 --- a/src/Validation/Rules/Unique.php +++ b/src/Validation/Rules/Unique.php @@ -12,4 +12,11 @@ public function passes(): bool ->whereEqual($this->column ?? $this->field, $this->getValue()) ->count() === 0; } + + public function message(): string|null + { + return trans('validation.unique', [ + 'field' => $this->field, + ]); + } } diff --git a/src/Validation/Rules/Uuid.php b/src/Validation/Rules/Uuid.php index b8fe4733..1b058a88 100644 --- a/src/Validation/Rules/Uuid.php +++ b/src/Validation/Rules/Uuid.php @@ -12,4 +12,9 @@ public function passes(): bool { return Str::isUuid($this->getValue()); } + + public function message(): string|null + { + return trans('validation.uuid', ['field' => $this->field]); + } } From c6302a23eae6ce055e1aca8063f64667bca1b6fd Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 09:42:56 -0500 Subject: [PATCH 02/35] feat: update failing rules to store error messages instead of rule classes --- src/Validation/Validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index 2173eb36..c0c16482 100755 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -177,7 +177,7 @@ protected function checkRule(string $field, Rule $rule, string|int|null $parent $passes = $rule->passes(); if (! $passes) { - $this->failing[$field][] = $rule::class; + $this->failing[$field][] = $rule->message(); } $this->validated[] = $field; From 8c5d0d4640e5a44cf65aaa08663e15717dbe78a4 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 10:30:33 -0500 Subject: [PATCH 03/35] test(refactor): update validation tests to use error message keys instead of rule classes --- tests/Unit/Validation/ValidatorTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Validation/ValidatorTest.php b/tests/Unit/Validation/ValidatorTest.php index 2ef7c082..997a070e 100644 --- a/tests/Unit/Validation/ValidatorTest.php +++ b/tests/Unit/Validation/ValidatorTest.php @@ -102,7 +102,7 @@ public function toArray(): array expect($validator->passes())->toBeFalse(); expect($validator->failing())->toBe([ - 'name' => [Required::class], + 'name' => ['validation.required'], ]); expect($validator->invalid())->toBe([ @@ -174,8 +174,8 @@ public function toArray(): array expect($validator->passes())->toBeFalsy(); expect($validator->failing())->toBe([ - 'customer' => [IsDictionary::class], - 'customer.email' => [IsString::class], + 'customer' => ['validation.dictionary'], + 'customer.email' => ['validation.string'], ]); expect($validator->invalid())->toBe([ @@ -274,7 +274,7 @@ public function toArray(): array expect($validator->passes())->toBeFalsy(); expect($validator->failing())->toBe([ - 'date' => [Required::class], + 'date' => ['validation.required'], ]); expect($validator->invalid())->toBe([ @@ -308,7 +308,7 @@ public function toArray(): array expect($validator->passes())->toBeFalse(); expect($validator->failing())->toBe([ - 'date' => [Required::class], + 'date' => ['validation.required'], ]); expect($validator->invalid())->toBe([ From f92652d95d3a2036bcb707188e614e8e7c4ca1e8 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 10:38:16 -0500 Subject: [PATCH 04/35] style: php cs --- tests/Unit/Validation/ValidatorTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Unit/Validation/ValidatorTest.php b/tests/Unit/Validation/ValidatorTest.php index 997a070e..76fdcefd 100644 --- a/tests/Unit/Validation/ValidatorTest.php +++ b/tests/Unit/Validation/ValidatorTest.php @@ -7,9 +7,6 @@ use Phenix\Validation\Exceptions\InvalidCollectionDefinition; use Phenix\Validation\Exceptions\InvalidData; use Phenix\Validation\Exceptions\InvalidDictionaryDefinition; -use Phenix\Validation\Rules\IsDictionary; -use Phenix\Validation\Rules\IsString; -use Phenix\Validation\Rules\Required; use Phenix\Validation\Types\Arr; use Phenix\Validation\Types\ArrList; use Phenix\Validation\Types\Collection; From a07f5575d6bcae29da0473c48a8c625d2feecbc5 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 15:34:08 -0500 Subject: [PATCH 05/35] feat: add message method to validation rules for float, integer, and numeric types --- src/Validation/Rules/Numbers/IsFloat.php | 5 +++++ src/Validation/Rules/Numbers/IsInteger.php | 5 +++++ src/Validation/Rules/Numbers/IsNumeric.php | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/Validation/Rules/Numbers/IsFloat.php b/src/Validation/Rules/Numbers/IsFloat.php index bf64491e..82b767c7 100644 --- a/src/Validation/Rules/Numbers/IsFloat.php +++ b/src/Validation/Rules/Numbers/IsFloat.php @@ -14,4 +14,9 @@ public function passes(): bool { return is_float($this->getValue()); } + + public function message(): string|null + { + return trans('validation.float', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Numbers/IsInteger.php b/src/Validation/Rules/Numbers/IsInteger.php index ace93227..e0ca1b7e 100644 --- a/src/Validation/Rules/Numbers/IsInteger.php +++ b/src/Validation/Rules/Numbers/IsInteger.php @@ -14,4 +14,9 @@ public function passes(): bool { return is_integer($this->getValue()); } + + public function message(): string|null + { + return trans('validation.integer', ['field' => $this->field]); + } } diff --git a/src/Validation/Rules/Numbers/IsNumeric.php b/src/Validation/Rules/Numbers/IsNumeric.php index 6950e9cd..b188bb50 100644 --- a/src/Validation/Rules/Numbers/IsNumeric.php +++ b/src/Validation/Rules/Numbers/IsNumeric.php @@ -14,4 +14,9 @@ public function passes(): bool { return is_numeric($this->getValue()); } + + public function message(): string|null + { + return trans('validation.numeric', ['field' => $this->field]); + } } From c250c9756202e57ac1063dc3db7da7b15707bffa Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 15:41:25 -0500 Subject: [PATCH 06/35] feat: add validation messages for various rules in English language file --- .../application/lang/en/validation.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/fixtures/application/lang/en/validation.php diff --git a/tests/fixtures/application/lang/en/validation.php b/tests/fixtures/application/lang/en/validation.php new file mode 100644 index 00000000..1d181268 --- /dev/null +++ b/tests/fixtures/application/lang/en/validation.php @@ -0,0 +1,57 @@ + 'The :field is invalid.', + 'required' => 'The :field field is required.', + 'string' => 'The :field must be a string.', + 'array' => 'The :field must be an array.', + 'boolean' => 'The :field field must be true or false.', + 'file' => 'The :field must be a file.', + 'url' => 'The :field must be a valid URL.', + 'email' => 'The :field must be a valid email address.', + 'uuid' => 'The :field must be a valid UUID.', + 'ulid' => 'The :field must be a valid ULID.', + 'integer' => 'The :field must be an integer.', + 'numeric' => 'The :field must be a number.', + 'float' => 'The :field must be a float.', + 'confirmed' => 'The :field does not match :other.', + 'in' => 'The selected :field is invalid. Allowed: :values.', + 'not_in' => 'The selected :field is invalid. Disallowed: :values.', + 'exists' => 'The selected :field is invalid.', + 'unique' => 'The :field has already been taken.', + 'mimes' => 'The :field must be a file of type: :values.', + 'regex' => 'The :field format is invalid.', + 'starts_with' => 'The :field must start with: :values.', + 'ends_with' => 'The :field must end with: :values.', + 'does_not_start_with' => 'The :field must not start with: :values.', + 'does_not_end_with' => 'The :field must not end with: :values.', + 'size' => [ + 'numeric' => 'The :field must be :size.', + 'string' => 'The :field must be :size characters.', + 'array' => 'The :field must contain :size items.', + 'file' => 'The :field must be :size kilobytes.', + ], + 'min' => [ + 'numeric' => 'The :field must be at least :min.', + 'string' => 'The :field must be at least :min characters.', + 'array' => 'The :field must have at least :min items.', + 'file' => 'The :field must be at least :min kilobytes.', + ], + 'max' => [ + 'numeric' => 'The :field may not be greater than :max.', + 'string' => 'The :field may not be greater than :max characters.', + 'array' => 'The :field may not have more than :max items.', + 'file' => 'The :field may not be greater than :max kilobytes.', + ], + 'between' => [ + 'numeric' => 'The :field must be between :min and :max.', + 'string' => 'The :field must be between :min and :max characters.', + 'array' => 'The :field must have between :min and :max items.', + 'file' => 'The :field must be between :min and :max kilobytes.', + ], + 'date' => [ + 'is_date' => 'The :field is not a valid date.', + 'after' => 'The :field must be a date after the specified date.', + 'format' => 'The :field does not match the format :format.', + ], +]; From 9810c3beb4dcceaf76cdd9b31f0caa3fb0386fe3 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 15:42:22 -0500 Subject: [PATCH 07/35] feat: add unit tests for Required validation rule --- tests/Unit/Validation/Rules/RequiredTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/Unit/Validation/Rules/RequiredTest.php diff --git a/tests/Unit/Validation/Rules/RequiredTest.php b/tests/Unit/Validation/Rules/RequiredTest.php new file mode 100644 index 00000000..83835bba --- /dev/null +++ b/tests/Unit/Validation/Rules/RequiredTest.php @@ -0,0 +1,20 @@ +setField('name')->setData([]); + + expect($rule->passes())->toBeFalse(); + expect($rule->message())->toBe('The name field is required.'); +}); + +it('passes required when value present', function (): void { + $rule = new Required(); + $rule->setField('name')->setData(['name' => 'John']); + + expect($rule->passes())->toBeTrue(); +}); From 5084445acd0349a22e7686aa6b03d177e9f97dea Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:03:36 -0500 Subject: [PATCH 08/35] test: add additional tests for Max validation rule messages --- tests/Unit/Validation/Rules/MaxTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Unit/Validation/Rules/MaxTest.php b/tests/Unit/Validation/Rules/MaxTest.php index 8e7b7df2..50fbc0f8 100644 --- a/tests/Unit/Validation/Rules/MaxTest.php +++ b/tests/Unit/Validation/Rules/MaxTest.php @@ -47,3 +47,19 @@ public function count(): int false, ], ]); + +it('builds proper max messages for each type', function (int|float $limit, string $field, array $data, string $expectedFragment): void { + $rule = new Max($limit); + $rule->setField($field)->setData($data); + + expect($rule->passes())->toBeFalse(); + + $message = $rule->message(); + + expect($message)->toBeString(); + expect($message)->toContain($expectedFragment); +})->with([ + 'numeric' => [1, 'value', ['value' => 2], 'greater than'], + 'string' => [3, 'name', ['name' => 'John'], 'greater than 3 characters'], + 'array' => [1, 'items', ['items' => ['a','b']], 'more than 1 items'], +]); From 2ffe36708bc72e9424c0c7c76979a3df021143a2 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:06:11 -0500 Subject: [PATCH 09/35] test: add comprehensive validation rule messages tests --- .../Validation/Rules/RuleMessagesTest.php | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 tests/Unit/Validation/Rules/RuleMessagesTest.php diff --git a/tests/Unit/Validation/Rules/RuleMessagesTest.php b/tests/Unit/Validation/Rules/RuleMessagesTest.php new file mode 100644 index 00000000..077f694c --- /dev/null +++ b/tests/Unit/Validation/Rules/RuleMessagesTest.php @@ -0,0 +1,204 @@ +setField('field')->setData([]); + + expect($rule->message())->toBe('The field is invalid.'); +}); + +it('returns null for nullable when failing (missing field)', function (): void { + $rule = new Nullable(); + $rule->setField('foo')->setData([]); + + expect($rule->passes())->toBeFalse(); + expect($rule->message())->toBeNull(); +}); + +it('returns null for optional when failing (empty string)', function (): void { + $rule = new Optional(); + $rule->setField('foo')->setData(['foo' => '']); + + expect($rule->passes())->toBeFalse(); + expect($rule->message())->toBeNull(); +}); + +it('builds size/min/max/between messages', function () { + $size = (new Size(3))->setField('name')->setData(['name' => 'John']); + + expect($size->passes())->toBeFalse(); + expect($size->message())->toContain('must be 3 characters'); + + $min = (new Min(5))->setField('name')->setData(['name' => 'John']); + + expect($min->passes())->toBeFalse(); + expect($min->message())->toContain('at least 5 characters'); + + + $max = (new Max(2))->setField('items')->setData(['items' => ['a','b','c']]); + + expect($max->passes())->toBeFalse(); + expect($max->message())->toContain('more than 2 items'); + + $between = (new Between(2,4))->setField('items')->setData(['items' => ['a','b','c','d','e']]); + + expect($between->passes())->toBeFalse(); + expect($between->message())->toContain('between 2 and 4 items'); +}); + +it('string and type messages', function () { + $string = (new IsString())->setField('name')->setData(['name' => 123]); + + expect($string->passes())->toBeFalse(); + expect($string->message())->toContain('must be a string'); + + $array = (new IsArray())->setField('arr')->setData(['arr' => 'not-array']); + expect($array->passes())->toBeFalse(); + expect($array->message())->toContain('must be an array'); + + $bool = (new IsBool())->setField('bool')->setData(['bool' => 'not-bool']); + expect($bool->passes())->toBeFalse(); + expect($bool->message())->toContain('must be true or false'); +}); + +it('other scalar type messages', function () { + $int = (new IsInteger())->setField('age')->setData(['age' => '12']); + + expect($int->passes())->toBeFalse(); + expect($int->message())->toContain('must be an integer'); + + $num = (new IsNumeric())->setField('code')->setData(['code' => 'abc']); + + expect($num->passes())->toBeFalse(); + expect($num->message())->toContain('must be a number'); + + $float = (new IsFloat())->setField('ratio')->setData(['ratio' => 10]); + + expect($float->passes())->toBeFalse(); + expect($float->message())->toContain('must be a float'); +}); + +it('format/uuid/url/email messages', function () { + $file = (new IsFile())->setField('upload')->setData(['upload' => 'not-file']); + + expect($file->passes())->toBeFalse(); + expect($file->message())->toContain('must be a file'); + + $url = (new IsUrl())->setField('site')->setData(['site' => 'notaurl']); + + expect($url->passes())->toBeFalse(); + expect($url->message())->toContain('valid URL'); + + $email = (new IsEmail())->setField('email')->setData(['email' => 'invalid']); + + expect($email->passes())->toBeFalse(); + expect($email->message())->toContain('valid email'); + + $uuid = (new Uuid())->setField('id')->setData(['id' => 'not-uuid']); + + expect($uuid->passes())->toBeFalse(); + expect($uuid->message())->toContain('valid UUID'); + + $ulid = (new Ulid())->setField('id')->setData(['id' => 'not-ulid']); + + expect($ulid->passes())->toBeFalse(); + expect($ulid->message())->toContain('valid ULID'); +}); + +it('in / not in messages', function () { + $in = (new In(['a','b']))->setField('val')->setData(['val' => 'c']); + + expect($in->passes())->toBeFalse(); + expect($in->message())->toContain('Allowed'); + + $notIn = (new NotIn(['a','b']))->setField('val')->setData(['val' => 'a']); + + expect($notIn->passes())->toBeFalse(); + expect($notIn->message())->toContain('Disallowed'); +}); + +it('regex and start/end messages', function () { + $regex = (new RegEx('/^[0-9]+$/'))->setField('code')->setData(['code' => 'abc']); + + expect($regex->passes())->toBeFalse(); + expect($regex->message())->toContain('format is invalid'); + + $starts = (new StartsWith('pre'))->setField('text')->setData(['text' => 'post']); + + expect($starts->passes())->toBeFalse(); + expect($starts->message())->toContain('must start with'); + + $ends = (new EndsWith('suf'))->setField('text')->setData(['text' => 'prefix']); + + expect($ends->passes())->toBeFalse(); + expect($ends->message())->toContain('must end with'); + + $dns = (new DoesNotStartWith('pre'))->setField('text')->setData(['text' => 'prefix']); + + expect($dns->passes())->toBeFalse(); + expect($dns->message())->toContain('must not start with'); + + $dne = (new DoesNotEndWith('suf'))->setField('text')->setData(['text' => 'endsuf']); + + expect($dne->passes())->toBeFalse(); + expect($dne->message())->toContain('must not end with'); +}); + +it('confirmed rule message', function () { + $confirmed = (new Confirmed('password_confirmation'))->setField('password')->setData([ + 'password' => 'secret1', + 'password_confirmation' => 'secret2', + ]); + + expect($confirmed->passes())->toBeFalse(); + expect($confirmed->message())->toContain('does not match'); +}); + +// Skipping exists/unique due to heavy QueryBuilder dependencies; would require DB mocking layer. + +it('date related messages', function () { + $isDate = (new IsDate())->setField('start')->setData(['start' => 'not-date']); + + expect($isDate->passes())->toBeFalse(); + expect($isDate->message())->toContain('not a valid date'); + + $format = (new Format('Y-m-d'))->setField('start')->setData(['start' => '2020/01/01']); + + expect($format->passes())->toBeFalse(); + expect($format->message())->toContain('does not match the format'); +}); \ No newline at end of file From 760cec6fdb2d2bb07a55181a54c88a33e55b6109 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:25:42 -0500 Subject: [PATCH 10/35] test: add unit test for Confirmed validation rule --- tests/Unit/Validation/Rules/ConfirmedTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/Unit/Validation/Rules/ConfirmedTest.php diff --git a/tests/Unit/Validation/Rules/ConfirmedTest.php b/tests/Unit/Validation/Rules/ConfirmedTest.php new file mode 100644 index 00000000..2adb8c6f --- /dev/null +++ b/tests/Unit/Validation/Rules/ConfirmedTest.php @@ -0,0 +1,16 @@ +setField('password')->setData([ + 'password' => 'secret1', + 'password_confirmation' => 'secret2', + ]); + + assertFalse($rule->passes()); + assertStringContainsString('does not match', (string) $rule->message()); +}); From e72efb81846d6c46e00c1e9a3fd52ad3625566d1 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:27:09 -0500 Subject: [PATCH 11/35] test: add unit tests for DoesNotEndWith and DoesNotStartWith validation rules --- .../Validation/Rules/DoesNotEndWithTest.php | 20 +++++++++++++++++++ .../Validation/Rules/DoesNotStartWithTest.php | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/Unit/Validation/Rules/DoesNotEndWithTest.php create mode 100644 tests/Unit/Validation/Rules/DoesNotStartWithTest.php diff --git a/tests/Unit/Validation/Rules/DoesNotEndWithTest.php b/tests/Unit/Validation/Rules/DoesNotEndWithTest.php new file mode 100644 index 00000000..4d8dbc2f --- /dev/null +++ b/tests/Unit/Validation/Rules/DoesNotEndWithTest.php @@ -0,0 +1,20 @@ +setField('text')->setData(['text' => 'endsuf']); + + assertFalse($rule->passes()); + assertStringContainsString('must not end', (string) $rule->message()); +}); + +it('passes when string does not end with forbidden suffix', function () { + $rule = new DoesNotEndWith('suf'); + $rule->setField('text')->setData(['text' => 'suffixx']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DoesNotStartWithTest.php b/tests/Unit/Validation/Rules/DoesNotStartWithTest.php new file mode 100644 index 00000000..b044cf95 --- /dev/null +++ b/tests/Unit/Validation/Rules/DoesNotStartWithTest.php @@ -0,0 +1,20 @@ +setField('text')->setData(['text' => 'prefix']); + + assertFalse($rule->passes()); + assertStringContainsString('must not start', (string) $rule->message()); +}); + +it('passes when string does not start with forbidden prefix', function () { + $rule = new DoesNotStartWith('pre'); + $rule->setField('text')->setData(['text' => 'xpre']); + + assertTrue($rule->passes()); +}); From cc8beaf7f858380d3776bf83eadf9d1d8e3e7600 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:27:40 -0500 Subject: [PATCH 12/35] test: add unit tests for EndsWith and StartsWith validation rules --- tests/Unit/Validation/Rules/EndsWithTest.php | 20 +++++++++++++++++++ .../Unit/Validation/Rules/StartsWithTest.php | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/Unit/Validation/Rules/EndsWithTest.php create mode 100644 tests/Unit/Validation/Rules/StartsWithTest.php diff --git a/tests/Unit/Validation/Rules/EndsWithTest.php b/tests/Unit/Validation/Rules/EndsWithTest.php new file mode 100644 index 00000000..e55255f2 --- /dev/null +++ b/tests/Unit/Validation/Rules/EndsWithTest.php @@ -0,0 +1,20 @@ +setField('text')->setData(['text' => 'prefix']); + + assertFalse($rule->passes()); + assertStringContainsString('must end with', (string) $rule->message()); +}); + +it('passes when string ends with needle', function () { + $rule = new EndsWith('suf'); + $rule->setField('text')->setData(['text' => 'endsuf']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/StartsWithTest.php b/tests/Unit/Validation/Rules/StartsWithTest.php new file mode 100644 index 00000000..edecc832 --- /dev/null +++ b/tests/Unit/Validation/Rules/StartsWithTest.php @@ -0,0 +1,20 @@ +setField('text')->setData(['text' => 'postfix']); + + assertFalse($rule->passes()); + assertStringContainsString('must start with', (string) $rule->message()); +}); + +it('passes when string starts with needle', function () { + $rule = new StartsWith('pre'); + $rule->setField('text')->setData(['text' => 'prefix']); + + assertTrue($rule->passes()); +}); From 7b2f698f61d2fd3600263f6662b3e66c5ef3213c Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:28:08 -0500 Subject: [PATCH 13/35] test: add unit tests for Format and IsDate validation rules --- .../Unit/Validation/Rules/FormatDateTest.php | 20 +++++++++++++++++++ tests/Unit/Validation/Rules/IsDateTest.php | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/Unit/Validation/Rules/FormatDateTest.php create mode 100644 tests/Unit/Validation/Rules/IsDateTest.php diff --git a/tests/Unit/Validation/Rules/FormatDateTest.php b/tests/Unit/Validation/Rules/FormatDateTest.php new file mode 100644 index 00000000..d96e0c1c --- /dev/null +++ b/tests/Unit/Validation/Rules/FormatDateTest.php @@ -0,0 +1,20 @@ +setField('start')->setData(['start' => '2024/01/01']); + + assertFalse($rule->passes()); + assertStringContainsString('does not match the format', (string) $rule->message()); +}); + +it('passes when date matches expected format', function () { + $rule = new Format('Y-m-d'); + $rule->setField('start')->setData(['start' => '2024-01-01']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsDateTest.php b/tests/Unit/Validation/Rules/IsDateTest.php new file mode 100644 index 00000000..29388b5b --- /dev/null +++ b/tests/Unit/Validation/Rules/IsDateTest.php @@ -0,0 +1,20 @@ +setField('start')->setData(['start' => 'not-date']); + + assertFalse($rule->passes()); + assertStringContainsString('not a valid date', (string) $rule->message()); +}); + +it('passes for valid date string', function () { + $rule = new IsDate(); + $rule->setField('start')->setData(['start' => '2024-12-01']); + + assertTrue($rule->passes()); +}); From 650d712aaa0d2e18a68b563ee8ae76aa9b384bb8 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:28:18 -0500 Subject: [PATCH 14/35] test: add unit tests for In and NotIn validation rules --- tests/Unit/Validation/Rules/InTest.php | 20 ++++++++++++++++++++ tests/Unit/Validation/Rules/NotInTest.php | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/Unit/Validation/Rules/InTest.php create mode 100644 tests/Unit/Validation/Rules/NotInTest.php diff --git a/tests/Unit/Validation/Rules/InTest.php b/tests/Unit/Validation/Rules/InTest.php new file mode 100644 index 00000000..d03cdde0 --- /dev/null +++ b/tests/Unit/Validation/Rules/InTest.php @@ -0,0 +1,20 @@ +setField('val')->setData(['val' => 'c']); + + assertFalse($rule->passes()); + assertStringContainsString('Allowed', (string) $rule->message()); +}); + +it('passes when value is in allowed list', function () { + $rule = new In(['a','b']); + $rule->setField('val')->setData(['val' => 'a']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/NotInTest.php b/tests/Unit/Validation/Rules/NotInTest.php new file mode 100644 index 00000000..a166899a --- /dev/null +++ b/tests/Unit/Validation/Rules/NotInTest.php @@ -0,0 +1,20 @@ +setField('val')->setData(['val' => 'b']); + + assertFalse($rule->passes()); + assertStringContainsString('Disallowed', (string) $rule->message()); +}); + +it('passes when value is not inside the forbidden list', function () { + $rule = new NotIn(['a','b','c']); + $rule->setField('val')->setData(['val' => 'x']); + + assertTrue($rule->passes()); +}); From ee52cc5eff6dbabb309f9e6b2aa77d2d52a7cfd2 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:28:29 -0500 Subject: [PATCH 15/35] test: add unit tests for RegEx validation rule --- tests/Unit/Validation/Rules/RegExTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/Unit/Validation/Rules/RegExTest.php diff --git a/tests/Unit/Validation/Rules/RegExTest.php b/tests/Unit/Validation/Rules/RegExTest.php new file mode 100644 index 00000000..6d4583ec --- /dev/null +++ b/tests/Unit/Validation/Rules/RegExTest.php @@ -0,0 +1,20 @@ +setField('code')->setData(['code' => 'abc']); + + assertFalse($rule->passes()); + assertStringContainsString('format is invalid', (string) $rule->message()); +}); + +it('passes when value matches regex', function () { + $rule = new RegEx('/^[0-9]+$/'); + $rule->setField('code')->setData(['code' => '123']); + + assertTrue($rule->passes()); +}); From b881fb9d26fa0437be8bc1c02f6a5ab829cbdf9d Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 16:30:46 -0500 Subject: [PATCH 16/35] test: add unit tests for UUID and ULID validation rules --- tests/Unit/Validation/Rules/UidTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/Unit/Validation/Rules/UidTest.php diff --git a/tests/Unit/Validation/Rules/UidTest.php b/tests/Unit/Validation/Rules/UidTest.php new file mode 100644 index 00000000..b2727c1f --- /dev/null +++ b/tests/Unit/Validation/Rules/UidTest.php @@ -0,0 +1,18 @@ +setField('id')->setData(['id' => 'not-uuid']); + + assertFalse($uuid->passes()); + assertStringContainsString('valid UUID', (string) $uuid->message()); + + $ulid = (new Ulid())->setField('id')->setData(['id' => 'not-ulid']); + + assertFalse($ulid->passes()); + assertStringContainsString('valid ULID', (string) $ulid->message()); +}); From 71a83572d07d04663a11ff7e2b7f5b276124d4b5 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:18:29 -0500 Subject: [PATCH 17/35] refactor: remove unused message method from Rule class --- src/Validation/Rules/Rule.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Validation/Rules/Rule.php b/src/Validation/Rules/Rule.php index cc1794e7..179c63f8 100644 --- a/src/Validation/Rules/Rule.php +++ b/src/Validation/Rules/Rule.php @@ -51,9 +51,4 @@ protected function getValueType(): string { return gettype($this->data->get($this->field) ?? null); } - - public function message(): string|null - { - return trans('validation.rule', ['field' => $this->field]); - } } From 9b5c40d3f2464cdbd8b34bc6d2f2667473746b58 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:18:38 -0500 Subject: [PATCH 18/35] feat: add message method to Digits and DigitsBetween validation rules --- src/Validation/Rules/Numbers/Digits.php | 8 ++++++++ src/Validation/Rules/Numbers/DigitsBetween.php | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Validation/Rules/Numbers/Digits.php b/src/Validation/Rules/Numbers/Digits.php index f270c919..a76e1515 100644 --- a/src/Validation/Rules/Numbers/Digits.php +++ b/src/Validation/Rules/Numbers/Digits.php @@ -21,4 +21,12 @@ public function passes(): bool return strlen($digits) === $this->digits; } + + public function message(): string|null + { + return trans('validation.digits', [ + 'field' => $this->field, + 'digits' => $this->digits, + ]); + } } diff --git a/src/Validation/Rules/Numbers/DigitsBetween.php b/src/Validation/Rules/Numbers/DigitsBetween.php index 71569189..4bf1bd4b 100644 --- a/src/Validation/Rules/Numbers/DigitsBetween.php +++ b/src/Validation/Rules/Numbers/DigitsBetween.php @@ -19,4 +19,13 @@ public function passes(): bool return $digits >= $this->min && $digits <= $this->max; } + + public function message(): string|null + { + return trans('validation.digits_between', [ + 'field' => $this->field, + 'min' => $this->min, + 'max' => $this->max, + ]); + } } From d1460425bb4d6c01999686b7ce4585bcb1e6f91a Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:18:43 -0500 Subject: [PATCH 19/35] feat: add dictionary validation message to language file --- tests/fixtures/application/lang/en/validation.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fixtures/application/lang/en/validation.php b/tests/fixtures/application/lang/en/validation.php index 1d181268..6f23add7 100644 --- a/tests/fixtures/application/lang/en/validation.php +++ b/tests/fixtures/application/lang/en/validation.php @@ -14,6 +14,7 @@ 'integer' => 'The :field must be an integer.', 'numeric' => 'The :field must be a number.', 'float' => 'The :field must be a float.', + 'dictionary' => 'The :field field must be a dictionary.', 'confirmed' => 'The :field does not match :other.', 'in' => 'The selected :field is invalid. Allowed: :values.', 'not_in' => 'The selected :field is invalid. Disallowed: :values.', From b0fd99d7921c9f63f9cd3500aa44c7feff6c2b42 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:18:52 -0500 Subject: [PATCH 20/35] fix: update validation error messages for clarity in ValidatorTest --- tests/Unit/Validation/ValidatorTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Validation/ValidatorTest.php b/tests/Unit/Validation/ValidatorTest.php index 76fdcefd..9de37967 100644 --- a/tests/Unit/Validation/ValidatorTest.php +++ b/tests/Unit/Validation/ValidatorTest.php @@ -99,7 +99,7 @@ public function toArray(): array expect($validator->passes())->toBeFalse(); expect($validator->failing())->toBe([ - 'name' => ['validation.required'], + 'name' => ['The name field is required.'], ]); expect($validator->invalid())->toBe([ @@ -171,8 +171,8 @@ public function toArray(): array expect($validator->passes())->toBeFalsy(); expect($validator->failing())->toBe([ - 'customer' => ['validation.dictionary'], - 'customer.email' => ['validation.string'], + 'customer' => ['The customer field must be a dictionary.'], + 'customer.email' => ['The customer.email must be a string.'], ]); expect($validator->invalid())->toBe([ @@ -271,7 +271,7 @@ public function toArray(): array expect($validator->passes())->toBeFalsy(); expect($validator->failing())->toBe([ - 'date' => ['validation.required'], + 'date' => ['The date field is required.'], ]); expect($validator->invalid())->toBe([ @@ -305,7 +305,7 @@ public function toArray(): array expect($validator->passes())->toBeFalse(); expect($validator->failing())->toBe([ - 'date' => ['validation.required'], + 'date' => ['The date field is required.'], ]); expect($validator->invalid())->toBe([ From 7bf16c6745784968e545597408b5f029c9f96b4e Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:20:25 -0500 Subject: [PATCH 21/35] test: add unit tests for various validation rules including Between, IsArray, IsBool, IsEmail, IsFile, IsFloat, IsInteger, IsNumeric, IsString, IsUrl, Nullable, and Optional --- tests/Unit/Validation/Rules/BetweenTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsArrayTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsBoolTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsEmailTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsFileTest.php | 13 ++++++++++++ tests/Unit/Validation/Rules/IsFloatTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsIntegerTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsNumericTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsStringTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/IsUrlTest.php | 20 ++++++++++++++++++ tests/Unit/Validation/Rules/NullableTest.php | 21 +++++++++++++++++++ tests/Unit/Validation/Rules/OptionalTest.php | 21 +++++++++++++++++++ tests/Unit/Validation/Rules/SizeTest.php | 16 ++++++++++++++ tests/Unit/Validation/Rules/UidTest.php | 4 ++-- 14 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Validation/Rules/BetweenTest.php create mode 100644 tests/Unit/Validation/Rules/IsArrayTest.php create mode 100644 tests/Unit/Validation/Rules/IsBoolTest.php create mode 100644 tests/Unit/Validation/Rules/IsEmailTest.php create mode 100644 tests/Unit/Validation/Rules/IsFileTest.php create mode 100644 tests/Unit/Validation/Rules/IsFloatTest.php create mode 100644 tests/Unit/Validation/Rules/IsIntegerTest.php create mode 100644 tests/Unit/Validation/Rules/IsNumericTest.php create mode 100644 tests/Unit/Validation/Rules/IsStringTest.php create mode 100644 tests/Unit/Validation/Rules/IsUrlTest.php create mode 100644 tests/Unit/Validation/Rules/NullableTest.php create mode 100644 tests/Unit/Validation/Rules/OptionalTest.php diff --git a/tests/Unit/Validation/Rules/BetweenTest.php b/tests/Unit/Validation/Rules/BetweenTest.php new file mode 100644 index 00000000..e2ed15f5 --- /dev/null +++ b/tests/Unit/Validation/Rules/BetweenTest.php @@ -0,0 +1,20 @@ +setField('items')->setData(['items' => ['a','b','c','d','e']]); + + assertFalse($rule->passes()); + assertStringContainsString('between 2 and 4 items', (string) $rule->message()); +}); + +it('passes between for array inside range', function () { + $rule = new Between(2, 4); + $rule->setField('items')->setData(['items' => ['a','b','c']]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsArrayTest.php b/tests/Unit/Validation/Rules/IsArrayTest.php new file mode 100644 index 00000000..013f7489 --- /dev/null +++ b/tests/Unit/Validation/Rules/IsArrayTest.php @@ -0,0 +1,20 @@ +setField('data')->setData(['data' => 'string']); + + assertFalse($rule->passes()); + assertStringContainsString('must be an array', (string) $rule->message()); +}); + +it('passes is_array when value is array', function () { + $rule = new IsArray(); + $rule->setField('data')->setData(['data' => []]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsBoolTest.php b/tests/Unit/Validation/Rules/IsBoolTest.php new file mode 100644 index 00000000..c936aaae --- /dev/null +++ b/tests/Unit/Validation/Rules/IsBoolTest.php @@ -0,0 +1,20 @@ +setField('flag')->setData(['flag' => 'nope']); + + assertFalse($rule->passes()); + assertStringContainsString('must be true or false', (string) $rule->message()); +}); + +it('passes is_bool when value boolean', function () { + $rule = new IsBool(); + $rule->setField('flag')->setData(['flag' => true]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsEmailTest.php b/tests/Unit/Validation/Rules/IsEmailTest.php new file mode 100644 index 00000000..ab6d8c67 --- /dev/null +++ b/tests/Unit/Validation/Rules/IsEmailTest.php @@ -0,0 +1,20 @@ +setField('email')->setData(['email' => 'invalid']); + + assertFalse($rule->passes()); + assertStringContainsString('valid email', (string) $rule->message()); +}); + +it('passes is_email when valid', function () { + $rule = new IsEmail(); + $rule->setField('email')->setData(['email' => 'user@example.com']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsFileTest.php b/tests/Unit/Validation/Rules/IsFileTest.php new file mode 100644 index 00000000..b029a6aa --- /dev/null +++ b/tests/Unit/Validation/Rules/IsFileTest.php @@ -0,0 +1,13 @@ +setField('upload')->setData(['upload' => 'string']); + + assertFalse($rule->passes()); + assertStringContainsString('must be a file', (string) $rule->message()); +}); diff --git a/tests/Unit/Validation/Rules/IsFloatTest.php b/tests/Unit/Validation/Rules/IsFloatTest.php new file mode 100644 index 00000000..c39558ee --- /dev/null +++ b/tests/Unit/Validation/Rules/IsFloatTest.php @@ -0,0 +1,20 @@ +setField('ratio')->setData(['ratio' => 10]); + + assertFalse($rule->passes()); + assertStringContainsString('must be a float', (string) $rule->message()); +}); + +it('passes is_float when value float', function () { + $rule = new IsFloat(); + $rule->setField('ratio')->setData(['ratio' => 10.5]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsIntegerTest.php b/tests/Unit/Validation/Rules/IsIntegerTest.php new file mode 100644 index 00000000..eda322cb --- /dev/null +++ b/tests/Unit/Validation/Rules/IsIntegerTest.php @@ -0,0 +1,20 @@ +setField('age')->setData(['age' => '12']); + + assertFalse($rule->passes()); + assertStringContainsString('must be an integer', (string) $rule->message()); +}); + +it('passes is_integer when value integer', function () { + $rule = new IsInteger(); + $rule->setField('age')->setData(['age' => 12]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsNumericTest.php b/tests/Unit/Validation/Rules/IsNumericTest.php new file mode 100644 index 00000000..dd13c38b --- /dev/null +++ b/tests/Unit/Validation/Rules/IsNumericTest.php @@ -0,0 +1,20 @@ +setField('code')->setData(['code' => 'abc']); + + assertFalse($rule->passes()); + assertStringContainsString('must be a number', (string) $rule->message()); +}); + +it('passes is_numeric when value numeric', function () { + $rule = new IsNumeric(); + $rule->setField('code')->setData(['code' => '123']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsStringTest.php b/tests/Unit/Validation/Rules/IsStringTest.php new file mode 100644 index 00000000..fb5c4727 --- /dev/null +++ b/tests/Unit/Validation/Rules/IsStringTest.php @@ -0,0 +1,20 @@ +setField('name')->setData(['name' => 123]); + + assertFalse($rule->passes()); + assertStringContainsString('must be a string', (string) $rule->message()); +}); + +it('passes is_string when value is a string', function () { + $rule = new IsString(); + $rule->setField('name')->setData(['name' => 'John']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsUrlTest.php b/tests/Unit/Validation/Rules/IsUrlTest.php new file mode 100644 index 00000000..accb9b66 --- /dev/null +++ b/tests/Unit/Validation/Rules/IsUrlTest.php @@ -0,0 +1,20 @@ +setField('site')->setData(['site' => 'notaurl']); + + assertFalse($rule->passes()); + assertStringContainsString('valid URL', (string) $rule->message()); +}); + +it('passes is_url when valid', function () { + $rule = new IsUrl(); + $rule->setField('site')->setData(['site' => 'https://example.com']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/NullableTest.php b/tests/Unit/Validation/Rules/NullableTest.php new file mode 100644 index 00000000..b1c634ab --- /dev/null +++ b/tests/Unit/Validation/Rules/NullableTest.php @@ -0,0 +1,21 @@ +setField('foo')->setData([]); + + assertFalse($rule->passes()); + assertSame(null, $rule->message()); +}); + +it('nullable passes and returns null message when value is null', function () { + $rule = new Nullable(); + $rule->setField('foo')->setData(['foo' => null]); + + assertTrue($rule->passes()); + assertSame(null, $rule->message()); +}); diff --git a/tests/Unit/Validation/Rules/OptionalTest.php b/tests/Unit/Validation/Rules/OptionalTest.php new file mode 100644 index 00000000..083b29b7 --- /dev/null +++ b/tests/Unit/Validation/Rules/OptionalTest.php @@ -0,0 +1,21 @@ +setField('foo')->setData([]); + + assertTrue($rule->passes()); + assertSame(null, $rule->message()); +}); + +it('optional fails when present but empty', function () { + $rule = new Optional(); + $rule->setField('foo')->setData(['foo' => '']); + + assertFalse($rule->passes()); + assertSame(null, $rule->message()); +}); diff --git a/tests/Unit/Validation/Rules/SizeTest.php b/tests/Unit/Validation/Rules/SizeTest.php index 7bce5c36..e4fd6ffc 100644 --- a/tests/Unit/Validation/Rules/SizeTest.php +++ b/tests/Unit/Validation/Rules/SizeTest.php @@ -4,6 +4,21 @@ use Phenix\Validation\Rules\Size; +it('fails size for string length mismatch', function () { + $rule = new Size(5); + $rule->setField('name')->setData(['name' => 'John']); + + assertFalse($rule->passes()); + assertStringContainsString('must be 5 characters', (string) $rule->message()); +}); + +it('passes size for exact string length', function () { + $rule = new Size(4); + $rule->setField('name')->setData(['name' => 'John']); + + assertTrue($rule->passes()); +}); + it('checks size according to data type', function ( float|int $limit, string $field, @@ -47,3 +62,4 @@ public function count(): int false, ], ]); + diff --git a/tests/Unit/Validation/Rules/UidTest.php b/tests/Unit/Validation/Rules/UidTest.php index b2727c1f..f0e465b1 100644 --- a/tests/Unit/Validation/Rules/UidTest.php +++ b/tests/Unit/Validation/Rules/UidTest.php @@ -1,10 +1,10 @@ setField('id')->setData(['id' => 'not-uuid']); From b57183ebe1bd603a42aacd482afda88e59b908ab Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:20:31 -0500 Subject: [PATCH 22/35] refactor: remove RuleMessagesTest file to streamline validation tests --- .../Validation/Rules/RuleMessagesTest.php | 204 ------------------ 1 file changed, 204 deletions(-) delete mode 100644 tests/Unit/Validation/Rules/RuleMessagesTest.php diff --git a/tests/Unit/Validation/Rules/RuleMessagesTest.php b/tests/Unit/Validation/Rules/RuleMessagesTest.php deleted file mode 100644 index 077f694c..00000000 --- a/tests/Unit/Validation/Rules/RuleMessagesTest.php +++ /dev/null @@ -1,204 +0,0 @@ -setField('field')->setData([]); - - expect($rule->message())->toBe('The field is invalid.'); -}); - -it('returns null for nullable when failing (missing field)', function (): void { - $rule = new Nullable(); - $rule->setField('foo')->setData([]); - - expect($rule->passes())->toBeFalse(); - expect($rule->message())->toBeNull(); -}); - -it('returns null for optional when failing (empty string)', function (): void { - $rule = new Optional(); - $rule->setField('foo')->setData(['foo' => '']); - - expect($rule->passes())->toBeFalse(); - expect($rule->message())->toBeNull(); -}); - -it('builds size/min/max/between messages', function () { - $size = (new Size(3))->setField('name')->setData(['name' => 'John']); - - expect($size->passes())->toBeFalse(); - expect($size->message())->toContain('must be 3 characters'); - - $min = (new Min(5))->setField('name')->setData(['name' => 'John']); - - expect($min->passes())->toBeFalse(); - expect($min->message())->toContain('at least 5 characters'); - - - $max = (new Max(2))->setField('items')->setData(['items' => ['a','b','c']]); - - expect($max->passes())->toBeFalse(); - expect($max->message())->toContain('more than 2 items'); - - $between = (new Between(2,4))->setField('items')->setData(['items' => ['a','b','c','d','e']]); - - expect($between->passes())->toBeFalse(); - expect($between->message())->toContain('between 2 and 4 items'); -}); - -it('string and type messages', function () { - $string = (new IsString())->setField('name')->setData(['name' => 123]); - - expect($string->passes())->toBeFalse(); - expect($string->message())->toContain('must be a string'); - - $array = (new IsArray())->setField('arr')->setData(['arr' => 'not-array']); - expect($array->passes())->toBeFalse(); - expect($array->message())->toContain('must be an array'); - - $bool = (new IsBool())->setField('bool')->setData(['bool' => 'not-bool']); - expect($bool->passes())->toBeFalse(); - expect($bool->message())->toContain('must be true or false'); -}); - -it('other scalar type messages', function () { - $int = (new IsInteger())->setField('age')->setData(['age' => '12']); - - expect($int->passes())->toBeFalse(); - expect($int->message())->toContain('must be an integer'); - - $num = (new IsNumeric())->setField('code')->setData(['code' => 'abc']); - - expect($num->passes())->toBeFalse(); - expect($num->message())->toContain('must be a number'); - - $float = (new IsFloat())->setField('ratio')->setData(['ratio' => 10]); - - expect($float->passes())->toBeFalse(); - expect($float->message())->toContain('must be a float'); -}); - -it('format/uuid/url/email messages', function () { - $file = (new IsFile())->setField('upload')->setData(['upload' => 'not-file']); - - expect($file->passes())->toBeFalse(); - expect($file->message())->toContain('must be a file'); - - $url = (new IsUrl())->setField('site')->setData(['site' => 'notaurl']); - - expect($url->passes())->toBeFalse(); - expect($url->message())->toContain('valid URL'); - - $email = (new IsEmail())->setField('email')->setData(['email' => 'invalid']); - - expect($email->passes())->toBeFalse(); - expect($email->message())->toContain('valid email'); - - $uuid = (new Uuid())->setField('id')->setData(['id' => 'not-uuid']); - - expect($uuid->passes())->toBeFalse(); - expect($uuid->message())->toContain('valid UUID'); - - $ulid = (new Ulid())->setField('id')->setData(['id' => 'not-ulid']); - - expect($ulid->passes())->toBeFalse(); - expect($ulid->message())->toContain('valid ULID'); -}); - -it('in / not in messages', function () { - $in = (new In(['a','b']))->setField('val')->setData(['val' => 'c']); - - expect($in->passes())->toBeFalse(); - expect($in->message())->toContain('Allowed'); - - $notIn = (new NotIn(['a','b']))->setField('val')->setData(['val' => 'a']); - - expect($notIn->passes())->toBeFalse(); - expect($notIn->message())->toContain('Disallowed'); -}); - -it('regex and start/end messages', function () { - $regex = (new RegEx('/^[0-9]+$/'))->setField('code')->setData(['code' => 'abc']); - - expect($regex->passes())->toBeFalse(); - expect($regex->message())->toContain('format is invalid'); - - $starts = (new StartsWith('pre'))->setField('text')->setData(['text' => 'post']); - - expect($starts->passes())->toBeFalse(); - expect($starts->message())->toContain('must start with'); - - $ends = (new EndsWith('suf'))->setField('text')->setData(['text' => 'prefix']); - - expect($ends->passes())->toBeFalse(); - expect($ends->message())->toContain('must end with'); - - $dns = (new DoesNotStartWith('pre'))->setField('text')->setData(['text' => 'prefix']); - - expect($dns->passes())->toBeFalse(); - expect($dns->message())->toContain('must not start with'); - - $dne = (new DoesNotEndWith('suf'))->setField('text')->setData(['text' => 'endsuf']); - - expect($dne->passes())->toBeFalse(); - expect($dne->message())->toContain('must not end with'); -}); - -it('confirmed rule message', function () { - $confirmed = (new Confirmed('password_confirmation'))->setField('password')->setData([ - 'password' => 'secret1', - 'password_confirmation' => 'secret2', - ]); - - expect($confirmed->passes())->toBeFalse(); - expect($confirmed->message())->toContain('does not match'); -}); - -// Skipping exists/unique due to heavy QueryBuilder dependencies; would require DB mocking layer. - -it('date related messages', function () { - $isDate = (new IsDate())->setField('start')->setData(['start' => 'not-date']); - - expect($isDate->passes())->toBeFalse(); - expect($isDate->message())->toContain('not a valid date'); - - $format = (new Format('Y-m-d'))->setField('start')->setData(['start' => '2020/01/01']); - - expect($format->passes())->toBeFalse(); - expect($format->message())->toContain('does not match the format'); -}); \ No newline at end of file From fefca519a5a58bf94786515faf09975dccf30453 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:33:12 -0500 Subject: [PATCH 23/35] test: add unit tests for date validation rules including After, AfterOrEqual, Before, BeforeOrEqual, Equal, and their related variants --- .../Validation/Rules/DateAfterOrEqualTest.php | 27 ++++++++++++++ .../Rules/DateAfterOrEqualToTest.php | 36 +++++++++++++++++++ tests/Unit/Validation/Rules/DateAfterTest.php | 13 +++++++ .../Unit/Validation/Rules/DateAfterToTest.php | 26 ++++++++++++++ .../Rules/DateBeforeOrEqualTest.php | 27 ++++++++++++++ .../Rules/DateBeforeOrEqualToTest.php | 36 +++++++++++++++++++ .../Unit/Validation/Rules/DateBeforeTest.php | 20 +++++++++++ .../Validation/Rules/DateBeforeToTest.php | 26 ++++++++++++++ tests/Unit/Validation/Rules/DateEqualTest.php | 20 +++++++++++ .../Unit/Validation/Rules/DateEqualToTest.php | 26 ++++++++++++++ 10 files changed, 257 insertions(+) create mode 100644 tests/Unit/Validation/Rules/DateAfterOrEqualTest.php create mode 100644 tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php create mode 100644 tests/Unit/Validation/Rules/DateAfterTest.php create mode 100644 tests/Unit/Validation/Rules/DateAfterToTest.php create mode 100644 tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php create mode 100644 tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php create mode 100644 tests/Unit/Validation/Rules/DateBeforeTest.php create mode 100644 tests/Unit/Validation/Rules/DateBeforeToTest.php create mode 100644 tests/Unit/Validation/Rules/DateEqualTest.php create mode 100644 tests/Unit/Validation/Rules/DateEqualToTest.php diff --git a/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php b/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php new file mode 100644 index 00000000..0606fe97 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php @@ -0,0 +1,27 @@ +setField('date')->setData(['date' => '2023-12-31']); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.after_or_equal', (string) $rule->message()); +}); + +it('passes when date is equal', function () { + $rule = new AfterOrEqual('2024-01-01'); + $rule->setField('date')->setData(['date' => '2024-01-01']); + + assertTrue($rule->passes()); +}); + +it('passes when date is after', function () { + $rule = new AfterOrEqual('2024-01-01'); + $rule->setField('date')->setData(['date' => '2024-01-02']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php b/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php new file mode 100644 index 00000000..73b7972e --- /dev/null +++ b/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php @@ -0,0 +1,36 @@ +setField('start_date')->setData([ + 'start_date' => '2024-01-01', + 'end_date' => '2024-01-02', + ]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.after_or_equal_to', (string) $rule->message()); +}); + +it('passes when date is equal to related date', function () { + $rule = new AfterOrEqualTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-02', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); + +it('passes when date is after related date', function () { + $rule = new AfterOrEqualTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-03', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateAfterTest.php b/tests/Unit/Validation/Rules/DateAfterTest.php new file mode 100644 index 00000000..91db8043 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateAfterTest.php @@ -0,0 +1,13 @@ +setField('date')->setData(['date' => '2023-12-31']); + + assertFalse($rule->passes()); + assertStringContainsString('must be a date after', (string) $rule->message()); +}); diff --git a/tests/Unit/Validation/Rules/DateAfterToTest.php b/tests/Unit/Validation/Rules/DateAfterToTest.php new file mode 100644 index 00000000..c5fe939b --- /dev/null +++ b/tests/Unit/Validation/Rules/DateAfterToTest.php @@ -0,0 +1,26 @@ +setField('start_date')->setData([ + 'start_date' => '2024-01-02', + 'end_date' => '2024-01-02', + ]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.after_to', (string) $rule->message()); +}); + +it('passes when date is after related date', function () { + $rule = new AfterTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-03', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php b/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php new file mode 100644 index 00000000..8834c9c7 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php @@ -0,0 +1,27 @@ +setField('date')->setData(['date' => '2024-01-02']); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.before_or_equal', (string) $rule->message()); +}); + +it('passes when date is equal to given date', function () { + $rule = new BeforeOrEqual('2024-01-01'); + $rule->setField('date')->setData(['date' => '2024-01-01']); + + assertTrue($rule->passes()); +}); + +it('passes when date is before given date', function () { + $rule = new BeforeOrEqual('2024-01-01'); + $rule->setField('date')->setData(['date' => '2023-12-31']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php b/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php new file mode 100644 index 00000000..f4d0dae9 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php @@ -0,0 +1,36 @@ +setField('start_date')->setData([ + 'start_date' => '2024-01-03', + 'end_date' => '2024-01-02', + ]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.before_or_equal_to', (string) $rule->message()); +}); + +it('passes when date is equal to related date', function () { + $rule = new BeforeOrEqualTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-02', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); + +it('passes when date is before related date', function () { + $rule = new BeforeOrEqualTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-01', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateBeforeTest.php b/tests/Unit/Validation/Rules/DateBeforeTest.php new file mode 100644 index 00000000..7c18f63f --- /dev/null +++ b/tests/Unit/Validation/Rules/DateBeforeTest.php @@ -0,0 +1,20 @@ +setField('date')->setData(['date' => '2024-01-01']); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.before', (string) $rule->message()); +}); + +it('passes when date is before given date', function () { + $rule = new Before('2024-01-01'); + $rule->setField('date')->setData(['date' => '2023-12-31']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateBeforeToTest.php b/tests/Unit/Validation/Rules/DateBeforeToTest.php new file mode 100644 index 00000000..99bde265 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateBeforeToTest.php @@ -0,0 +1,26 @@ +setField('start_date')->setData([ + 'start_date' => '2024-01-02', + 'end_date' => '2024-01-01', + ]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.before_to', (string) $rule->message()); +}); + +it('passes when date is before related date', function () { + $rule = new BeforeTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-01', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateEqualTest.php b/tests/Unit/Validation/Rules/DateEqualTest.php new file mode 100644 index 00000000..80ab7567 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateEqualTest.php @@ -0,0 +1,20 @@ +setField('date')->setData(['date' => '2024-01-02']); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.equal', (string) $rule->message()); +}); + +it('passes when date is equal to given date', function () { + $rule = new Equal('2024-01-01'); + $rule->setField('date')->setData(['date' => '2024-01-01']); + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DateEqualToTest.php b/tests/Unit/Validation/Rules/DateEqualToTest.php new file mode 100644 index 00000000..664bf168 --- /dev/null +++ b/tests/Unit/Validation/Rules/DateEqualToTest.php @@ -0,0 +1,26 @@ +setField('start_date')->setData([ + 'start_date' => '2024-01-01', + 'end_date' => '2024-01-02', + ]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.date.equal_to', (string) $rule->message()); +}); + +it('passes when date is equal to related date', function () { + $rule = new EqualTo('end_date'); + $rule->setField('start_date')->setData([ + 'start_date' => '2024-01-02', + 'end_date' => '2024-01-02', + ]); + + assertTrue($rule->passes()); +}); From 89818ae109293dfca6c4b343a71a954b4b65aa76 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:33:25 -0500 Subject: [PATCH 24/35] style: php cs --- tests/Unit/Validation/Rules/SizeTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Unit/Validation/Rules/SizeTest.php b/tests/Unit/Validation/Rules/SizeTest.php index e4fd6ffc..965a4074 100644 --- a/tests/Unit/Validation/Rules/SizeTest.php +++ b/tests/Unit/Validation/Rules/SizeTest.php @@ -62,4 +62,3 @@ public function count(): int false, ], ]); - From 2da29e3662b7c9172474ce361fb3aa9dee0bc9c0 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:57:44 -0500 Subject: [PATCH 25/35] test: add unit tests for Digits and DigitsBetween validation rules --- .../Validation/Rules/DigitsBetweenTest.php | 27 +++++++++++++++++++ tests/Unit/Validation/Rules/DigitsTest.php | 20 ++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/Unit/Validation/Rules/DigitsBetweenTest.php create mode 100644 tests/Unit/Validation/Rules/DigitsTest.php diff --git a/tests/Unit/Validation/Rules/DigitsBetweenTest.php b/tests/Unit/Validation/Rules/DigitsBetweenTest.php new file mode 100644 index 00000000..b503c2f1 --- /dev/null +++ b/tests/Unit/Validation/Rules/DigitsBetweenTest.php @@ -0,0 +1,27 @@ +setField('value')->setData(['value' => 12]); // 2 digits + + assertFalse($rule->passes()); + assertStringContainsString('validation.digits_between', (string) $rule->message()); +}); + +it('fails when digits count is above maximum', function () { + $rule = new DigitsBetween(3, 5); + $rule->setField('value')->setData(['value' => 123456]); // 6 digits + + assertFalse($rule->passes()); +}); + +it('passes when digits count is within range', function () { + $rule = new DigitsBetween(3, 5); + $rule->setField('value')->setData(['value' => 1234]); // 4 digits + + assertTrue($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/DigitsTest.php b/tests/Unit/Validation/Rules/DigitsTest.php new file mode 100644 index 00000000..35aea50e --- /dev/null +++ b/tests/Unit/Validation/Rules/DigitsTest.php @@ -0,0 +1,20 @@ +setField('code')->setData(['code' => 12]); // length 2 + + assertFalse($rule->passes()); + assertStringContainsString('validation.digits', (string) $rule->message()); +}); + +it('passes when value digits length matches required', function () { + $rule = new Digits(3); + $rule->setField('code')->setData(['code' => 123]); + + assertTrue($rule->passes()); +}); From 487afc14b22300dfec9a69d649418798da1718a5 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 17:57:50 -0500 Subject: [PATCH 26/35] test: add unit tests for IsCollection and IsList validation rules --- .../Validation/Rules/IsCollectionTest.php | 27 +++++++++++++++++++ tests/Unit/Validation/Rules/IsListTest.php | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/Unit/Validation/Rules/IsCollectionTest.php create mode 100644 tests/Unit/Validation/Rules/IsListTest.php diff --git a/tests/Unit/Validation/Rules/IsCollectionTest.php b/tests/Unit/Validation/Rules/IsCollectionTest.php new file mode 100644 index 00000000..92112633 --- /dev/null +++ b/tests/Unit/Validation/Rules/IsCollectionTest.php @@ -0,0 +1,27 @@ +setField('items')->setData(['items' => ['a', ['nested' => 'value']]]); + + assertTrue($rule->passes()); +}); + +it('fails for scalar-only list (should be a list, not collection)', function () { + $rule = new IsCollection(); + $rule->setField('items')->setData(['items' => ['a', 'b', 'c']]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.collection', (string) $rule->message()); +}); + +it('fails for associative array where not list', function () { + $rule = new IsCollection(); + $rule->setField('items')->setData(['items' => ['a' => 'v', 'b' => 'z']]); + + assertFalse($rule->passes()); +}); diff --git a/tests/Unit/Validation/Rules/IsListTest.php b/tests/Unit/Validation/Rules/IsListTest.php new file mode 100644 index 00000000..a3da4a88 --- /dev/null +++ b/tests/Unit/Validation/Rules/IsListTest.php @@ -0,0 +1,27 @@ +setField('items')->setData(['items' => ['a', 'b', 'c']]); + + assertTrue($rule->passes()); +}); + +it('fails for non list array (associative)', function () { + $rule = new IsList(); + $rule->setField('items')->setData(['items' => ['a' => 'value', 'b' => 'v']]); + + assertFalse($rule->passes()); + assertStringContainsString('validation.list', (string) $rule->message()); +}); + +it('fails when list contains non scalar values', function () { + $rule = new IsList(); + $rule->setField('items')->setData(['items' => ['a', ['nested']]]); + + assertFalse($rule->passes()); +}); From 8713d1932d3d89b1d94a42c52c4a6c119232a306 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 18:01:29 -0500 Subject: [PATCH 27/35] test: add unit tests for Min rule message generation --- tests/Unit/Validation/Rules/MinTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Unit/Validation/Rules/MinTest.php b/tests/Unit/Validation/Rules/MinTest.php index 099a3ccc..fa6ea426 100644 --- a/tests/Unit/Validation/Rules/MinTest.php +++ b/tests/Unit/Validation/Rules/MinTest.php @@ -47,3 +47,19 @@ public function count(): int false, ], ]); + +it('builds proper min messages for each type', function (int|float $limit, string $field, array $data, string $expectedFragment): void { + $rule = new Min($limit); + $rule->setField($field)->setData($data); + + expect($rule->passes())->toBeFalse(); + + $message = $rule->message(); + + expect($message)->toBeString(); + expect($message)->toContain($expectedFragment); +})->with([ + 'numeric' => [3, 'value', ['value' => 2], 'The value must be at least 3'], + 'string' => [5, 'name', ['name' => 'John'], 'The name must be at least 5 characters'], + 'array' => [3, 'items', ['items' => ['a','b']], 'The items must have at least 3 items'], +]); From 92f54d354cab2a939ae19fa08c245c9c49c020a6 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 18:18:23 -0500 Subject: [PATCH 28/35] test: add unit tests for Exists and Unique validation rules --- tests/Unit/Validation/Rules/ExistsTest.php | 29 ++++++++++ tests/Unit/Validation/Rules/UniqueTest.php | 65 ++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tests/Unit/Validation/Rules/ExistsTest.php create mode 100644 tests/Unit/Validation/Rules/UniqueTest.php diff --git a/tests/Unit/Validation/Rules/ExistsTest.php b/tests/Unit/Validation/Rules/ExistsTest.php new file mode 100644 index 00000000..48521abf --- /dev/null +++ b/tests/Unit/Validation/Rules/ExistsTest.php @@ -0,0 +1,29 @@ +getMockBuilder(MysqlConnectionPool::class)->getMock(); + + $connection->expects($this->exactly(1)) + ->method('prepare') + ->willReturnOnConsecutiveCalls( + new Statement(new Result([['exists' => 0]])), + ); + + $this->app->swap(Connection::default(), $connection); + + $exists = new Exists(DB::from('users'), 'email'); + $exists->setData(['email' => 'Abc@ietf.org']); + $exists->setField('email'); + + expect($exists->passes())->toBeFalse(); + expect($exists->message())->toBe('The selected email is invalid.'); +}); diff --git a/tests/Unit/Validation/Rules/UniqueTest.php b/tests/Unit/Validation/Rules/UniqueTest.php new file mode 100644 index 00000000..a55c0806 --- /dev/null +++ b/tests/Unit/Validation/Rules/UniqueTest.php @@ -0,0 +1,65 @@ + 0)', function (): void { + $connection = $this->getMockBuilder(MysqlConnectionPool::class)->getMock(); + + $connection->expects($this->exactly(1)) + ->method('prepare') + ->willReturnOnConsecutiveCalls( + new Statement(new Result([[ 'COUNT(*)' => 1 ]])), + ); + + $this->app->swap(Connection::default(), $connection); + + $unique = new Unique(DB::from('users'), 'email'); + $unique->setData(['email' => 'user@example.com']); + $unique->setField('email'); + + assertFalse($unique->passes()); + assertSame('The email has already been taken.', (string) $unique->message()); +}); + +it('passes validation when value does not exist (count == 0)', function (): void { + $connection = $this->getMockBuilder(MysqlConnectionPool::class)->getMock(); + + $connection->expects($this->exactly(1)) + ->method('prepare') + ->willReturnOnConsecutiveCalls( + new Statement(new Result([[ 'COUNT(*)' => 0 ]])), + ); + + $this->app->swap(Connection::default(), $connection); + + $unique = new Unique(DB::from('users'), 'email'); + $unique->setData(['email' => 'user@example.com']); + $unique->setField('email'); + + assertTrue($unique->passes()); +}); + +it('passes validation when value does not exist using custom column', function (): void { + $connection = $this->getMockBuilder(MysqlConnectionPool::class)->getMock(); + + $connection->expects($this->exactly(1)) + ->method('prepare') + ->willReturnOnConsecutiveCalls( + new Statement(new Result([[ 'COUNT(*)' => 0 ]])), + ); + + $this->app->swap(Connection::default(), $connection); + + $unique = new Unique(DB::from('users'), 'user_email'); + $unique->setData(['email' => 'user@example.com']); + $unique->setField('email'); + + assertTrue($unique->passes()); +}); From 489bfd73554557e799a2e70d10183473646e333b Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 18:18:57 -0500 Subject: [PATCH 29/35] test: add unit tests for Mimes validation rule --- tests/Unit/Validation/Rules/MimesTest.php | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/Unit/Validation/Rules/MimesTest.php diff --git a/tests/Unit/Validation/Rules/MimesTest.php b/tests/Unit/Validation/Rules/MimesTest.php new file mode 100644 index 00000000..389d9af4 --- /dev/null +++ b/tests/Unit/Validation/Rules/MimesTest.php @@ -0,0 +1,61 @@ +getFilename(), + file_get_contents($file), + $contentType, + [['Content-Type', $contentType]] + ); + + $rule = new Mimes(['image/png']); + $rule->setField('image')->setData(['image' => $bufferedFile]); + + assertTrue($rule->passes()); +}); + +it('fails when file mime type is not allowed', function (): void { + $file = __DIR__ . '/../../../fixtures/files/user.png'; + $fileInfo = new SplFileInfo($file); + $contentType = mime_content_type($file); // image/png + + $bufferedFile = new BufferedFile( + $fileInfo->getFilename(), + file_get_contents($file), + $contentType, + [['Content-Type', $contentType]] + ); + + $rule = new Mimes(['image/jpeg']); + $rule->setField('image')->setData(['image' => $bufferedFile]); + + assertFalse($rule->passes()); + assertStringContainsString('image/jpeg', (string) $rule->message()); +}); + +it('passes when file mime type is in multi-value whitelist', function (): void { + $file = __DIR__ . '/../../../fixtures/files/user.png'; + $fileInfo = new SplFileInfo($file); + $contentType = mime_content_type($file); // image/png + + $bufferedFile = new BufferedFile( + $fileInfo->getFilename(), + file_get_contents($file), + $contentType, + [['Content-Type', $contentType]] + ); + + $rule = new Mimes(['image/jpeg', 'image/png']); + $rule->setField('image')->setData(['image' => $bufferedFile]); + + assertTrue($rule->passes()); +}); From a4d185d907ddf3e3cd2f0116755672b3a0c621a0 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Mon, 6 Oct 2025 19:49:58 -0500 Subject: [PATCH 30/35] feat(validation): add missing translation keys and update tests to assert final messages Added missing validation translation keys (collection, list, digits, digits_between, extended date keys) and adjusted rule tests to assert rendered messages instead of translation keys. --- .../Unit/Validation/Rules/DateAfterOrEqualTest.php | 3 ++- .../Validation/Rules/DateAfterOrEqualToTest.php | 2 +- tests/Unit/Validation/Rules/DateAfterToTest.php | 2 +- .../Unit/Validation/Rules/DateBeforeOrEqualTest.php | 2 +- .../Validation/Rules/DateBeforeOrEqualToTest.php | 2 +- tests/Unit/Validation/Rules/DateBeforeTest.php | 2 +- tests/Unit/Validation/Rules/DateBeforeToTest.php | 2 +- tests/Unit/Validation/Rules/DateEqualTest.php | 2 +- tests/Unit/Validation/Rules/DateEqualToTest.php | 2 +- tests/Unit/Validation/Rules/DigitsBetweenTest.php | 2 +- tests/Unit/Validation/Rules/DigitsTest.php | 2 +- tests/Unit/Validation/Rules/IsCollectionTest.php | 2 +- tests/Unit/Validation/Rules/IsListTest.php | 2 +- tests/fixtures/application/lang/en/validation.php | 13 +++++++++++++ 14 files changed, 27 insertions(+), 13 deletions(-) diff --git a/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php b/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php index 0606fe97..1507dd47 100644 --- a/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php +++ b/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php @@ -9,7 +9,8 @@ $rule->setField('date')->setData(['date' => '2023-12-31']); assertFalse($rule->passes()); - assertStringContainsString('validation.date.after_or_equal', (string) $rule->message()); + // Ahora la traducción existe, verificamos el mensaje traducido + assertStringContainsString('The date must be a date after or equal to the specified date.', (string) $rule->message()); }); it('passes when date is equal', function () { diff --git a/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php b/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php index 73b7972e..ffcafbea 100644 --- a/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php +++ b/tests/Unit/Validation/Rules/DateAfterOrEqualToTest.php @@ -12,7 +12,7 @@ ]); assertFalse($rule->passes()); - assertStringContainsString('validation.date.after_or_equal_to', (string) $rule->message()); + assertStringContainsString('must be a date after or equal to end_date', (string) $rule->message()); }); it('passes when date is equal to related date', function () { diff --git a/tests/Unit/Validation/Rules/DateAfterToTest.php b/tests/Unit/Validation/Rules/DateAfterToTest.php index c5fe939b..f8aaff5f 100644 --- a/tests/Unit/Validation/Rules/DateAfterToTest.php +++ b/tests/Unit/Validation/Rules/DateAfterToTest.php @@ -12,7 +12,7 @@ ]); assertFalse($rule->passes()); - assertStringContainsString('validation.date.after_to', (string) $rule->message()); + assertStringContainsString('must be a date after end_date', (string) $rule->message()); }); it('passes when date is after related date', function () { diff --git a/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php b/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php index 8834c9c7..ea4310cc 100644 --- a/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php +++ b/tests/Unit/Validation/Rules/DateBeforeOrEqualTest.php @@ -9,7 +9,7 @@ $rule->setField('date')->setData(['date' => '2024-01-02']); assertFalse($rule->passes()); - assertStringContainsString('validation.date.before_or_equal', (string) $rule->message()); + assertStringContainsString('The date must be a date before or equal to the specified date.', (string) $rule->message()); }); it('passes when date is equal to given date', function () { diff --git a/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php b/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php index f4d0dae9..a63c20fa 100644 --- a/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php +++ b/tests/Unit/Validation/Rules/DateBeforeOrEqualToTest.php @@ -12,7 +12,7 @@ ]); assertFalse($rule->passes()); - assertStringContainsString('validation.date.before_or_equal_to', (string) $rule->message()); + assertStringContainsString('must be a date before or equal to end_date', (string) $rule->message()); }); it('passes when date is equal to related date', function () { diff --git a/tests/Unit/Validation/Rules/DateBeforeTest.php b/tests/Unit/Validation/Rules/DateBeforeTest.php index 7c18f63f..717ca276 100644 --- a/tests/Unit/Validation/Rules/DateBeforeTest.php +++ b/tests/Unit/Validation/Rules/DateBeforeTest.php @@ -9,7 +9,7 @@ $rule->setField('date')->setData(['date' => '2024-01-01']); assertFalse($rule->passes()); - assertStringContainsString('validation.date.before', (string) $rule->message()); + assertStringContainsString('The date must be a date before the specified date.', (string) $rule->message()); }); it('passes when date is before given date', function () { diff --git a/tests/Unit/Validation/Rules/DateBeforeToTest.php b/tests/Unit/Validation/Rules/DateBeforeToTest.php index 99bde265..fdbeb1af 100644 --- a/tests/Unit/Validation/Rules/DateBeforeToTest.php +++ b/tests/Unit/Validation/Rules/DateBeforeToTest.php @@ -12,7 +12,7 @@ ]); assertFalse($rule->passes()); - assertStringContainsString('validation.date.before_to', (string) $rule->message()); + assertStringContainsString('must be a date before end_date', (string) $rule->message()); }); it('passes when date is before related date', function () { diff --git a/tests/Unit/Validation/Rules/DateEqualTest.php b/tests/Unit/Validation/Rules/DateEqualTest.php index 80ab7567..0c67eda8 100644 --- a/tests/Unit/Validation/Rules/DateEqualTest.php +++ b/tests/Unit/Validation/Rules/DateEqualTest.php @@ -9,7 +9,7 @@ $rule->setField('date')->setData(['date' => '2024-01-02']); assertFalse($rule->passes()); - assertStringContainsString('validation.date.equal', (string) $rule->message()); + assertStringContainsString('The date must be a date equal to the specified date.', (string) $rule->message()); }); it('passes when date is equal to given date', function () { diff --git a/tests/Unit/Validation/Rules/DateEqualToTest.php b/tests/Unit/Validation/Rules/DateEqualToTest.php index 664bf168..26cbeabb 100644 --- a/tests/Unit/Validation/Rules/DateEqualToTest.php +++ b/tests/Unit/Validation/Rules/DateEqualToTest.php @@ -12,7 +12,7 @@ ]); assertFalse($rule->passes()); - assertStringContainsString('validation.date.equal_to', (string) $rule->message()); + assertStringContainsString('must be a date equal to end_date', (string) $rule->message()); }); it('passes when date is equal to related date', function () { diff --git a/tests/Unit/Validation/Rules/DigitsBetweenTest.php b/tests/Unit/Validation/Rules/DigitsBetweenTest.php index b503c2f1..e9c098fd 100644 --- a/tests/Unit/Validation/Rules/DigitsBetweenTest.php +++ b/tests/Unit/Validation/Rules/DigitsBetweenTest.php @@ -9,7 +9,7 @@ $rule->setField('value')->setData(['value' => 12]); // 2 digits assertFalse($rule->passes()); - assertStringContainsString('validation.digits_between', (string) $rule->message()); + assertStringContainsString('must be between 3 and 5 digits', (string) $rule->message()); }); it('fails when digits count is above maximum', function () { diff --git a/tests/Unit/Validation/Rules/DigitsTest.php b/tests/Unit/Validation/Rules/DigitsTest.php index 35aea50e..97633fbd 100644 --- a/tests/Unit/Validation/Rules/DigitsTest.php +++ b/tests/Unit/Validation/Rules/DigitsTest.php @@ -9,7 +9,7 @@ $rule->setField('code')->setData(['code' => 12]); // length 2 assertFalse($rule->passes()); - assertStringContainsString('validation.digits', (string) $rule->message()); + assertStringContainsString('must be 3 digits', (string) $rule->message()); }); it('passes when value digits length matches required', function () { diff --git a/tests/Unit/Validation/Rules/IsCollectionTest.php b/tests/Unit/Validation/Rules/IsCollectionTest.php index 92112633..01c3b003 100644 --- a/tests/Unit/Validation/Rules/IsCollectionTest.php +++ b/tests/Unit/Validation/Rules/IsCollectionTest.php @@ -16,7 +16,7 @@ $rule->setField('items')->setData(['items' => ['a', 'b', 'c']]); assertFalse($rule->passes()); - assertStringContainsString('validation.collection', (string) $rule->message()); + assertStringContainsString('must be a collection', (string) $rule->message()); }); it('fails for associative array where not list', function () { diff --git a/tests/Unit/Validation/Rules/IsListTest.php b/tests/Unit/Validation/Rules/IsListTest.php index a3da4a88..f87163b2 100644 --- a/tests/Unit/Validation/Rules/IsListTest.php +++ b/tests/Unit/Validation/Rules/IsListTest.php @@ -16,7 +16,7 @@ $rule->setField('items')->setData(['items' => ['a' => 'value', 'b' => 'v']]); assertFalse($rule->passes()); - assertStringContainsString('validation.list', (string) $rule->message()); + assertStringContainsString('must be a list', (string) $rule->message()); }); it('fails when list contains non scalar values', function () { diff --git a/tests/fixtures/application/lang/en/validation.php b/tests/fixtures/application/lang/en/validation.php index 6f23add7..df18a4a4 100644 --- a/tests/fixtures/application/lang/en/validation.php +++ b/tests/fixtures/application/lang/en/validation.php @@ -15,6 +15,8 @@ 'numeric' => 'The :field must be a number.', 'float' => 'The :field must be a float.', 'dictionary' => 'The :field field must be a dictionary.', + 'collection' => 'The :field must be a collection.', + 'list' => 'The :field must be a list.', 'confirmed' => 'The :field does not match :other.', 'in' => 'The selected :field is invalid. Allowed: :values.', 'not_in' => 'The selected :field is invalid. Disallowed: :values.', @@ -26,6 +28,8 @@ 'ends_with' => 'The :field must end with: :values.', 'does_not_start_with' => 'The :field must not start with: :values.', 'does_not_end_with' => 'The :field must not end with: :values.', + 'digits' => 'The :field must be :digits digits.', + 'digits_between' => 'The :field must be between :min and :max digits.', 'size' => [ 'numeric' => 'The :field must be :size.', 'string' => 'The :field must be :size characters.', @@ -54,5 +58,14 @@ 'is_date' => 'The :field is not a valid date.', 'after' => 'The :field must be a date after the specified date.', 'format' => 'The :field does not match the format :format.', + 'equal_to' => 'The :field must be a date equal to :other.', + 'after_to' => 'The :field must be a date after :other.', + 'after_or_equal_to' => 'The :field must be a date after or equal to :other.', + 'before_or_equal_to' => 'The :field must be a date before or equal to :other.', + 'after_or_equal' => 'The :field must be a date after or equal to the specified date.', + 'before_or_equal' => 'The :field must be a date before or equal to the specified date.', + 'equal' => 'The :field must be a date equal to the specified date.', + 'before_to' => 'The :field must be a date before :other.', + 'before' => 'The :field must be a date before the specified date.', ], ]; From 6f4d426be7ff782fc0d7cf0dc8cb49398d45e903 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Tue, 7 Oct 2025 08:34:47 -0500 Subject: [PATCH 31/35] refactor(validation): use getFieldForHumans() across rule error messages --- src/Validation/Rules/Between.php | 2 +- src/Validation/Rules/Confirmed.php | 2 +- src/Validation/Rules/Dates/After.php | 2 +- src/Validation/Rules/Dates/AfterOrEqual.php | 2 +- src/Validation/Rules/Dates/AfterOrEqualTo.php | 2 +- src/Validation/Rules/Dates/AfterTo.php | 2 +- src/Validation/Rules/Dates/Before.php | 2 +- src/Validation/Rules/Dates/BeforeOrEqual.php | 2 +- src/Validation/Rules/Dates/BeforeOrEqualTo.php | 2 +- src/Validation/Rules/Dates/BeforeTo.php | 2 +- src/Validation/Rules/Dates/Equal.php | 2 +- src/Validation/Rules/Dates/EqualTo.php | 2 +- src/Validation/Rules/Dates/Format.php | 2 +- src/Validation/Rules/Dates/IsDate.php | 2 +- src/Validation/Rules/DoesNotEndWith.php | 2 +- src/Validation/Rules/DoesNotStartWith.php | 2 +- src/Validation/Rules/EndsWith.php | 2 +- src/Validation/Rules/Exists.php | 2 +- src/Validation/Rules/In.php | 2 +- src/Validation/Rules/IsArray.php | 2 +- src/Validation/Rules/IsBool.php | 2 +- src/Validation/Rules/IsCollection.php | 2 +- src/Validation/Rules/IsDictionary.php | 2 +- src/Validation/Rules/IsEmail.php | 2 +- src/Validation/Rules/IsFile.php | 2 +- src/Validation/Rules/IsList.php | 2 +- src/Validation/Rules/IsString.php | 2 +- src/Validation/Rules/IsUrl.php | 2 +- src/Validation/Rules/Max.php | 2 +- src/Validation/Rules/Mimes.php | 2 +- src/Validation/Rules/Min.php | 2 +- src/Validation/Rules/NotIn.php | 2 +- src/Validation/Rules/Numbers/Digits.php | 2 +- src/Validation/Rules/Numbers/DigitsBetween.php | 2 +- src/Validation/Rules/Numbers/IsFloat.php | 2 +- src/Validation/Rules/Numbers/IsInteger.php | 2 +- src/Validation/Rules/Numbers/IsNumeric.php | 2 +- src/Validation/Rules/RegEx.php | 2 +- src/Validation/Rules/Required.php | 2 +- src/Validation/Rules/Rule.php | 11 +++++++++++ src/Validation/Rules/Size.php | 2 +- src/Validation/Rules/StartsWith.php | 2 +- src/Validation/Rules/Ulid.php | 2 +- src/Validation/Rules/Unique.php | 2 +- src/Validation/Rules/Uuid.php | 2 +- tests/fixtures/application/lang/en/validation.php | 5 +++++ 46 files changed, 60 insertions(+), 44 deletions(-) diff --git a/src/Validation/Rules/Between.php b/src/Validation/Rules/Between.php index b1c11038..567141ee 100644 --- a/src/Validation/Rules/Between.php +++ b/src/Validation/Rules/Between.php @@ -35,7 +35,7 @@ public function message(): string|null }; return trans($key, [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'min' => $this->min, 'max' => $this->max, ]); diff --git a/src/Validation/Rules/Confirmed.php b/src/Validation/Rules/Confirmed.php index f84f502a..a1000b07 100644 --- a/src/Validation/Rules/Confirmed.php +++ b/src/Validation/Rules/Confirmed.php @@ -24,7 +24,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.confirmed', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'other' => $this->confirmationField, ]); } diff --git a/src/Validation/Rules/Dates/After.php b/src/Validation/Rules/Dates/After.php index 0ab6689b..7297a35a 100644 --- a/src/Validation/Rules/Dates/After.php +++ b/src/Validation/Rules/Dates/After.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.after', ['field' => $this->field]); + return trans('validation.date.after', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Dates/AfterOrEqual.php b/src/Validation/Rules/Dates/AfterOrEqual.php index 110633ea..6ba3b263 100644 --- a/src/Validation/Rules/Dates/AfterOrEqual.php +++ b/src/Validation/Rules/Dates/AfterOrEqual.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.after_or_equal', ['field' => $this->field]); + return trans('validation.date.after_or_equal', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Dates/AfterOrEqualTo.php b/src/Validation/Rules/Dates/AfterOrEqualTo.php index d6a91f8d..7b047d37 100644 --- a/src/Validation/Rules/Dates/AfterOrEqualTo.php +++ b/src/Validation/Rules/Dates/AfterOrEqualTo.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.date.after_or_equal_to', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'other' => $this->relatedField, ]); } diff --git a/src/Validation/Rules/Dates/AfterTo.php b/src/Validation/Rules/Dates/AfterTo.php index fe6bcac1..a6643dbf 100644 --- a/src/Validation/Rules/Dates/AfterTo.php +++ b/src/Validation/Rules/Dates/AfterTo.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.date.after_to', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'other' => $this->relatedField, ]); } diff --git a/src/Validation/Rules/Dates/Before.php b/src/Validation/Rules/Dates/Before.php index 377747de..7069450f 100644 --- a/src/Validation/Rules/Dates/Before.php +++ b/src/Validation/Rules/Dates/Before.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.before', ['field' => $this->field]); + return trans('validation.date.before', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Dates/BeforeOrEqual.php b/src/Validation/Rules/Dates/BeforeOrEqual.php index f7f98d3d..31586bb9 100644 --- a/src/Validation/Rules/Dates/BeforeOrEqual.php +++ b/src/Validation/Rules/Dates/BeforeOrEqual.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.before_or_equal', ['field' => $this->field]); + return trans('validation.date.before_or_equal', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Dates/BeforeOrEqualTo.php b/src/Validation/Rules/Dates/BeforeOrEqualTo.php index 047e8201..9ec91206 100644 --- a/src/Validation/Rules/Dates/BeforeOrEqualTo.php +++ b/src/Validation/Rules/Dates/BeforeOrEqualTo.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.date.before_or_equal_to', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'other' => $this->relatedField, ]); } diff --git a/src/Validation/Rules/Dates/BeforeTo.php b/src/Validation/Rules/Dates/BeforeTo.php index a16200c5..aea0b0f7 100644 --- a/src/Validation/Rules/Dates/BeforeTo.php +++ b/src/Validation/Rules/Dates/BeforeTo.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.date.before_to', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'other' => $this->relatedField, ]); } diff --git a/src/Validation/Rules/Dates/Equal.php b/src/Validation/Rules/Dates/Equal.php index 31b2e9d5..0cf98adf 100644 --- a/src/Validation/Rules/Dates/Equal.php +++ b/src/Validation/Rules/Dates/Equal.php @@ -24,6 +24,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.equal', ['field' => $this->field]); + return trans('validation.date.equal', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Dates/EqualTo.php b/src/Validation/Rules/Dates/EqualTo.php index af3740a3..b5792bdf 100644 --- a/src/Validation/Rules/Dates/EqualTo.php +++ b/src/Validation/Rules/Dates/EqualTo.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.date.equal_to', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'other' => $this->relatedField, ]); } diff --git a/src/Validation/Rules/Dates/Format.php b/src/Validation/Rules/Dates/Format.php index 7deb00cb..e639996c 100644 --- a/src/Validation/Rules/Dates/Format.php +++ b/src/Validation/Rules/Dates/Format.php @@ -23,6 +23,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.format', ['field' => $this->field, 'format' => $this->format]); + return trans('validation.date.format', ['field' => $this->getFieldForHumans(), 'format' => $this->format]); } } diff --git a/src/Validation/Rules/Dates/IsDate.php b/src/Validation/Rules/Dates/IsDate.php index dbe07188..69528103 100644 --- a/src/Validation/Rules/Dates/IsDate.php +++ b/src/Validation/Rules/Dates/IsDate.php @@ -30,6 +30,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.is_date', ['field' => $this->field]); + return trans('validation.date.is_date', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/DoesNotEndWith.php b/src/Validation/Rules/DoesNotEndWith.php index 99820817..cc6d1ed6 100644 --- a/src/Validation/Rules/DoesNotEndWith.php +++ b/src/Validation/Rules/DoesNotEndWith.php @@ -14,7 +14,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.does_not_end_with', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => $this->needle, ]); } diff --git a/src/Validation/Rules/DoesNotStartWith.php b/src/Validation/Rules/DoesNotStartWith.php index 96351993..7328c1c4 100644 --- a/src/Validation/Rules/DoesNotStartWith.php +++ b/src/Validation/Rules/DoesNotStartWith.php @@ -14,7 +14,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.does_not_start_with', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => $this->needle, ]); } diff --git a/src/Validation/Rules/EndsWith.php b/src/Validation/Rules/EndsWith.php index 3d96fb52..cc0c851d 100644 --- a/src/Validation/Rules/EndsWith.php +++ b/src/Validation/Rules/EndsWith.php @@ -14,7 +14,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.ends_with', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => $this->needle, ]); } diff --git a/src/Validation/Rules/Exists.php b/src/Validation/Rules/Exists.php index 92c80df4..a06147de 100644 --- a/src/Validation/Rules/Exists.php +++ b/src/Validation/Rules/Exists.php @@ -24,7 +24,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.exists', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), ]); } } diff --git a/src/Validation/Rules/In.php b/src/Validation/Rules/In.php index 6eeba192..b222795a 100644 --- a/src/Validation/Rules/In.php +++ b/src/Validation/Rules/In.php @@ -21,7 +21,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.in', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => implode(', ', $this->haystack), ]); } diff --git a/src/Validation/Rules/IsArray.php b/src/Validation/Rules/IsArray.php index c4ecd03f..0cf35a8e 100644 --- a/src/Validation/Rules/IsArray.php +++ b/src/Validation/Rules/IsArray.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.array', ['field' => $this->field]); + return trans('validation.array', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsBool.php b/src/Validation/Rules/IsBool.php index 88951b74..2cacc3a6 100644 --- a/src/Validation/Rules/IsBool.php +++ b/src/Validation/Rules/IsBool.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.boolean', ['field' => $this->field]); + return trans('validation.boolean', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsCollection.php b/src/Validation/Rules/IsCollection.php index 5451ca18..f1e6bdab 100644 --- a/src/Validation/Rules/IsCollection.php +++ b/src/Validation/Rules/IsCollection.php @@ -20,6 +20,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.collection', ['field' => $this->field]); + return trans('validation.collection', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsDictionary.php b/src/Validation/Rules/IsDictionary.php index 09691526..2b7edab9 100644 --- a/src/Validation/Rules/IsDictionary.php +++ b/src/Validation/Rules/IsDictionary.php @@ -20,6 +20,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.dictionary', ['field' => $this->field]); + return trans('validation.dictionary', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsEmail.php b/src/Validation/Rules/IsEmail.php index 1b887b3c..944c5855 100644 --- a/src/Validation/Rules/IsEmail.php +++ b/src/Validation/Rules/IsEmail.php @@ -38,6 +38,6 @@ public function pusValidation(EmailValidation $emailValidation): self public function message(): string|null { - return trans('validation.email', ['field' => $this->field]); + return trans('validation.email', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsFile.php b/src/Validation/Rules/IsFile.php index 4efd2b2e..06466f6b 100644 --- a/src/Validation/Rules/IsFile.php +++ b/src/Validation/Rules/IsFile.php @@ -17,6 +17,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.file', ['field' => $this->field]); + return trans('validation.file', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsList.php b/src/Validation/Rules/IsList.php index 7c6e2b3b..5b21294e 100644 --- a/src/Validation/Rules/IsList.php +++ b/src/Validation/Rules/IsList.php @@ -31,6 +31,6 @@ protected function isScalar(array $data): bool public function message(): string|null { - return trans('validation.list', ['field' => $this->field]); + return trans('validation.list', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsString.php b/src/Validation/Rules/IsString.php index b6a2e023..9891c068 100644 --- a/src/Validation/Rules/IsString.php +++ b/src/Validation/Rules/IsString.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.string', ['field' => $this->field]); + return trans('validation.string', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsUrl.php b/src/Validation/Rules/IsUrl.php index 3a9bcdce..d45d4ec4 100644 --- a/src/Validation/Rules/IsUrl.php +++ b/src/Validation/Rules/IsUrl.php @@ -14,6 +14,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.url', ['field' => $this->field]); + return trans('validation.url', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Max.php b/src/Validation/Rules/Max.php index c4a5739f..43ad759f 100644 --- a/src/Validation/Rules/Max.php +++ b/src/Validation/Rules/Max.php @@ -24,7 +24,7 @@ public function message(): string|null }; return trans($key, [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'max' => $this->limit, ]); } diff --git a/src/Validation/Rules/Mimes.php b/src/Validation/Rules/Mimes.php index e426ae3b..51d2c4f9 100644 --- a/src/Validation/Rules/Mimes.php +++ b/src/Validation/Rules/Mimes.php @@ -21,7 +21,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.mimes', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => implode(', ', $this->haystack), ]); } diff --git a/src/Validation/Rules/Min.php b/src/Validation/Rules/Min.php index e02c64ba..b55c6033 100644 --- a/src/Validation/Rules/Min.php +++ b/src/Validation/Rules/Min.php @@ -24,7 +24,7 @@ public function message(): string|null }; return trans($key, [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'min' => $this->limit, ]); } diff --git a/src/Validation/Rules/NotIn.php b/src/Validation/Rules/NotIn.php index 1abb0b4b..9035cb2f 100644 --- a/src/Validation/Rules/NotIn.php +++ b/src/Validation/Rules/NotIn.php @@ -14,7 +14,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.not_in', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => implode(', ', $this->haystack), ]); } diff --git a/src/Validation/Rules/Numbers/Digits.php b/src/Validation/Rules/Numbers/Digits.php index a76e1515..a36f9a10 100644 --- a/src/Validation/Rules/Numbers/Digits.php +++ b/src/Validation/Rules/Numbers/Digits.php @@ -25,7 +25,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.digits', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'digits' => $this->digits, ]); } diff --git a/src/Validation/Rules/Numbers/DigitsBetween.php b/src/Validation/Rules/Numbers/DigitsBetween.php index 4bf1bd4b..a1b6bd65 100644 --- a/src/Validation/Rules/Numbers/DigitsBetween.php +++ b/src/Validation/Rules/Numbers/DigitsBetween.php @@ -23,7 +23,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.digits_between', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'min' => $this->min, 'max' => $this->max, ]); diff --git a/src/Validation/Rules/Numbers/IsFloat.php b/src/Validation/Rules/Numbers/IsFloat.php index 82b767c7..a0ea4033 100644 --- a/src/Validation/Rules/Numbers/IsFloat.php +++ b/src/Validation/Rules/Numbers/IsFloat.php @@ -17,6 +17,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.float', ['field' => $this->field]); + return trans('validation.float', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Numbers/IsInteger.php b/src/Validation/Rules/Numbers/IsInteger.php index e0ca1b7e..a5ad9cb1 100644 --- a/src/Validation/Rules/Numbers/IsInteger.php +++ b/src/Validation/Rules/Numbers/IsInteger.php @@ -17,6 +17,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.integer', ['field' => $this->field]); + return trans('validation.integer', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Numbers/IsNumeric.php b/src/Validation/Rules/Numbers/IsNumeric.php index b188bb50..718e7a25 100644 --- a/src/Validation/Rules/Numbers/IsNumeric.php +++ b/src/Validation/Rules/Numbers/IsNumeric.php @@ -17,6 +17,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.numeric', ['field' => $this->field]); + return trans('validation.numeric', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/RegEx.php b/src/Validation/Rules/RegEx.php index 56bc9285..c1dbb59d 100644 --- a/src/Validation/Rules/RegEx.php +++ b/src/Validation/Rules/RegEx.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.regex', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), ]); } } diff --git a/src/Validation/Rules/Required.php b/src/Validation/Rules/Required.php index 8761b5c9..3e32bd9c 100644 --- a/src/Validation/Rules/Required.php +++ b/src/Validation/Rules/Required.php @@ -35,6 +35,6 @@ public function skip(): bool public function message(): string|null { - return trans('validation.required', ['field' => $this->field]); + return trans('validation.required', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Rule.php b/src/Validation/Rules/Rule.php index 179c63f8..2898b397 100644 --- a/src/Validation/Rules/Rule.php +++ b/src/Validation/Rules/Rule.php @@ -6,6 +6,7 @@ use Adbar\Dot; use Amp\Http\Server\FormParser\BufferedFile; +use Phenix\Facades\Translator; use Phenix\Validation\Contracts\Rule as RuleContract; use function is_array; @@ -14,6 +15,7 @@ abstract class Rule implements RuleContract { protected string $field; + protected Dot $data; public function __construct(array|null $data = null) @@ -51,4 +53,13 @@ protected function getValueType(): string { return gettype($this->data->get($this->field) ?? null); } + + protected function getFieldForHumans(): string + { + if (Translator::has("validation.fields.{$this->field}")) { + return Translator::get("validation.fields.{$this->field}"); + } + + return $this->field; + } } diff --git a/src/Validation/Rules/Size.php b/src/Validation/Rules/Size.php index bea1ce3a..5d48ba62 100644 --- a/src/Validation/Rules/Size.php +++ b/src/Validation/Rules/Size.php @@ -62,7 +62,7 @@ public function message(): string|null }; return trans($key, [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'size' => $this->limit, ]); } diff --git a/src/Validation/Rules/StartsWith.php b/src/Validation/Rules/StartsWith.php index b46fd0ac..8864e118 100644 --- a/src/Validation/Rules/StartsWith.php +++ b/src/Validation/Rules/StartsWith.php @@ -19,7 +19,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.starts_with', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), 'values' => $this->needle, ]); } diff --git a/src/Validation/Rules/Ulid.php b/src/Validation/Rules/Ulid.php index c2e37208..80dbc752 100644 --- a/src/Validation/Rules/Ulid.php +++ b/src/Validation/Rules/Ulid.php @@ -16,6 +16,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.ulid', ['field' => $this->field]); + return trans('validation.ulid', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Unique.php b/src/Validation/Rules/Unique.php index 0fc715ab..ae6de253 100644 --- a/src/Validation/Rules/Unique.php +++ b/src/Validation/Rules/Unique.php @@ -16,7 +16,7 @@ public function passes(): bool public function message(): string|null { return trans('validation.unique', [ - 'field' => $this->field, + 'field' => $this->getFieldForHumans(), ]); } } diff --git a/src/Validation/Rules/Uuid.php b/src/Validation/Rules/Uuid.php index 1b058a88..4da4621c 100644 --- a/src/Validation/Rules/Uuid.php +++ b/src/Validation/Rules/Uuid.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.uuid', ['field' => $this->field]); + return trans('validation.uuid', ['field' => $this->getFieldForHumans()]); } } diff --git a/tests/fixtures/application/lang/en/validation.php b/tests/fixtures/application/lang/en/validation.php index df18a4a4..eb20f53d 100644 --- a/tests/fixtures/application/lang/en/validation.php +++ b/tests/fixtures/application/lang/en/validation.php @@ -68,4 +68,9 @@ 'before_to' => 'The :field must be a date before :other.', 'before' => 'The :field must be a date before the specified date.', ], + + 'fields' => [ + 'full_name' => 'Full name', + 'customer.email' => 'customer email address', + ], ]; From 8904b16d7737fd9858d0312446d5f05976f5a4d7 Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Tue, 7 Oct 2025 08:45:11 -0500 Subject: [PATCH 32/35] style: php cs --- src/Validation/Rules/Dates/Equal.php | 2 +- src/Validation/Rules/IsArray.php | 2 +- src/Validation/Rules/IsBool.php | 2 +- src/Validation/Rules/IsEmail.php | 2 +- src/Validation/Rules/IsString.php | 2 +- src/Validation/Rules/Ulid.php | 2 +- src/Validation/Rules/Uuid.php | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Validation/Rules/Dates/Equal.php b/src/Validation/Rules/Dates/Equal.php index 0cf98adf..20625d7a 100644 --- a/src/Validation/Rules/Dates/Equal.php +++ b/src/Validation/Rules/Dates/Equal.php @@ -24,6 +24,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.date.equal', ['field' => $this->getFieldForHumans()]); + return trans('validation.date.equal', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsArray.php b/src/Validation/Rules/IsArray.php index 0cf35a8e..016e634e 100644 --- a/src/Validation/Rules/IsArray.php +++ b/src/Validation/Rules/IsArray.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.array', ['field' => $this->getFieldForHumans()]); + return trans('validation.array', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsBool.php b/src/Validation/Rules/IsBool.php index 2cacc3a6..2e5b601d 100644 --- a/src/Validation/Rules/IsBool.php +++ b/src/Validation/Rules/IsBool.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.boolean', ['field' => $this->getFieldForHumans()]); + return trans('validation.boolean', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsEmail.php b/src/Validation/Rules/IsEmail.php index 944c5855..0f35aaf3 100644 --- a/src/Validation/Rules/IsEmail.php +++ b/src/Validation/Rules/IsEmail.php @@ -38,6 +38,6 @@ public function pusValidation(EmailValidation $emailValidation): self public function message(): string|null { - return trans('validation.email', ['field' => $this->getFieldForHumans()]); + return trans('validation.email', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/IsString.php b/src/Validation/Rules/IsString.php index 9891c068..7e960169 100644 --- a/src/Validation/Rules/IsString.php +++ b/src/Validation/Rules/IsString.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.string', ['field' => $this->getFieldForHumans()]); + return trans('validation.string', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Ulid.php b/src/Validation/Rules/Ulid.php index 80dbc752..846338a2 100644 --- a/src/Validation/Rules/Ulid.php +++ b/src/Validation/Rules/Ulid.php @@ -16,6 +16,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.ulid', ['field' => $this->getFieldForHumans()]); + return trans('validation.ulid', ['field' => $this->getFieldForHumans()]); } } diff --git a/src/Validation/Rules/Uuid.php b/src/Validation/Rules/Uuid.php index 4da4621c..67adc216 100644 --- a/src/Validation/Rules/Uuid.php +++ b/src/Validation/Rules/Uuid.php @@ -15,6 +15,6 @@ public function passes(): bool public function message(): string|null { - return trans('validation.uuid', ['field' => $this->getFieldForHumans()]); + return trans('validation.uuid', ['field' => $this->getFieldForHumans()]); } } From a713a37690ce699b26cc141b83cca1b93f96206a Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Tue, 7 Oct 2025 09:32:57 -0500 Subject: [PATCH 33/35] refactor(validation): update field label for last name in validation language file --- tests/fixtures/application/lang/en/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/application/lang/en/validation.php b/tests/fixtures/application/lang/en/validation.php index eb20f53d..242bed4b 100644 --- a/tests/fixtures/application/lang/en/validation.php +++ b/tests/fixtures/application/lang/en/validation.php @@ -70,7 +70,7 @@ ], 'fields' => [ - 'full_name' => 'Full name', + 'last_name' => 'last name', 'customer.email' => 'customer email address', ], ]; From 706a8552388601be41d5403c07ad37a26ca2cb6b Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Tue, 7 Oct 2025 10:30:17 -0500 Subject: [PATCH 34/35] tests: display field name for human when field is registered in fields of validation file --- tests/Unit/Validation/Rules/IsStringTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Validation/Rules/IsStringTest.php b/tests/Unit/Validation/Rules/IsStringTest.php index fb5c4727..8d483d64 100644 --- a/tests/Unit/Validation/Rules/IsStringTest.php +++ b/tests/Unit/Validation/Rules/IsStringTest.php @@ -4,7 +4,7 @@ use Phenix\Validation\Rules\IsString; -it('fails is_string when value not a string', function () { +it('fails when value not a string', function () { $rule = new IsString(); $rule->setField('name')->setData(['name' => 123]); @@ -12,9 +12,17 @@ assertStringContainsString('must be a string', (string) $rule->message()); }); -it('passes is_string when value is a string', function () { +it('passes when value is a string', function () { $rule = new IsString(); $rule->setField('name')->setData(['name' => 'John']); assertTrue($rule->passes()); }); + +it('display field name for humans', function () { + $rule = new IsString(); + $rule->setField('last_name')->setData(['last_name' => 123]); + + assertFalse($rule->passes()); + assertStringContainsString('last name must be a string', (string) $rule->message()); +}); From b7a5f9754e45717adba703dc538cf9357612e50d Mon Sep 17 00:00:00 2001 From: barbosa89 Date: Tue, 7 Oct 2025 12:13:22 -0500 Subject: [PATCH 35/35] chore: rename tests, remove comment [skip ci] --- tests/Unit/Validation/Rules/DateAfterOrEqualTest.php | 1 - tests/Unit/Validation/Rules/IsNumericTest.php | 4 ++-- tests/Unit/Validation/Rules/IsUrlTest.php | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php b/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php index 1507dd47..754c3dd6 100644 --- a/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php +++ b/tests/Unit/Validation/Rules/DateAfterOrEqualTest.php @@ -9,7 +9,6 @@ $rule->setField('date')->setData(['date' => '2023-12-31']); assertFalse($rule->passes()); - // Ahora la traducción existe, verificamos el mensaje traducido assertStringContainsString('The date must be a date after or equal to the specified date.', (string) $rule->message()); }); diff --git a/tests/Unit/Validation/Rules/IsNumericTest.php b/tests/Unit/Validation/Rules/IsNumericTest.php index dd13c38b..194da4a8 100644 --- a/tests/Unit/Validation/Rules/IsNumericTest.php +++ b/tests/Unit/Validation/Rules/IsNumericTest.php @@ -4,7 +4,7 @@ use Phenix\Validation\Rules\Numbers\IsNumeric; -it('fails is_numeric when value not numeric', function () { +it('fails when value not numeric', function () { $rule = new IsNumeric(); $rule->setField('code')->setData(['code' => 'abc']); @@ -12,7 +12,7 @@ assertStringContainsString('must be a number', (string) $rule->message()); }); -it('passes is_numeric when value numeric', function () { +it('passes when value numeric', function () { $rule = new IsNumeric(); $rule->setField('code')->setData(['code' => '123']); diff --git a/tests/Unit/Validation/Rules/IsUrlTest.php b/tests/Unit/Validation/Rules/IsUrlTest.php index accb9b66..9c09bebe 100644 --- a/tests/Unit/Validation/Rules/IsUrlTest.php +++ b/tests/Unit/Validation/Rules/IsUrlTest.php @@ -4,7 +4,7 @@ use Phenix\Validation\Rules\IsUrl; -it('fails is_url when invalid url', function () { +it('fails when invalid url', function () { $rule = new IsUrl(); $rule->setField('site')->setData(['site' => 'notaurl']); @@ -12,7 +12,7 @@ assertStringContainsString('valid URL', (string) $rule->message()); }); -it('passes is_url when valid', function () { +it('passes when valid', function () { $rule = new IsUrl(); $rule->setField('site')->setData(['site' => 'https://example.com']);