From d4e4d79c4888d44b9cf3e66ac95116532a058a41 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:13:10 +0100 Subject: [PATCH] Rewrite validation rule --- CHANGELOG.md | 7 + README.md | 42 ++-- phpstan-baseline.neon | 133 ------------- phpstan.neon.dist | 1 - src/Extensions/PostalCode.php | 45 ----- src/Extensions/PostalCodeFor.php | 58 ------ src/PostalCodeValidator.php | 4 - src/Rules/PostalCode.php | 130 ++++++------ src/Rules/PostalCodeExtension.php | 36 ++++ src/ValidationServiceProvider.php | 22 +-- tests/Integration/PostalCodeForTest.php | 121 ------------ tests/Integration/PostalCodeTest.php | 94 --------- tests/Integration/PostalCodeWithTest.php | 164 --------------- tests/Rules/PostalCodeRuleExtensionTest.php | 190 ++++++++++++++++++ tests/Rules/PostalCodeRuleTest.php | 208 ++++++++++++++++++++ tests/Unit/PostalCodeRuleTest.php | 41 ---- tests/Unit/PostalCodeValidatorTest.php | 12 -- 17 files changed, 527 insertions(+), 781 deletions(-) delete mode 100644 phpstan-baseline.neon delete mode 100644 src/Extensions/PostalCode.php delete mode 100644 src/Extensions/PostalCodeFor.php create mode 100644 src/Rules/PostalCodeExtension.php delete mode 100644 tests/Integration/PostalCodeForTest.php delete mode 100644 tests/Integration/PostalCodeTest.php delete mode 100644 tests/Integration/PostalCodeWithTest.php create mode 100644 tests/Rules/PostalCodeRuleExtensionTest.php create mode 100644 tests/Rules/PostalCodeRuleTest.php delete mode 100644 tests/Unit/PostalCodeRuleTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b6000..bc61f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 4.0.0 +### Changed + +- Country codes in incorrect casing are no longer accepted +- Validation now passes on `null`. Add `required` where appropriate to enforce presence +- Validation based on a country field is now done via the `postal_code` rule + ### Removed - Support for Lumen @@ -15,3 +21,4 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Custom error replacers `:countries` and `:examples` - Manual validation through facade - Validation overriding +- Validation rule `postal_code_with`, functionality has been merged into `postal_code` diff --git a/README.md b/README.md index b0056fc..bfc1a06 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Worldwide postal code validation for Laravel, based on Google's Address Data Ser - [Installation](#installation) - [Usage](#usage) - [Available rules](#available-rules) - - [Fluent API](#fluent-api) - [Adding an error message](#adding-an-error-message) - [Changelog](#changelog) - [Contributing](#contributing) @@ -59,58 +58,41 @@ package manually, you can do this by adding the following line to your `config/a ``` ## Usage + Postal code validation perfectly integrates into your Laravel application, you can use it just like you would any framework validation rule. ### Available rules + This package adds the following validation rules: #### postal_code:foo,bar,... + The field under validation must be a valid postal code in at least one of the given countries. Arguments must be countries in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) format. ```php -'postal_code' => 'postal_code:NL,DE,FR,BE' -``` - -#### postal_code_with:foo,bar,... -The field under validation must be a postal code in at least one of the countries in the given fields _only if_ at least -one of the specified fields is present. - -```php -'billing.country' => 'required|string|max:2', -... -'shipping.country' => 'nullable|string|max:2', -'shipping.postal_code' => 'postal_code_with:billing.country,shipping.country' +Validator::validate($data, [ + 'address.postal_code' => 'required|postal_code:NL,BE', +]); ``` -### Fluent API -If you prefer using a fluent object style over string based rules, that's also available: +Alternatively, you may use an object-oriented approach: ```php -'postal_code' => [ - PostalCode::for('NL')->or('BE'), -], -``` +use Axlon\PostalCodeValidation\Rules\PostalCode; -The same goes for the `postal_code_with` rule: - -```php -'billing.country' => 'required|string|max:2', -... -'shipping.country' => 'nullable|string|max:2', -'shipping.postal_code' => [ - PostalCode::with('billing.country')->or('shipping.country') -], +Validator::validate($data, [ + 'address.postal_code' => ['required', PostalCode::of('NL', 'BE')], +]); ``` ### Adding an error message -To add a meaningful error message, add the following lines to `resources/lang/{your language}/validation.php`: +To add a meaningful error message, add the following to `resources/lang/{language}/validation.php`: ```php 'postal_code' => ':Attribute is not a valid postal code.', -'postal_code_with' => ':Attribute is not a valid postal code.', ``` ## Changelog diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon deleted file mode 100644 index 178bb73..0000000 --- a/phpstan-baseline.neon +++ /dev/null @@ -1,133 +0,0 @@ -parameters: - ignoreErrors: - - - message: '#^Parameter \#1 \$countryCode of method Axlon\\PostalCodeValidation\\PostalCodeValidator\:\:passes\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Extensions/PostalCodeFor.php - - - - message: '#^Cannot call method all\(\) on mixed\.$#' - identifier: method.nonObject - count: 6 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Cannot call method errors\(\) on mixed\.$#' - identifier: method.nonObject - count: 6 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Cannot call method make\(\) on Illuminate\\Foundation\\Application\|null\.$#' - identifier: method.nonObject - count: 7 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Cannot call method make\(\) on mixed\.$#' - identifier: method.nonObject - count: 7 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Cannot call method passes\(\) on mixed\.$#' - identifier: method.nonObject - count: 6 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Cannot call method validate\(\) on mixed\.$#' - identifier: method.nonObject - count: 1 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Parameter \#2 \$haystack of static method PHPUnit\\Framework\\Assert\:\:assertContains\(\) expects iterable, mixed given\.$#' - identifier: argument.type - count: 3 - path: tests/Integration/PostalCodeForTest.php - - - - message: '#^Cannot call method all\(\) on mixed\.$#' - identifier: method.nonObject - count: 4 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Cannot call method errors\(\) on mixed\.$#' - identifier: method.nonObject - count: 4 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Cannot call method make\(\) on Illuminate\\Foundation\\Application\|null\.$#' - identifier: method.nonObject - count: 5 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Cannot call method make\(\) on mixed\.$#' - identifier: method.nonObject - count: 5 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Cannot call method passes\(\) on mixed\.$#' - identifier: method.nonObject - count: 4 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Cannot call method validate\(\) on mixed\.$#' - identifier: method.nonObject - count: 1 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Parameter \#2 \$haystack of static method PHPUnit\\Framework\\Assert\:\:assertContains\(\) expects iterable, mixed given\.$#' - identifier: argument.type - count: 3 - path: tests/Integration/PostalCodeTest.php - - - - message: '#^Cannot call method all\(\) on mixed\.$#' - identifier: method.nonObject - count: 9 - path: tests/Integration/PostalCodeWithTest.php - - - - message: '#^Cannot call method errors\(\) on mixed\.$#' - identifier: method.nonObject - count: 9 - path: tests/Integration/PostalCodeWithTest.php - - - - message: '#^Cannot call method make\(\) on Illuminate\\Foundation\\Application\|null\.$#' - identifier: method.nonObject - count: 10 - path: tests/Integration/PostalCodeWithTest.php - - - - message: '#^Cannot call method make\(\) on mixed\.$#' - identifier: method.nonObject - count: 10 - path: tests/Integration/PostalCodeWithTest.php - - - - message: '#^Cannot call method passes\(\) on mixed\.$#' - identifier: method.nonObject - count: 9 - path: tests/Integration/PostalCodeWithTest.php - - - - message: '#^Cannot call method validate\(\) on mixed\.$#' - identifier: method.nonObject - count: 1 - path: tests/Integration/PostalCodeWithTest.php - - - - message: '#^Parameter \#2 \$haystack of static method PHPUnit\\Framework\\Assert\:\:assertContains\(\) expects iterable, mixed given\.$#' - identifier: argument.type - count: 5 - path: tests/Integration/PostalCodeWithTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0c1a17b..8608441 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,6 +1,5 @@ includes: - phar://phpstan.phar/conf/bleedingEdge.neon - - ./phpstan-baseline.neon parameters: level: 10 diff --git a/src/Extensions/PostalCode.php b/src/Extensions/PostalCode.php deleted file mode 100644 index a139180..0000000 --- a/src/Extensions/PostalCode.php +++ /dev/null @@ -1,45 +0,0 @@ -validator->passes($parameter, $value)) { - return true; - } - } - - return false; - } -} diff --git a/src/Extensions/PostalCodeFor.php b/src/Extensions/PostalCodeFor.php deleted file mode 100644 index 31726d5..0000000 --- a/src/Extensions/PostalCodeFor.php +++ /dev/null @@ -1,58 +0,0 @@ -getData()), $parameters); - - if ($parameters === []) { - return true; - } - - foreach ($parameters as $parameter) { - if ($parameter === null) { - continue; - } - - if ($this->validator->passes($parameter, $value)) { - return true; - } - } - - return false; - } -} diff --git a/src/PostalCodeValidator.php b/src/PostalCodeValidator.php index 11ea0d3..abd359b 100644 --- a/src/PostalCodeValidator.php +++ b/src/PostalCodeValidator.php @@ -81,8 +81,6 @@ public function passes(string $countryCode, ?string ...$postalCodes): bool */ public function patternFor(string $countryCode): ?string { - $countryCode = strtoupper($countryCode); - if (array_key_exists($countryCode, self::ALIASES)) { $countryCode = self::ALIASES[$countryCode]; } @@ -98,8 +96,6 @@ public function patternFor(string $countryCode): ?string */ public function supports(string $countryCode): bool { - $countryCode = strtoupper($countryCode); - return array_key_exists($countryCode, $this->patterns) || array_key_exists($countryCode, self::ALIASES); } diff --git a/src/Rules/PostalCode.php b/src/Rules/PostalCode.php index 559748b..a983747 100644 --- a/src/Rules/PostalCode.php +++ b/src/Rules/PostalCode.php @@ -4,99 +4,109 @@ namespace Axlon\PostalCodeValidation\Rules; -final class PostalCode -{ - /** - * Whether or not this rule is dependant. - * - * @var bool - */ - protected $dependent; - - /** - * The rule parameters. - * - * @var string[] - */ - protected $parameters; +use Axlon\PostalCodeValidation\PostalCodeValidator; +use Closure; +use Illuminate\Contracts\Validation\DataAwareRule; +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\App; +use InvalidArgumentException; +final class PostalCode implements ValidationRule, DataAwareRule +{ /** - * Create a new postal code validation rule. + * Create a new validation rule instance. * - * @param array $parameters - * @param bool $dependant + * @param array $countryCodes + * @param array $data * @return void */ - public function __construct(array $parameters, bool $dependant) - { - $this->dependent = $dependant; - $this->parameters = $parameters; + private function __construct( + private array $countryCodes, + private array $data = [], + ) { } /** - * Convert the rule to a validation string. + * Determine if the given value is a country code. * - * @return string + * @param mixed $value + * @return bool + * @phpstan-assert-if-true =non-empty-string $value */ - public function __toString(): string + private static function isCountryCode(mixed $value): bool { - return 'postal_code' . ($this->dependent ? '_with:' : ':') . implode(',', $this->parameters); + return is_string($value) && preg_match('/^[A-Z]{2}$/', $value) === 1; } /** - * Get a postal_code_with constraint builder instance. + * Create a new validation rule instance. * - * @param string $country - * @return static + * @param array|string ...$parameters + * @return self */ - public static function for(string $country): self + public static function of(array|string ...$parameters): self { - return static::forCountry($country); - } + /** @var list $parameters */ + $parameters = Arr::flatten($parameters, depth: 1); - /** - * Create a new postal code validation rule for given countries. - * - * @param string ...$parameters - * @return static - */ - public static function forCountry(string ...$parameters): self - { - return new static($parameters, false); - } + if ($parameters === []) { + throw new InvalidArgumentException('Postal code validation requires at least 1 parameter'); + } - /** - * Create a new postal code validation rule for given inputs. - * - * @param string ...$parameters - * @return static - */ - public static function forInput(string ...$parameters): self - { - return new static($parameters, true); + return new self($parameters); } /** - * Add additional validation parameters to the rule. + * Set the data under validation. * - * @param string ...$parameters + * @param array $data * @return $this */ - public function or(string ...$parameters): self + public function setData(array $data): self { - $this->parameters = array_merge($this->parameters, $parameters); + $this->data = $data; return $this; } /** - * Get a postal_code_with constraint builder instance. + * Run the validation rule. * - * @param string $field - * @return static + * @param string $attribute + * @param mixed $value + * @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail + * @return void */ - public static function with(string $field): self + public function validate(string $attribute, mixed $value, Closure $fail): void { - return static::forInput($field); + if (is_null($value)) { + return; + } + + if (!is_string($value)) { + $fail('validation.string')->translate(); + } else { + $value = mb_strtoupper($value); + + /** @var \Axlon\PostalCodeValidation\PostalCodeValidator $validator */ + $validator = App::make(PostalCodeValidator::class); + + foreach ($this->countryCodes as $countryCode) { + if (!self::isCountryCode($countryCode)) { + $countryCode = Arr::get($this->data, $countryCode); + + if (!self::isCountryCode($countryCode)) { + continue; + } + } + + if ($validator->passes($countryCode, $value)) { + return; + } + } + + $fail('validation.postal_code')->translate(); + } } } diff --git a/src/Rules/PostalCodeExtension.php b/src/Rules/PostalCodeExtension.php new file mode 100644 index 0000000..14139e2 --- /dev/null +++ b/src/Rules/PostalCodeExtension.php @@ -0,0 +1,36 @@ + $parameters + * @param \Illuminate\Validation\Validator $validator + * @return bool + */ + public static function make( + string $attribute, + mixed $value, + array $parameters, + Validator $validator, + ): bool { + $rule = InvokableValidationRule::make(PostalCode::of($parameters)); + $rule->setData($validator->getData()); + $rule->setValidator($validator); + + return $rule->passes($attribute, $value); + } +} diff --git a/src/ValidationServiceProvider.php b/src/ValidationServiceProvider.php index 6be0037..407c4bd 100644 --- a/src/ValidationServiceProvider.php +++ b/src/ValidationServiceProvider.php @@ -4,8 +4,9 @@ namespace Axlon\PostalCodeValidation; -use Illuminate\Contracts\Validation\Factory; +use Axlon\PostalCodeValidation\Rules\PostalCodeExtension; use Illuminate\Support\ServiceProvider; +use Illuminate\Validation\Factory; final class ValidationServiceProvider extends ServiceProvider { @@ -33,26 +34,11 @@ public function register(): void /** * Register the postal code validation rules with the validator. * - * @param \Illuminate\Contracts\Validation\Factory $validator + * @param \Illuminate\Validation\Factory $validator * @return void */ private function registerRules(Factory $validator): void { - $validator->extend('postal_code', 'Axlon\PostalCodeValidation\Extensions\PostalCode@validate'); - - if (method_exists($validator, 'extendDependent')) { - $validator->extendDependent( - 'postal_code_for', - 'Axlon\PostalCodeValidation\Extensions\PostalCodeFor@validate', - ); - - $validator->extendDependent( - 'postal_code_with', - 'Axlon\PostalCodeValidation\Extensions\PostalCodeFor@validate', - ); - } else { - $validator->extend('postal_code_for', 'Axlon\PostalCodeValidation\Extensions\PostalCodeFor@validate'); - $validator->extend('postal_code_with', 'Axlon\PostalCodeValidation\Extensions\PostalCodeFor@validate'); - } + $validator->extendDependent('postal_code', PostalCodeExtension::make(...)); } } diff --git a/tests/Integration/PostalCodeForTest.php b/tests/Integration/PostalCodeForTest.php deleted file mode 100644 index 2346423..0000000 --- a/tests/Integration/PostalCodeForTest.php +++ /dev/null @@ -1,121 +0,0 @@ -app->make('validator')->make( - ['postal_code' => '1234 AB', 'country' => 'not-a-country'], - ['postal_code' => 'postal_code_for:country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_for', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code_for' rule fails invalid input. - * - * @return void - */ - public function testValidationFailsInvalidPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => 'not-a-postal-code', 'country' => 'NL'], - ['postal_code' => 'postal_code_for:country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_for', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code' rule fails null input. - * - * @return void - * @link https://github.com/axlon/laravel-postal-code-validation/issues/23 - */ - public function testValidationFailsNullPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => null, 'country' => 'DE'], - ['postal_code' => 'postal_code_for:country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_for', $validator->errors()->all()); - } - - public function testValidationPassesIfAllFieldsAreMissing(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code_for:country'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if the 'postal_code_for' rule ignores references that aren't present. - * - * @return void - */ - public function testValidationIgnoresMissingFields(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB', 'empty' => '', 'null' => null, 'country' => 'NL'], - ['postal_code' => 'postal_code_for:empty,missing,null,country'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if the 'postal_code_for' rule passes valid input. - * - * @return void - */ - public function testValidationPassesValidPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB', 'country' => 'NL'], - ['postal_code' => 'postal_code_for:country'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if an exception is thrown when calling the 'postal_code' rule without arguments. - * - * @return void - */ - public function testValidationThrowsWithoutParameters(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code_for'], - ); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Validation rule postal_code_with requires at least 1 parameter.'); - - $validator->validate(); - } -} diff --git a/tests/Integration/PostalCodeTest.php b/tests/Integration/PostalCodeTest.php deleted file mode 100644 index 1cac4a0..0000000 --- a/tests/Integration/PostalCodeTest.php +++ /dev/null @@ -1,94 +0,0 @@ -app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code:not-a-country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code' rule fails invalid input. - * - * @return void - */ - public function testValidationFailsInvalidPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => 'not-a-postal-code'], - ['postal_code' => 'postal_code:NL'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code' rule fails null input. - * - * @return void - * @link https://github.com/axlon/laravel-postal-code-validation/issues/23 - */ - public function testValidationFailsNullPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => null], - ['postal_code' => 'postal_code:DE'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code' rule passes valid input. - * - * @return void - */ - public function testValidationPassesValidPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code:NL'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if an exception is thrown when calling the 'postal_code' rule without arguments. - * - * @return void - */ - public function testValidationThrowsWithoutParameters(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code'], - ); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Validation rule postal_code requires at least 1 parameter.'); - - $validator->validate(); - } -} diff --git a/tests/Integration/PostalCodeWithTest.php b/tests/Integration/PostalCodeWithTest.php deleted file mode 100644 index cef0550..0000000 --- a/tests/Integration/PostalCodeWithTest.php +++ /dev/null @@ -1,164 +0,0 @@ -app->make('validator')->make( - ['postal_code' => '1234 AB', 'country' => 'not-a-country'], - ['postal_code' => 'postal_code_with:country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_with', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code_with' rule fails invalid input. - * - * @return void - */ - public function testValidationFailsInvalidPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => 'not-a-postal-code', 'country' => 'NL'], - ['postal_code' => 'postal_code_with:country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_with', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code_with' rule fails invalid input. - * - * @return void - */ - public function testValidationFailsInvalidPostalCodeInArray(): void - { - $validator = $this->app->make('validator')->make( - ['postal_codes' => ['not-a-postal-code'], 'countries' => ['NL']], - ['postal_codes.*' => 'postal_code_with:countries.*'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_with', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code' rule fails null input. - * - * @return void - * @link https://github.com/axlon/laravel-postal-code-validation/issues/23 - */ - public function testValidationFailsNullPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => null, 'country' => 'DE'], - ['postal_code' => 'postal_code_with:country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_with', $validator->errors()->all()); - } - - public function testValidationPassesIfAllFieldsAreMissing(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code_with:country'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if the 'postal_code_with' rule ignores references that aren't present. - * - * @return void - */ - public function testValidationIgnoresMissingFields(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB', 'empty' => '', 'null' => null, 'country' => 'NL'], - ['postal_code' => 'postal_code_with:empty,missing,null,country'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - public function testValidationIgnoresMissingFieldsFailing(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB', 'empty' => '', 'null' => null, 'country' => 'BE'], - ['postal_code' => 'postal_code_with:empty,missing,null,country'], - ); - - self::assertFalse($validator->passes()); - self::assertContains('validation.postal_code_with', $validator->errors()->all()); - } - - /** - * Test if the 'postal_code_with' rule passes valid input. - * - * @return void - */ - public function testValidationPassesValidPostalCode(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB', 'country' => 'NL'], - ['postal_code' => 'postal_code_with:country'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if the 'postal_code_with' rule passes valid input. - * - * @return void - */ - public function testValidationPassesValidPostalCodeInArray(): void - { - $validator = $this->app->make('validator')->make( - ['postal_codes' => ['1234 AB'], 'countries' => ['NL']], - ['postal_codes.*' => 'postal_code_with:countries.*'], - ); - - self::assertTrue($validator->passes()); - self::assertEmpty($validator->errors()->all()); - } - - /** - * Test if an exception is thrown when calling the 'postal_code' rule without arguments. - * - * @return void - */ - public function testValidationThrowsWithoutParameters(): void - { - $validator = $this->app->make('validator')->make( - ['postal_code' => '1234 AB'], - ['postal_code' => 'postal_code_with'], - ); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Validation rule postal_code_with requires at least 1 parameter.'); - - $validator->validate(); - } -} diff --git a/tests/Rules/PostalCodeRuleExtensionTest.php b/tests/Rules/PostalCodeRuleExtensionTest.php new file mode 100644 index 0000000..ba7fea2 --- /dev/null +++ b/tests/Rules/PostalCodeRuleExtensionTest.php @@ -0,0 +1,190 @@ + $data */ + $data = require __DIR__ . '/../../resources/examples.php'; + + yield from Collection::make($data)->map(static function (string $example, string $countryCode) { + return [$countryCode, $example]; + }); + } + + public function testItFailsWhenCountryCodeFieldIsInvalid(): void + { + $data = [ + 'country' => 'nL', + 'value' => '1234 AB', + ]; + + $rules = [ + 'value' => 'postal_code:country', + ]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenCountryCodeFieldIsMissing(): void + { + $data = ['value' => '1234 AB']; + $rules = ['value' => 'postal_code:country']; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenCountryCodeFieldIsNull(): void + { + $data = [ + 'country' => null, + 'value' => '1234 AB', + ]; + + $rules = [ + 'value' => 'postal_code:country', + ]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenCountryCodeIsInvalid(): void + { + $data = ['value' => '1234 AB']; + $rules = ['value' => 'postal_code:nL']; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenValueDoesNotMatchAnyCountryCode(): void + { + $data = ['value' => '95014']; + $rules = ['value' => 'postal_code:NL,BE']; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenValueDoesNotMatchAnyCountryCodeField(): void + { + $data = [ + 'country_1' => 'NL', + 'country_2' => 'BE', + 'value' => '95014', + ]; + + $rules = ['value' => 'postal_code:country_1,country_2']; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItPassesWhenValueIsMissing(): void + { + $data = []; + $rules = ['value' => 'postal_code:NL']; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueIsNull(): void + { + $data = ['value' => null]; + $rules = ['value' => 'postal_code:NL']; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesAnyCountryCode(): void + { + $data = ['value' => '4000']; + $rules = ['value' => 'postal_code:NL,BE']; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesAnyCountryCodeField(): void + { + $data = [ + 'country_1' => 'NL', + 'country_2' => 'BE', + 'value' => '4000', + ]; + + $rules = [ + 'value' => 'postal_code:country_1,country_2', + ]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + #[DataProvider('provideExamples')] + public function testItPassesWhenValueMatchesCountryCode(string $countryCode, string $example): void + { + $data = ['value' => $example]; + $rules = ['value' => "postal_code:$countryCode"]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesCountryCodeField(): void + { + $data = [ + 'country' => 'NL', + 'value' => '1234 AB', + ]; + + $rules = [ + 'value' => 'postal_code:NL', + ]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItThrowsWithoutParameters(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Postal code validation requires at least 1 parameter'); + + Validator::validate(['value' => '1234 AB'], ['value' => 'postal_code']); + } +} diff --git a/tests/Rules/PostalCodeRuleTest.php b/tests/Rules/PostalCodeRuleTest.php new file mode 100644 index 0000000..52ab9d3 --- /dev/null +++ b/tests/Rules/PostalCodeRuleTest.php @@ -0,0 +1,208 @@ + $data */ + $data = require __DIR__ . '/../../resources/examples.php'; + + yield from Collection::make($data)->map(static function (string $example, string $countryCode) { + return [$countryCode, $example]; + }); + } + + public function testItFailsWhenCountryCodeFieldIsInvalid(): void + { + $data = [ + 'country' => 'nL', + 'value' => '1234 AB', + ]; + + $rules = [ + 'value' => PostalCode::of('country'), + ]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenCountryCodeFieldIsMissing(): void + { + $data = ['value' => '1234 AB']; + $rules = ['value' => PostalCode::of('country')]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenCountryCodeFieldIsNull(): void + { + $data = [ + 'country' => null, + 'value' => '1234 AB', + ]; + + $rules = [ + 'value' => PostalCode::of('country'), + ]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenCountryCodeIsInvalid(): void + { + $data = ['value' => '1234 AB']; + $rules = ['value' => PostalCode::of('nL')]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenValueDoesNotMatchAnyCountryCode(): void + { + $data = ['value' => '95014']; + $rules = ['value' => PostalCode::of('NL', 'BE')]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItFailsWhenValueDoesNotMatchAnyCountryCodeField(): void + { + $data = [ + 'country_1' => 'NL', + 'country_2' => 'BE', + 'value' => '95014', + ]; + + $rules = ['value' => PostalCode::of('country_1', 'country_2')]; + + $validator = Validator::make($data, $rules); + + self::assertFalse($validator->passes()); + self::assertContains('validation.postal_code', $validator->errors()->all()); + } + + public function testItPassesWhenValueIsMissing(): void + { + $data = []; + $rules = ['value' => PostalCode::of('NL')]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueIsNull(): void + { + $data = ['value' => null]; + $rules = ['value' => PostalCode::of('NL')]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesAnyCountryCode(): void + { + $data = ['value' => '4000']; + $rules = ['value' => PostalCode::of('NL', 'BE')]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesAnyCountryCodeField(): void + { + $data = [ + 'country_1' => 'NL', + 'country_2' => 'BE', + 'value' => '4000', + ]; + + $rules = [ + 'value' => PostalCode::of('country_1', 'country_2'), + ]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + #[DataProvider('provideExamples')] + public function testItPassesWhenValueMatchesCountryCode(string $countryCode, string $example): void + { + $data = ['value' => $example]; + $rules = ['value' => PostalCode::of($countryCode)]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesCountryCodeField(): void + { + $data = [ + 'country' => 'NL', + 'value' => '1234 AB', + ]; + + $rules = [ + 'value' => PostalCode::of('NL'), + ]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + } + + public function testItPassesWhenValueMatchesCountryCodeInArray(): void + { + $data = [ + 'values' => ['1234 AB', '4000'], + 'countries' => ['NL', 'BE'], + ]; + + $rules = [ + 'values.*' => 'postal_code:countries.*', + ]; + + $validator = Validator::make($data, $rules); + + self::assertTrue($validator->passes()); + self::assertEmpty($validator->errors()->all()); + } + + public function testItThrowsWithoutParameters(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Postal code validation requires at least 1 parameter'); + + PostalCode::of(); + } +} diff --git a/tests/Unit/PostalCodeRuleTest.php b/tests/Unit/PostalCodeRuleTest.php deleted file mode 100644 index 07ca846..0000000 --- a/tests/Unit/PostalCodeRuleTest.php +++ /dev/null @@ -1,41 +0,0 @@ -or('baz')); - - self::assertEquals('postal_code_with:foo', (string) PostalCode::with('foo')); - self::assertEquals('postal_code_with:foo,bar,baz', (string) PostalCode::with('foo')->or('bar')->or('baz')); - } - - /** - * Test the creation of explicit postal code rules. - * - * @return void - */ - public function testExplicitRuleCreation(): void - { - self::assertEquals('postal_code:', (string) PostalCode::forCountry()); - self::assertEquals('postal_code:foo', (string) PostalCode::forCountry('foo')); - self::assertEquals('postal_code:foo,bar,baz', (string) PostalCode::forCountry('foo', 'bar')->or('baz')); - - self::assertEquals('postal_code:foo', (string) PostalCode::for('foo')); - self::assertEquals('postal_code:foo,bar,baz', (string) PostalCode::for('foo')->or('bar')->or('baz')); - } -} diff --git a/tests/Unit/PostalCodeValidatorTest.php b/tests/Unit/PostalCodeValidatorTest.php index 0581b96..2ce8715 100644 --- a/tests/Unit/PostalCodeValidatorTest.php +++ b/tests/Unit/PostalCodeValidatorTest.php @@ -74,18 +74,6 @@ public function testGreatBritainInwardCodeMaxLength(): void self::assertFalse($this->validator->passes('GB', 'NN1 5LLL')); } - /** - * Test whether lower case country codes can be used. - * - * @return void - */ - public function testLowerCaseCountryCode(): void - { - self::assertTrue($this->validator->supports('nl')); - self::assertNotNull($this->validator->patternFor('nl')); - self::assertTrue($this->validator->passes('nl', '1234 AB')); - } - /** * Test whether null patterns match any value. *