-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the GTIN validator (standalone) and Laravel Validation rule cla…
…sses
- Loading branch information
1 parent
e8d0721
commit 55de04e
Showing
5 changed files
with
265 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Illuminate\Translation\ArrayLoader; | ||
use Illuminate\Translation\Translator; | ||
use Illuminate\Validation\Validator; | ||
use PHPUnit\Framework\TestCase; | ||
use Vanilo\Support\Validation\Rules\MustBeAValidGtin; | ||
|
||
class GtinValidationRuleTest extends TestCase | ||
{ | ||
private $translator; | ||
|
||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
|
||
$this->translator = new Translator(new ArrayLoader(), 'en_US'); | ||
app()->instance('translator', $this->translator); | ||
} | ||
|
||
/** @test */ | ||
public function it_accepts_a_valid_gtin() | ||
{ | ||
$validator = new Validator( | ||
$this->translator, | ||
['gtin' => '1300000000000'], | ||
['gtin' => [new MustBeAValidGtin()]] | ||
); | ||
|
||
$this->assertTrue($validator->passes()); | ||
} | ||
|
||
/** @test */ | ||
public function it_rejects_an_invalid_gtin() | ||
{ | ||
$validator = new Validator( | ||
$this->translator, | ||
['gtin' => '1300000000001'], | ||
['gtin' => [new MustBeAValidGtin()]] | ||
); | ||
|
||
$this->assertFalse($validator->passes()); | ||
} | ||
|
||
/** @test */ | ||
public function it_rejects_an_array_as_field_value() | ||
{ | ||
$validator = new Validator( | ||
$this->translator, | ||
['gtin' => []], | ||
['gtin' => [new MustBeAValidGtin()]] | ||
); | ||
|
||
$this->assertFalse($validator->passes()); | ||
} | ||
|
||
/** @test */ | ||
public function it_returns_the_correct_error_message() | ||
{ | ||
$validator = new Validator( | ||
$this->translator, | ||
['gtin' => 'invalid-gtin'], | ||
['gtin' => [new MustBeAValidGtin()]] | ||
); | ||
|
||
$this->assertEquals( | ||
'The gtin must be a valid Global Trade Item Number (GTIN) [8, 12, 13 or 14 digits with a valid check digit]', | ||
$validator->errors()->first('gtin') | ||
); | ||
} | ||
|
||
/** @test */ | ||
public function the_validation_message_can_be_overridden() | ||
{ | ||
$validator = new Validator( | ||
$this->translator, | ||
['gtin' => 'invalid-gtin'], | ||
['gtin' => [new MustBeAValidGtin()]], | ||
['gtin' => 'Custom GTIN validation error message.'] | ||
); | ||
|
||
$this->assertEquals( | ||
'Custom GTIN validation error message.', | ||
$validator->errors()->first('gtin') | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Vanilo\Support\Validation\GtinValidator; | ||
|
||
class GtinValidatorTest extends TestCase | ||
{ | ||
/** @test */ | ||
public function a_valid_gtin8_should_pass() | ||
{ | ||
$this->assertTrue(GtinValidator::isValid('80000006')); | ||
} | ||
|
||
/** @test */ | ||
public function an_invalid_gtin8_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('80000000')); | ||
} | ||
|
||
/** @test */ | ||
public function a_valid_gtin12_should_pass() | ||
{ | ||
$this->assertTrue(GtinValidator::isValid('120000000005')); | ||
} | ||
|
||
/** @test */ | ||
public function an_invalid_gtin12_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('120000000000')); | ||
} | ||
|
||
/** @test */ | ||
public function a_valid_gtin13_should_pass() | ||
{ | ||
$this->assertTrue(GtinValidator::isValid('1300000000000')); | ||
} | ||
|
||
/** @test */ | ||
public function an_invalid_gtin13_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('1300000000001')); | ||
} | ||
|
||
/** @test */ | ||
public function a_valid_gtin14_should_pass() | ||
{ | ||
$this->assertTrue(GtinValidator::isValid('14000000000003')); | ||
} | ||
|
||
/** @test */ | ||
public function an_invalid_gtin14_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('14000000000000')); | ||
} | ||
|
||
/** @test */ | ||
public function a_valid_gtin_integer_value_should_pass() | ||
{ | ||
$this->assertTrue(GtinValidator::isValid(80000006)); | ||
} | ||
|
||
/** @test */ | ||
public function a_too_short_value_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('7000003')); | ||
} | ||
|
||
/** @test */ | ||
public function a_too_long_value_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('150000000000004')); | ||
} | ||
|
||
/** @test */ | ||
public function a_value_with_a_length_of_nine_digits_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('900000001')); | ||
} | ||
|
||
/** @test */ | ||
public function a_value_with_a_length_of_ten_digits_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('1000000007')); | ||
} | ||
|
||
/** @test */ | ||
public function a_value_with_a_length_of_eleven_digits_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('11000000006')); | ||
} | ||
|
||
/** @test */ | ||
public function zeros_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('0000000000000')); | ||
} | ||
|
||
/** @test */ | ||
public function a_non_numeric_string_should_not_pass() | ||
{ | ||
$this->assertFalse(GtinValidator::isValid('string')); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Vanilo\Support\Validation; | ||
|
||
class GtinValidator | ||
{ | ||
protected static array $cache = []; | ||
|
||
public static function isValid(string|int $gtin): bool | ||
{ | ||
$gtin = (string) $gtin; | ||
if (! is_numeric($gtin)) { | ||
return false; | ||
} | ||
|
||
if (isset(static::$cache[$gtin])) { | ||
return static::$cache[$gtin]; | ||
} | ||
|
||
if (! preg_match('/^\d{8}(?:\d{4,6})?$/', $gtin)) { | ||
return false; | ||
} | ||
|
||
return static::$cache[$gtin] = static::isCheckSumCorrect($gtin); | ||
} | ||
|
||
protected static function isCheckSumCorrect(string $value): bool | ||
{ | ||
return substr($value, 0, -1).collect(str_split($value)) | ||
->slice(0, -1) | ||
->pipe(function ($collection) { | ||
return $collection->sum() === 0 ? collect(1) : $collection; | ||
}) | ||
->reverse() | ||
->values() | ||
->map(function ($digit, $key) { | ||
return $key % 2 === 0 ? $digit * 3 : $digit; | ||
}) | ||
->pipe(function ($collection) { | ||
return ceil($collection->sum() / 10) * 10 - $collection->sum(); | ||
}) === $value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Vanilo\Support\Validation\Rules; | ||
|
||
use Closure; | ||
use Illuminate\Contracts\Validation\ValidationRule; | ||
use Vanilo\Support\Validation\GtinValidator; | ||
|
||
class MustBeAValidGtin implements ValidationRule | ||
{ | ||
public function validate(string $attribute, mixed $value, Closure $fail): void | ||
{ | ||
if (!is_string($value) && !is_int($value)) { | ||
$fail(__('The :attribute must be a numeric string')); | ||
|
||
return; | ||
} | ||
|
||
if (!GtinValidator::isValid($value)) { | ||
$fail(__('The :attribute must be a valid Global Trade Item Number (GTIN) [8, 12, 13 or 14 digits with a valid check digit]')); | ||
} | ||
} | ||
} |