├── .github
└── workflows
│ ├── coding-standards.yml
│ ├── composer-require-checker.yml
│ ├── kahlan-tests.yml
│ └── psalm.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── crc-config.json
├── phpcs.xml.dist
├── psalm.xml
├── spec
├── Basic
│ ├── ErrorsSpec.php
│ ├── HasKeySpec.php
│ ├── HasNotKeySpec.php
│ ├── InArraySpec.php
│ ├── IsArraySpec.php
│ ├── IsAsAssertedSpec.php
│ ├── IsBoolSpec.php
│ ├── IsCallableSpec.php
│ ├── IsFloatSpec.php
│ ├── IsGreaterThanSpec.php
│ ├── IsInstanceOfSpec.php
│ ├── IsIntegerSpec.php
│ ├── IsIterableSpec.php
│ ├── IsLessThanSpec.php
│ ├── IsNotNullSpec.php
│ ├── IsNullSpec.php
│ ├── IsNumericSpec.php
│ ├── IsObjectSpec.php
│ ├── IsResourceSpec.php
│ ├── IsStringSpec.php
│ ├── NonEmptySpec.php
│ ├── RegexSpec.php
│ ├── UserSpec.php
│ └── ValidSpec.php
├── Combinator
│ ├── AllSpec.php
│ ├── AnyElementSpec.php
│ ├── AnySpec.php
│ ├── ApplySpec.php
│ ├── AssociativeSpec.php
│ ├── BindSpec.php
│ ├── EveryElementSpec.php
│ ├── FocusSpec.php
│ ├── MapErrorsSpec.php
│ ├── MapSpec.php
│ ├── SequenceSpec.php
│ └── TranslateErrorsSpec.php
├── Example
│ └── ApplicativeMonadicSpec.php
├── Result
│ ├── ValidationResultSpec.php
│ └── functionsSpec.php
├── Translator
│ ├── Combinator
│ │ └── CoalesceSpec.php
│ ├── ConstantTranslatorSpec.php
│ ├── IdentityTranslatorSpec.php
│ └── KeyValueTranslatorSpec.php
└── functionsSpec.php
└── src
├── Basic
├── Compare.php
├── ComposingAssertion.php
├── Errors.php
├── HasKey.php
├── HasNotKey.php
├── InArray.php
├── IsArray.php
├── IsAsAsserted.php
├── IsBool.php
├── IsCallable.php
├── IsFloat.php
├── IsGreaterThan.php
├── IsInstanceOf.php
├── IsInteger.php
├── IsIterable.php
├── IsLessThan.php
├── IsNotNull.php
├── IsNull.php
├── IsNumeric.php
├── IsObject.php
├── IsResource.php
├── IsString.php
├── NonEmpty.php
├── Regex.php
└── Valid.php
├── Combinator
├── All.php
├── Any.php
├── AnyElement.php
├── Apply.php
├── Associative.php
├── Bind.php
├── EveryElement.php
├── Focus.php
├── Map.php
├── MapErrors.php
├── Sequence.php
└── TranslateErrors.php
├── Equality.php
├── Result
├── ValidationResult.php
└── functions.php
├── Translator
├── Combinator
│ └── Coalesce.php
├── ConstantTranslator.php
├── IdentityTranslator.php
├── KeyValueTranslator.php
└── Translator.php
├── Validation.php
└── functions.php
/.github/workflows/coding-standards.yml:
--------------------------------------------------------------------------------
1 | name: "Check Coding Standards"
2 |
3 | on:
4 | pull_request:
5 | push:
6 |
7 | jobs:
8 | coding-standards:
9 | name: "Check Coding Standards"
10 |
11 | runs-on: ${{ matrix.operating-system }}
12 |
13 | strategy:
14 | matrix:
15 | dependencies:
16 | - "locked"
17 | php-version:
18 | - "7.4"
19 | - "8.0"
20 | operating-system:
21 | - "ubuntu-latest"
22 |
23 | steps:
24 | - name: "Checkout"
25 | uses: "actions/checkout@v2"
26 |
27 | - name: "Install PHP"
28 | uses: "shivammathur/setup-php@v2"
29 | with:
30 | coverage: "pcov"
31 | php-version: "${{ matrix.php-version }}"
32 | ini-values: memory_limit=-1
33 | tools: composer:v2, cs2pr
34 |
35 | - name: "Cache dependencies"
36 | uses: "actions/cache@v2"
37 | with:
38 | path: |
39 | ~/.composer/cache
40 | vendor
41 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
42 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
43 |
44 | - name: "Install lowest dependencies"
45 | if: ${{ matrix.dependencies == 'lowest' }}
46 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
47 |
48 | - name: "Install highest dependencies"
49 | if: ${{ matrix.dependencies == 'highest' }}
50 | run: "composer update --no-interaction --no-progress --no-suggest"
51 |
52 | - name: "Install locked dependencies"
53 | if: ${{ matrix.dependencies == 'locked' }}
54 | run: "composer install --no-interaction --no-progress --no-suggest"
55 |
56 | - name: "Coding Standard"
57 | run: "vendor/bin/phpcs -q --report=checkstyle | cs2pr"
58 |
--------------------------------------------------------------------------------
/.github/workflows/composer-require-checker.yml:
--------------------------------------------------------------------------------
1 | name: "Check soft dependencies"
2 |
3 | on:
4 | pull_request:
5 | push:
6 |
7 | jobs:
8 | coding-standards:
9 | name: "Check Coding Standards"
10 |
11 | runs-on: ${{ matrix.operating-system }}
12 |
13 | strategy:
14 | matrix:
15 | dependencies:
16 | - "locked"
17 | php-version:
18 | - "7.4"
19 | - "8.0"
20 | operating-system:
21 | - "ubuntu-latest"
22 |
23 | steps:
24 | - name: "Checkout"
25 | uses: "actions/checkout@v2"
26 |
27 | - name: "Install PHP"
28 | uses: "shivammathur/setup-php@v2"
29 | with:
30 | coverage: "pcov"
31 | php-version: "${{ matrix.php-version }}"
32 | ini-values: memory_limit=-1
33 | tools: composer:v2, cs2pr
34 |
35 | - name: "Cache dependencies"
36 | uses: "actions/cache@v2"
37 | with:
38 | path: |
39 | ~/.composer/cache
40 | vendor
41 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
42 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
43 |
44 | - name: "Install lowest dependencies"
45 | if: ${{ matrix.dependencies == 'lowest' }}
46 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
47 |
48 | - name: "Install highest dependencies"
49 | if: ${{ matrix.dependencies == 'highest' }}
50 | run: "composer update --no-interaction --no-progress --no-suggest"
51 |
52 | - name: "Install locked dependencies"
53 | if: ${{ matrix.dependencies == 'locked' }}
54 | run: "composer install --no-interaction --no-progress --no-suggest"
55 |
56 | - name: "Coding Standard"
57 | run: "vendor/bin/composer-require-checker check --config-file crc-config.json"
58 |
--------------------------------------------------------------------------------
/.github/workflows/kahlan-tests.yml:
--------------------------------------------------------------------------------
1 | name: "Kahlan tests"
2 |
3 | on:
4 | pull_request:
5 | push:
6 |
7 | jobs:
8 | kahlan:
9 | name: "Kahlan tests"
10 |
11 | runs-on: ${{ matrix.operating-system }}
12 |
13 | strategy:
14 | matrix:
15 | dependencies:
16 | - "lowest"
17 | - "highest"
18 | - "locked"
19 | php-version:
20 | - "7.4"
21 | - "8.0"
22 | operating-system:
23 | - "ubuntu-latest"
24 |
25 | steps:
26 | - name: "Checkout"
27 | uses: "actions/checkout@v2"
28 | with:
29 | fetch-depth: 0
30 |
31 | - name: "Install PHP"
32 | uses: "shivammathur/setup-php@v2"
33 | with:
34 | coverage: "pcov"
35 | php-version: "${{ matrix.php-version }}"
36 | ini-values: memory_limit=-1
37 | tools: composer:v2, cs2pr
38 |
39 | - name: "Cache dependencies"
40 | uses: "actions/cache@v2"
41 | with:
42 | path: |
43 | ~/.composer/cache
44 | vendor
45 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
46 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
47 |
48 | - name: "Install lowest dependencies"
49 | if: ${{ matrix.dependencies == 'lowest' }}
50 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
51 |
52 | - name: "Install highest dependencies"
53 | if: ${{ matrix.dependencies == 'highest' }}
54 | run: "composer update --no-interaction --no-progress --no-suggest"
55 |
56 | - name: "Install locked dependencies"
57 | if: ${{ matrix.dependencies == 'locked' }}
58 | run: "composer install --no-interaction --no-progress --no-suggest"
59 |
60 | - name: "Tests"
61 | run: "vendor/bin/kahlan"
--------------------------------------------------------------------------------
/.github/workflows/psalm.yml:
--------------------------------------------------------------------------------
1 | name: "Static Analysis by Psalm"
2 |
3 | on:
4 | pull_request:
5 | push:
6 |
7 | jobs:
8 | static-analysis-psalm:
9 | name: "Static Analysis by Psalm"
10 |
11 | runs-on: ${{ matrix.operating-system }}
12 |
13 | strategy:
14 | matrix:
15 | dependencies:
16 | - "locked"
17 | php-version:
18 | - "7.4"
19 | - "8.0"
20 | operating-system:
21 | - "ubuntu-latest"
22 |
23 | steps:
24 | - name: "Checkout"
25 | uses: "actions/checkout@v2"
26 |
27 | - name: "Install PHP"
28 | uses: "shivammathur/setup-php@v2"
29 | with:
30 | coverage: "pcov"
31 | php-version: "${{ matrix.php-version }}"
32 | ini-values: memory_limit=-1
33 | tools: composer:v2, cs2pr
34 |
35 | - name: "Cache dependencies"
36 | uses: "actions/cache@v2"
37 | with:
38 | path: |
39 | ~/.composer/cache
40 | vendor
41 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
42 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}"
43 |
44 | - name: "Install lowest dependencies"
45 | if: ${{ matrix.dependencies == 'lowest' }}
46 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
47 |
48 | - name: "Install highest dependencies"
49 | if: ${{ matrix.dependencies == 'highest' }}
50 | run: "composer update --no-interaction --no-progress --no-suggest"
51 |
52 | - name: "Install locked dependencies"
53 | if: ${{ matrix.dependencies == 'locked' }}
54 | run: "composer install --no-interaction --no-progress --no-suggest"
55 |
56 | - name: "psalm"
57 | run: "vendor/bin/psalm --output-format=github --shepherd --stats"
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | composer.lock
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.3.1]
9 |
10 | - added `Associative` combinator
11 |
12 | ## [0.3.0]
13 |
14 | - added `Valid` validator
15 | - added `Errors` validator
16 | - added `IsGreaterThan` and `IsLessThan` validators
17 | - added `Apply` combinator
18 | - added `Bind` combinator
19 | - added `composer-require-check`, `phpcs`, `phpstan` and `psalm` to `CI`
20 | - use `MESSAGE` constant in all validators
21 | - use `phpstan` strict rules
22 | - use `psalm`
23 | - add `Equality` interface to check object equality
24 | - add `curry` and `uncurry` functions
25 | - add `lift`, `sdo` and `mdo` functions
26 |
27 | ## [0.2.3] - 2018-12-05
28 |
29 | - return received `$data` in `Focus` combinator
30 |
31 | ## [0.2.2] - 2018-12-03
32 |
33 | - return `$data` in valid result for `Any` combinator
34 |
35 | ## [0.2.1] - 2018-11-30
36 |
37 | - pass on `$context` in `Map` and `MapErrors` combinators
38 |
39 | ## [0.2.0] - 2018-11-22
40 |
41 | - set `All` constructor as private
42 | - set `Any` constructor as private
43 | - added `MapError` validator
44 | - added `HasNotKey` validator
45 | - added `InArray` validator
46 | - added `IsNumeric` validator
47 | - added `TranslateErrors` combinator
48 |
49 | ## [0.1.0]
50 |
51 | Basic validators and combinators
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Marco Perone
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marcosh/php-validation-dsl",
3 | "description": "A DSL for validating data in a functional fashion",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Marco Perone",
9 | "email": "pasafama@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^7.4|^8.0",
14 | "webmozart/assert": "^1.10"
15 | },
16 | "autoload": {
17 | "psr-4": {
18 | "Marcosh\\PhpValidationDSL\\": "src/"
19 | },
20 | "files": [
21 | "src/functions.php",
22 | "src/Result/functions.php"
23 | ]
24 | },
25 | "require-dev": {
26 | "ext-json": "*",
27 | "kahlan/kahlan": "^5.1",
28 | "squizlabs/php_codesniffer": "^3.6",
29 | "vimeo/psalm": "^4.10|dev-master",
30 | "maglnet/composer-require-checker": "^3.3"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/crc-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "symbol-whitelist": [
3 | "array",
4 | "bool",
5 | "callable",
6 | "false",
7 | "null",
8 | "self",
9 | "static",
10 | "string",
11 | "true",
12 | "uncurry"
13 | ],
14 | "php-core-extensions" : [
15 | "Core",
16 | "pcre",
17 | "Reflection",
18 | "standard"
19 | ],
20 | "scan-files" : []
21 | }
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | src
15 | spec
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/spec/Basic/ErrorsSpec.php:
--------------------------------------------------------------------------------
1 | validate('anything')->equals(ValidationResult::errors([Errors::MESSAGE])))->toBeTruthy();
16 | });
17 |
18 | it('returns a custom error result if a custom formatter is passed', function () {
19 | $isString = Errors::withFormatter(function ($data) {
20 | return [(string) $data];
21 | });
22 |
23 | expect($isString->validate(true)->equals(ValidationResult::errors(['1'])))->toBeTruthy();
24 | });
25 |
26 | it('returns a translated error result if a translator is passed', function () {
27 | $isString = Errors::withTranslator(KeyValueTranslator::withDictionary([
28 | Errors::MESSAGE => 'NOT VALID!'
29 | ]));
30 |
31 | expect($isString->validate(true)->equals(ValidationResult::errors(['NOT VALID!'])))->toBeTruthy();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/spec/Basic/HasKeySpec.php:
--------------------------------------------------------------------------------
1 | null];
18 |
19 | expect($hasKey->validate($data)->equals(ValidationResult::valid($data)))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the key is not present', function () use ($hasKey) {
23 | $data = [];
24 |
25 | expect($hasKey->validate($data)->equals(ValidationResult::errors([HasKey::MISSING_KEY])))->toBeTruthy();
26 | });
27 |
28 | it('returns a custom error result if the key is not present and a custom formatter is passed', function () {
29 | $hasKey = HasKey::withKeyAndFormatter('key', function ($key, $data) {
30 | return [$key . json_encode($data)];
31 | });
32 |
33 | $data = [];
34 |
35 | expect($hasKey->validate($data)->equals(ValidationResult::errors(['key' . json_encode([])])))->toBeTruthy();
36 | });
37 |
38 | it('returns a translated error message if the key is not present and a translator is passed', function () {
39 | $hasKey = HasKey::withKeyAndTranslator(
40 | 'key',
41 | KeyValueTranslator::withDictionary([
42 | HasKey::MISSING_KEY => 'MISSING KEY!'
43 | ])
44 | );
45 |
46 | $data = [];
47 |
48 | expect($hasKey->validate($data)->equals(ValidationResult::errors(['MISSING KEY!'])))->toBeTruthy();
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/spec/Basic/HasNotKeySpec.php:
--------------------------------------------------------------------------------
1 | validate($data)->equals(ValidationResult::valid($data)))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the key is present', function () use ($hasNotKey) {
23 | $data = ['key' => null];
24 |
25 | expect($hasNotKey->validate($data)->equals(ValidationResult::errors([HasNotKey::PRESENT_KEY])))->toBeTruthy();
26 | });
27 |
28 | it('returns a custom error result if the key is present and a custom formatter is passed', function () {
29 | $hasKey = HasNotKey::withKeyAndFormatter('key', function ($key, $data) {
30 | return [$key . json_encode($data)];
31 | });
32 |
33 | $data = ['key' => null];
34 |
35 | expect($hasKey->validate($data)->equals(ValidationResult::errors(['key' . json_encode($data)])))->toBeTruthy();
36 | });
37 |
38 | it('returns a translated error message if the key is present and a translator is passed', function () {
39 | $hasKey = HasNotKey::withKeyAndTranslator(
40 | 'key',
41 | KeyValueTranslator::withDictionary([
42 | HasNotKey::PRESENT_KEY => 'PRESENT KEY!'
43 | ])
44 | );
45 |
46 | $data = ['key' => null];
47 |
48 | expect($hasKey->validate($data)->equals(ValidationResult::errors(['PRESENT KEY!'])))->toBeTruthy();
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/spec/Basic/InArraySpec.php:
--------------------------------------------------------------------------------
1 | validate($data)->equals(ValidationResult::valid($data)))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the value is not present', function () use ($inArray) {
23 | $data = 'toni';
24 |
25 | expect($inArray->validate($data)->equals(ValidationResult::errors([InArray::NOT_IN_ARRAY])))->toBeTruthy();
26 | });
27 |
28 | it('returns a custom error result if the value is not present and a custom formatter is passed', function () {
29 | $hasKey = InArray::withValuesAndFormatter(['gigi', 'bepi'], function ($values, $data) {
30 | return [json_encode($values) . $data];
31 | });
32 |
33 | $data = 'toni';
34 |
35 | expect($hasKey->validate($data)->equals(ValidationResult::errors([json_encode(['gigi', 'bepi']) . 'toni'])))
36 | ->toBeTruthy();
37 | });
38 |
39 | it('returns a translated error message if the value is not present and a translator is passed', function () {
40 | $hasKey = InArray::withValuesAndTranslator(
41 | ['gigi', 'bepi'],
42 | KeyValueTranslator::withDictionary([
43 | InArray::NOT_IN_ARRAY => 'NOT IN ARRAY!'
44 | ])
45 | );
46 |
47 | $data = 'toni';
48 |
49 | expect($hasKey->validate($data)->equals(ValidationResult::errors(['NOT IN ARRAY!'])))->toBeTruthy();
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/spec/Basic/IsArraySpec.php:
--------------------------------------------------------------------------------
1 | validate([])->equals(ValidationResult::valid([])))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not an array', function () use ($isArray) {
19 | expect($isArray->validate(42)->equals(ValidationResult::errors([IsArray::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is not an array and a custom formatter is passed', function () {
23 | $isArray = IsArray::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isArray->validate(42)->equals(ValidationResult::errors(['42'])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error message if the argument is not an array and a translator is passed', function () {
31 | $isArray = IsArray::withTranslator(KeyValueTranslator::withDictionary([
32 | IsArray::MESSAGE => 'NO ARRAY HERE!'
33 | ]));
34 |
35 | expect($isArray->validate(42)->equals(ValidationResult::errors(['NO ARRAY HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/IsAsAssertedSpec.php:
--------------------------------------------------------------------------------
1 | validate(42)->equals(ValidationResult::valid(42)))->toBeTruthy();
18 | });
19 |
20 | it('returns an error result if the assertion is not satisfied', function () use ($isAsAsserted) {
21 | expect($isAsAsserted->validate('fortytwo')->equals(ValidationResult::errors([IsAsAsserted::NOT_AS_ASSERTED])))
22 | ->toBeTruthy();
23 | });
24 |
25 | it(
26 | 'returns a custom error result if the assertion is not satisfied and a custom formatter is passed',
27 | function () {
28 | $isAsAsserted = IsAsAsserted::withAssertionAndErrorFormatter(
29 | function ($data) {
30 | return $data === 42;
31 | },
32 | function ($data) {
33 | return ['not 42'];
34 | }
35 | );
36 |
37 | expect($isAsAsserted->validate('fortytwo')->equals(ValidationResult::errors(['not 42'])))->toBeTruthy();
38 | }
39 | );
40 |
41 | it(
42 | 'returns a translated error result if the assertion is not satisfied and a translator is passed',
43 | function () {
44 | $isAsAsserted = IsAsAsserted::withAssertionAndTranslator(
45 | function ($data) {
46 | return $data === 42;
47 | },
48 | KeyValueTranslator::withDictionary([
49 | IsAsAsserted::NOT_AS_ASSERTED => 'NOT AS ASSERTED!'
50 | ])
51 | );
52 |
53 | expect($isAsAsserted->validate('fortytwo')->equals(ValidationResult::errors(['NOT AS ASSERTED!'])))
54 | ->toBeTruthy();
55 | }
56 | );
57 | });
58 |
--------------------------------------------------------------------------------
/spec/Basic/IsBoolSpec.php:
--------------------------------------------------------------------------------
1 | validate(true)->equals(ValidationResult::valid(true)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not a boolean', function () use ($isBool) {
19 | expect($isBool->validate('true')->equals(ValidationResult::errors([IsBool::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the argument is not a boolean and a custom formatter is passed', function () {
23 | $isBool = IsBool::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isBool->validate('true')->equals(ValidationResult::errors(['true'])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error message if the argument is not a boolean and a translator is passed', function () {
31 | $isArray = IsBool::withTranslator(KeyValueTranslator::withDictionary([
32 | IsBool::MESSAGE => 'NO BOOL HERE!'
33 | ]));
34 |
35 | expect($isArray->validate(42)->equals(ValidationResult::errors(['NO BOOL HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/IsCallableSpec.php:
--------------------------------------------------------------------------------
1 | validate('sprintf')->equals(ValidationResult::valid('sprintf')))->toBeTruthy();
27 | });
28 |
29 | it('returns a valid result if the argument is an anonymous function', function () use ($isCallable) {
30 | $function = function () {
31 | };
32 |
33 | expect($isCallable->validate($function)->equals(ValidationResult::valid($function)))->toBeTruthy();
34 | });
35 |
36 | it('returns a valid result if the argument is a static method', function () use ($isCallable) {
37 | $staticMethod = [CallableFoo::class, 'bar'];
38 |
39 | expect($isCallable->validate($staticMethod)->equals(ValidationResult::valid($staticMethod)))->toBeTruthy();
40 | });
41 |
42 | it('returns a valid result if the argument is an instance method', function () use ($isCallable) {
43 | $instanceMethod = [new CallableFoo(), 'baz'];
44 |
45 | expect($isCallable->validate($instanceMethod)->equals(ValidationResult::valid($instanceMethod)))->toBeTruthy();
46 | });
47 |
48 | it('returns an error result if the argument is not a callable', function () use ($isCallable) {
49 | expect($isCallable->validate('true')->equals(ValidationResult::errors([IsCallable::MESSAGE])))
50 | ->toBeTruthy();
51 | });
52 |
53 | it('returns a custom error if the argument is not a callable and a custom formatter is passed', function () {
54 | $isCallable = IsCallable::withFormatter(function ($data) {
55 | return [(string) $data];
56 | });
57 |
58 | expect($isCallable->validate('true')->equals(ValidationResult::errors(['true'])))->toBeTruthy();
59 | });
60 |
61 | it('returns a translated error if the argument is not a callable and a translator is passed', function () {
62 | $isCallable = IsCallable::withTranslator(KeyValueTranslator::withDictionary([
63 | IsCallable::MESSAGE => 'NOT A CALLABLE!'
64 | ]));
65 |
66 | expect($isCallable->validate('true')->equals(ValidationResult::errors(['NOT A CALLABLE!'])))->toBeTruthy();
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/spec/Basic/IsFloatSpec.php:
--------------------------------------------------------------------------------
1 | validate(12.34)->equals(ValidationResult::valid(12.34)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not a float', function () use ($isFloat) {
19 | expect($isFloat->validate('gigi')->equals(ValidationResult::errors([IsFloat::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is not a float and a custom formatter is passed', function () {
23 | $isFloat = IsFloat::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isFloat->validate('gigi')->equals(ValidationResult::errors(['gigi'])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error result if the argument is not a float and a translator is passed', function () {
31 | $isFloat = IsFloat::withTranslator(KeyValueTranslator::withDictionary([
32 | IsFloat::MESSAGE => 'NO FLOAT HERE!'
33 | ]));
34 |
35 | expect($isFloat->validate('gigi')->equals(ValidationResult::errors(['NO FLOAT HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/IsGreaterThanSpec.php:
--------------------------------------------------------------------------------
1 | validate(87)->equals(ValidationResult::valid(87)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the value is equal to be bound', function () use ($isGreaterThan) {
19 | expect($isGreaterThan->validate(42)->equals(ValidationResult::errors([IsGreaterThan::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the value is less than be bound', function () use ($isGreaterThan) {
23 | expect($isGreaterThan->validate(23)->equals(ValidationResult::errors([IsGreaterThan::MESSAGE])))->toBeTruthy();
24 | });
25 |
26 | it(
27 | 'returns a custom error result if the argument is less than the bound and a custom formatter is passed',
28 | function () {
29 | $isGreaterThan = IsGreaterThan::withBoundAndFormatter(42, function ($bound, $data) {
30 | return [$bound . $data];
31 | });
32 |
33 | expect($isGreaterThan->validate(23)->equals(ValidationResult::errors(['4223'])))->toBeTruthy();
34 | }
35 | );
36 |
37 | it(
38 | 'returns a translated error result if the argument is less than the bound and a translator is passed',
39 | function () {
40 | $isGreaterThan = IsGreaterThan::withBoundAndTranslator(42, KeyValueTranslator::withDictionary([
41 | IsGreaterThan::MESSAGE => 'LESS THAN 42!'
42 | ]));
43 |
44 | expect($isGreaterThan->validate('23')->equals(ValidationResult::errors(['LESS THAN 42!'])))->toBeTruthy();
45 | }
46 | );
47 | });
48 |
--------------------------------------------------------------------------------
/spec/Basic/IsInstanceOfSpec.php:
--------------------------------------------------------------------------------
1 | validate($instance)->equals(ValidationResult::valid($instance)))->toBeTruthy();
24 | });
25 |
26 | it('returns an error result if the argument is not a string', function () use ($isInstanceOf) {
27 | expect(
28 | $isInstanceOf->validate(new \stdClass())->equals(ValidationResult::errors([IsInstanceOf::NOT_AN_INSTANCE]))
29 | )->toBeTruthy();
30 | });
31 |
32 | it('returns a custom error result if the argument is not a string and a custom formatter is passed', function () {
33 | $isInstanceOf = IsInstanceOf::withClassNameAndFormatter(
34 | InstanceFoo::class,
35 | function (string $className, $data) {
36 | return [$className . json_encode($data)];
37 | }
38 | );
39 |
40 | expect($isInstanceOf->validate(new \stdClass())->equals(ValidationResult::errors([InstanceFoo::class . '{}'])))
41 | ->toBeTruthy();
42 | });
43 |
44 | it('returns a translated error result if the argument is not a string and a translator is passed', function () {
45 | $isInstanceOf = IsInstanceOf::withClassNameAndTranslator(
46 | InstanceFoo::class,
47 | KeyValueTranslator::withDictionary([
48 | IsInstanceOf::NOT_AN_INSTANCE => 'NO INSTANCE HERE!'
49 | ])
50 | );
51 |
52 | expect($isInstanceOf->validate(new \stdClass())->equals(ValidationResult::errors(['NO INSTANCE HERE!'])))
53 | ->toBeTruthy();
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/spec/Basic/IsIntegerSpec.php:
--------------------------------------------------------------------------------
1 | validate(42)->equals(ValidationResult::valid(42)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not an integer', function () use ($isInteger) {
19 | expect($isInteger->validate('gigi')->equals(ValidationResult::errors([IsInteger::MESSAGE])))
20 | ->toBeTruthy();
21 | });
22 |
23 | it('returns a custom error result if the argument is not an int and a custom formatter is passed', function () {
24 | $isInteger = IsInteger::withFormatter(function ($data) {
25 | return [(string) $data];
26 | });
27 |
28 | expect($isInteger->validate('gigi')->equals(ValidationResult::errors(['gigi'])))->toBeTruthy();
29 | });
30 |
31 | it('returns a translated error result if the argument is not an int and a translator is passed', function () {
32 | $isInteger = IsInteger::withTranslator(KeyValueTranslator::withDictionary([
33 | IsInteger::MESSAGE => 'NO INTEGER HERE!'
34 | ]));
35 |
36 | expect($isInteger->validate('gigi')->equals(ValidationResult::errors(['NO INTEGER HERE!'])))->toBeTruthy();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/spec/Basic/IsIterableSpec.php:
--------------------------------------------------------------------------------
1 | validate([])->equals(ValidationResult::valid([])))->toBeTruthy();
16 | });
17 |
18 | it('returns a valid result if the argument is a Traversable', function () use ($isIterable) {
19 | $iterator = new \ArrayIterator();
20 |
21 | expect($isIterable->validate($iterator)->equals(ValidationResult::valid($iterator)))->toBeTruthy();
22 | });
23 |
24 | it('returns an error result if the argument is not an iterable', function () use ($isIterable) {
25 | expect($isIterable->validate('gigi')->equals(ValidationResult::errors([IsIterable::MESSAGE])))
26 | ->toBeTruthy();
27 | });
28 |
29 | it('returns a custom error result if the argument is not iterable and a custom formatter is passed', function () {
30 | $isIterable = IsIterable::withFormatter(function ($data) {
31 | return [(string) $data];
32 | });
33 |
34 | expect($isIterable->validate('gigi')->equals(ValidationResult::errors(['gigi'])))->toBeTruthy();
35 | });
36 |
37 | it('returns a translated error result if the argument is not iterable and a translator is passed', function () {
38 | $isIterable = IsIterable::withTranslator(KeyValueTranslator::withDictionary([
39 | IsIterable::MESSAGE => 'NO ITERABLE HERE!'
40 | ]));
41 |
42 | expect($isIterable->validate('gigi')->equals(ValidationResult::errors(['NO ITERABLE HERE!'])))->toBeTruthy();
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/spec/Basic/IsLessThanSpec.php:
--------------------------------------------------------------------------------
1 | validate(23)->equals(ValidationResult::valid(23)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the value is equal to be bound', function () use ($isLessThan) {
19 | expect($isLessThan->validate(42)->equals(ValidationResult::errors([IsLessThan::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the value is greater than be bound', function () use ($isLessThan) {
23 | expect($isLessThan->validate(87)->equals(ValidationResult::errors([IsLessThan::MESSAGE])))->toBeTruthy();
24 | });
25 |
26 | it(
27 | 'returns a custom error result if the argument is greater than the bound and a custom formatter is passed',
28 | function () {
29 | $isGreaterThan = IsLessThan::withBoundAndFormatter(42, function ($bound, $data) {
30 | return [$bound . $data];
31 | });
32 |
33 | expect($isGreaterThan->validate(87)->equals(ValidationResult::errors(['4287'])))->toBeTruthy();
34 | }
35 | );
36 |
37 | it(
38 | 'returns a translated error result if the argument is greater than the bound and a translator is passed',
39 | function () {
40 | $isGreaterThan = IsLessThan::withBoundAndTranslator(42, KeyValueTranslator::withDictionary([
41 | IsLessThan::MESSAGE => 'MORE THAN 42!'
42 | ]));
43 |
44 | expect($isGreaterThan->validate('87')->equals(ValidationResult::errors(['MORE THAN 42!'])))->toBeTruthy();
45 | }
46 | );
47 | });
48 |
--------------------------------------------------------------------------------
/spec/Basic/IsNotNullSpec.php:
--------------------------------------------------------------------------------
1 | validate(42)->equals(ValidationResult::valid(42)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not null', function () use ($isNotNull) {
19 | expect($isNotNull->validate(null)->equals(ValidationResult::errors([IsNotNull::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is not null and a custom formatter is passed', function () {
23 | $isNotNull = IsNotNull::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isNotNull->validate(null)->equals(ValidationResult::errors([''])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error result if the argument is not null and a translator is passed', function () {
31 | $isNotNull = IsNotNull::withTranslator(KeyValueTranslator::withDictionary([
32 | IsNotNull::MESSAGE => 'NULL HERE!'
33 | ]));
34 |
35 | expect($isNotNull->validate(null)->equals(ValidationResult::errors(['NULL HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/IsNullSpec.php:
--------------------------------------------------------------------------------
1 | validate(null)->equals(ValidationResult::valid(null)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not null', function () use ($isNull) {
19 | expect($isNull->validate(42)->equals(ValidationResult::errors([IsNull::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is null and a custom formatter is passed', function () {
23 | $isNull = IsNull::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isNull->validate(42)->equals(ValidationResult::errors(['42'])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error result if the argument is null and a translator is passed', function () {
31 | $isNull = IsNull::withTranslator(KeyValueTranslator::withDictionary([
32 | IsNull::MESSAGE => 'NO NULL HERE!'
33 | ]));
34 |
35 | expect($isNull->validate(42)->equals(ValidationResult::errors(['NO NULL HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/IsNumericSpec.php:
--------------------------------------------------------------------------------
1 | validate(12.34)->equals(ValidationResult::valid(12.34)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not numeric', function () use ($isNumeric) {
19 | expect($isNumeric->validate('gigi')->equals(ValidationResult::errors([IsNumeric::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is not numeric and a custom formatter is passed', function () {
23 | $isFloat = IsNumeric::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isFloat->validate('gigi')->equals(ValidationResult::errors(['gigi'])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error result if the argument is not numeric and a translator is passed', function () {
31 | $isFloat = IsNumeric::withTranslator(KeyValueTranslator::withDictionary([
32 | IsNumeric::MESSAGE => 'NO NUMERIC HERE!'
33 | ]));
34 |
35 | expect($isFloat->validate('gigi')->equals(ValidationResult::errors(['NO NUMERIC HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/IsObjectSpec.php:
--------------------------------------------------------------------------------
1 | validate($object)->equals(ValidationResult::valid($object)))->toBeTruthy();
18 | });
19 |
20 | it('returns an error result if the argument is not an object', function () use ($isObject) {
21 | expect($isObject->validate(true)->equals(ValidationResult::errors([IsObject::MESSAGE])))->toBeTruthy();
22 | });
23 |
24 | it('returns a custom error result if the argument is not an object and a custom formatter is passed', function () {
25 | $isObject = IsObject::withFormatter(function ($data) {
26 | return [(string) $data];
27 | });
28 |
29 | expect($isObject->validate(true)->equals(ValidationResult::errors(['1'])))->toBeTruthy();
30 | });
31 |
32 | it('returns a translated error result if the argument is not an object and a translator is passed', function () {
33 | $isObject = IsObject::withTranslator(KeyValueTranslator::withDictionary([
34 | IsObject::MESSAGE => 'NO OBJECT HERE!'
35 | ]));
36 |
37 | expect($isObject->validate(true)->equals(ValidationResult::errors(['NO OBJECT HERE!'])))->toBeTruthy();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/spec/Basic/IsResourceSpec.php:
--------------------------------------------------------------------------------
1 | validate($resource)->equals(ValidationResult::valid($resource)))->toBeTruthy();
20 | });
21 |
22 | it('returns an error result if the argument is not a resource', function () use ($isResource) {
23 | expect($isResource->validate('gigi')->equals(ValidationResult::errors([IsResource::MESSAGE])))
24 | ->toBeTruthy();
25 | });
26 |
27 | it('returns a custom error result if the argument is not a resource and custom formatter is passed', function () {
28 | $isResource = IsResource::withFormatter(function ($data) {
29 | return [(string) $data];
30 | });
31 |
32 | expect($isResource->validate('gigi')->equals(ValidationResult::errors(['gigi'])))->toBeTruthy();
33 | });
34 |
35 | it('returns a translated error result if the argument is not a resource and translator is passed', function () {
36 | $isResource = IsResource::withTranslator(KeyValueTranslator::withDictionary([
37 | IsResource::MESSAGE => 'NO RESOURCE HERE!'
38 | ]));
39 |
40 | expect($isResource->validate('gigi')->equals(ValidationResult::errors(['NO RESOURCE HERE!'])))->toBeTruthy();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/spec/Basic/IsStringSpec.php:
--------------------------------------------------------------------------------
1 | validate('true')->equals(ValidationResult::valid('true')))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is not a string', function () use ($isString) {
19 | expect($isString->validate(true)->equals(ValidationResult::errors([IsString::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is not a string and a custom formatter is passed', function () {
23 | $isString = IsString::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isString->validate(true)->equals(ValidationResult::errors(['1'])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error result if the argument is not a string and a translator is passed', function () {
31 | $isString = IsString::withTranslator(KeyValueTranslator::withDictionary([
32 | IsString::MESSAGE => 'NO STRING HERE!'
33 | ]));
34 |
35 | expect($isString->validate(true)->equals(ValidationResult::errors(['NO STRING HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/NonEmptySpec.php:
--------------------------------------------------------------------------------
1 | validate(42)->equals(ValidationResult::valid(42)))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the argument is empty', function () use ($nonEmpty) {
19 | expect($nonEmpty->validate([])->equals(ValidationResult::errors([NonEmpty::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the argument is empty and a custom formatter is passed', function () {
23 | $isArray = NonEmpty::withFormatter(function ($data) {
24 | return [(string) $data];
25 | });
26 |
27 | expect($isArray->validate(null)->equals(ValidationResult::errors([''])))->toBeTruthy();
28 | });
29 |
30 | it('returns a translated error message if the argument is empty and a translator is passed', function () {
31 | $isArray = NonEmpty::withTranslator(KeyValueTranslator::withDictionary([
32 | NonEmpty::MESSAGE => 'EMPTY HERE!'
33 | ]));
34 |
35 | expect($isArray->validate([])->equals(ValidationResult::errors(['EMPTY HERE!'])))->toBeTruthy();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/spec/Basic/RegexSpec.php:
--------------------------------------------------------------------------------
1 | validate('gigi')->equals(ValidationResult::valid('gigi')))->toBeTruthy();
16 | });
17 |
18 | it('returns an error result if the pattern does not match', function () use ($regex) {
19 | expect($regex->validate('gigi@zucon')->equals(ValidationResult::errors([Regex::MESSAGE])))->toBeTruthy();
20 | });
21 |
22 | it('returns a custom error result if the pattern does not match and a custom formatter is passed', function () {
23 | $regex = Regex::withPatternAndFormatter('/^[\p{L} ]*$/u', function ($pattern, $data) {
24 | return [$pattern . $data];
25 | });
26 |
27 | expect($regex->validate('gigi@zucon')->equals(ValidationResult::errors(['/^[\p{L} ]*$/u' . 'gigi@zucon'])))
28 | ->toBeTruthy();
29 | });
30 |
31 | it('returns a translated error result if the pattern does not match and a translator is passed', function () {
32 | $regex = Regex::withPatternAndTranslator(
33 | '/^[\p{L} ]*$/u',
34 | KeyValueTranslator::withDictionary([
35 | Regex::MESSAGE => 'NO MATCH HERE!'
36 | ])
37 | );
38 |
39 | expect($regex->validate('gigi@zucon')->equals(ValidationResult::errors(['NO MATCH HERE!'])))->toBeTruthy();
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/spec/Basic/UserSpec.php:
--------------------------------------------------------------------------------
1 | = 0;
44 | })
45 | ])
46 | )
47 | ])
48 | ])
49 | ]);
50 |
51 | it('fails if the data is not an array', function () use ($userValidation) {
52 | expect($userValidation->validate('gigi')->equals(ValidationResult::errors([IsArray::MESSAGE])))
53 | ->toBeTruthy();
54 | });
55 |
56 | it('fails if the name field is missing', function () use ($userValidation) {
57 | expect($userValidation->validate(['age' => 18])->equals(ValidationResult::errors([HasKey::MISSING_KEY])))
58 | ->toBeTruthy();
59 | });
60 |
61 | it('fails if the name field is not a string', function () use ($userValidation) {
62 | expect(
63 | $userValidation->validate(['name' => 42, 'age' => 42])
64 | ->equals(ValidationResult::errors([IsString::MESSAGE]))
65 | )->toBeTruthy();
66 | });
67 |
68 | it('fails if the name field is an empty string', function () use ($userValidation) {
69 | expect(
70 | $userValidation->validate(['name' => '', 'age' => 42])
71 | ->equals(ValidationResult::errors([NonEmpty::MESSAGE]))
72 | )->toBeTruthy();
73 | });
74 |
75 | it('fails if the age field is missing', function () use ($userValidation) {
76 | expect($userValidation->validate(['name' => 'gigi'])->equals(ValidationResult::errors([HasKey::MISSING_KEY])))
77 | ->toBeTruthy();
78 | });
79 |
80 | it('fails if the age field is not an integer', function () use ($userValidation) {
81 | expect(
82 | $userValidation->validate(['name' => 'gigi', 'age' => 'gigi'])
83 | ->equals(ValidationResult::errors([IsInteger::MESSAGE]))
84 | )->toBeTruthy();
85 | });
86 |
87 | it('fails if the age field is negative', function () use ($userValidation) {
88 | expect(
89 | $userValidation->validate(['name' => 'gigi', 'age' => -3])
90 | ->equals(ValidationResult::errors([IsAsAsserted::NOT_AS_ASSERTED]))
91 | )->toBeTruthy();
92 | });
93 |
94 | it(
95 | 'succeeds if the name is a non empty string and the age is a positive integer',
96 | function () use ($userValidation) {
97 | expect(
98 | $userValidation->validate(['name' => 'gigi', 'age' => 42])
99 | ->equals(ValidationResult::valid(['name' => 'gigi', 'age' => 42]))
100 | )->toBeTruthy();
101 | }
102 | );
103 | });
104 |
--------------------------------------------------------------------------------
/spec/Basic/ValidSpec.php:
--------------------------------------------------------------------------------
1 | validate('anything')->equals(ValidationResult::valid('anything')))->toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/spec/Combinator/AllSpec.php:
--------------------------------------------------------------------------------
1 | validate('gigi')->equals(ValidationResult::valid('gigi')))->toBeTruthy();
20 | });
21 |
22 | it('returns a valid result if every validator succeeds', function () {
23 | $all = All::validations([
24 | new IsString(),
25 | Regex::withPattern('/^[\p{L} ]*$/u')
26 | ]);
27 |
28 | expect($all->validate('gigi')->equals(ValidationResult::valid('gigi')))->toBeTruthy();
29 | });
30 |
31 | it('returns an error result if one validator fails with all the errors combined', function () {
32 | $all = All::validations([
33 | new IsString(),
34 | new IsBool()
35 | ]);
36 |
37 | expect($all->validate(42)->equals(ValidationResult::errors([
38 | IsString::MESSAGE,
39 | IsBool::MESSAGE
40 | ])))->toBeTruthy();
41 | });
42 |
43 | it('returns a custom error result if one validator fails using the custom error formatter', function () {
44 | $all = All::validationsWithFormatter([
45 | new IsString(),
46 | new IsBool()
47 | ], function (...$errors) {
48 | return [json_encode($errors)];
49 | });
50 |
51 | expect(
52 | $all->validate(42)->equals(ValidationResult::errors(['[["[[],[\"is-string.not-a-string\"]]"],["is-bool.not-a-bool"]]']))
53 | )->toBeTruthy();
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/spec/Combinator/AnyElementSpec.php:
--------------------------------------------------------------------------------
1 | validate([])->equals(ValidationResult::errors([])))->toBeTruthy();
16 | });
17 |
18 | it('returns a valid result if the validation on one element succeeds', function () {
19 | $anyElement = AnyElement::validation(new IsString());
20 |
21 | expect($anyElement->validate([42, 'bepi'])->equals(ValidationResult::valid([42, 'bepi'])))->toBeTruthy();
22 | });
23 |
24 | it('returns a valid result if the validation on the first element succeeds', function () {
25 | $anyElement = AnyElement::validation(new IsString());
26 |
27 | expect($anyElement->validate(['gigi', 42])->equals(ValidationResult::valid(['gigi', 42])))->toBeTruthy();
28 | });
29 |
30 | it('returns a valid result if the validation succeeds on both elements', function () {
31 | $anyElement = AnyElement::validation(new IsString());
32 |
33 | expect($anyElement->validate(['gigi', 'bepi'])->equals(ValidationResult::valid(['gigi', 'bepi'])))->toBeTruthy();
34 | });
35 |
36 | it('returns an error result if every element fails the validation', function () {
37 | $anyElement = AnyElement::validation(new IsString());
38 |
39 | expect($anyElement->validate([true, 42])->equals(ValidationResult::errors([
40 | 0 => [IsString::MESSAGE],
41 | 1 => [IsString::MESSAGE]
42 | ])))->toBeTruthy();
43 | });
44 |
45 | it(
46 | 'returns a custom error result if every element fails the validation and a custom formatter is passed',
47 | function () {
48 | $anyElement = AnyElement::validationWithFormatter(
49 | new IsString(),
50 | function ($key, $resultMessages, $validationMessages) {
51 | $resultMessages[] = $key . $validationMessages[0];
52 |
53 | return $resultMessages;
54 | }
55 | );
56 |
57 | expect($anyElement->validate([true, 42])->equals(ValidationResult::errors([
58 | 0 . IsString::MESSAGE,
59 | 1 . IsString::MESSAGE
60 | ])))->toBeTruthy();
61 | }
62 | );
63 | });
64 |
--------------------------------------------------------------------------------
/spec/Combinator/AnySpec.php:
--------------------------------------------------------------------------------
1 | validate('gigi')->equals(ValidationResult::errors([Any::NOT_EVEN_ONE => []])))->toBeTruthy();
18 | });
19 |
20 | it('returns a valid result if one validator succeeds', function () {
21 | $any = Any::validations([
22 | new IsString(),
23 | new IsBool()
24 | ]);
25 |
26 | expect($any->validate(true)->equals(ValidationResult::valid(true)))->toBeTruthy();
27 | });
28 |
29 | it('returns an error result if every validator fails with all the errors combined', function () {
30 | $any = Any::validations([
31 | new IsString(),
32 | new IsBool()
33 | ]);
34 |
35 | expect($any->validate(42)->equals(ValidationResult::errors([
36 | Any::NOT_EVEN_ONE => [
37 | IsString::MESSAGE,
38 | IsBool::MESSAGE
39 | ]
40 | ])))->toBeTruthy();
41 | });
42 |
43 | it(
44 | 'returns a custom error result if every validator fails with the errors combined by the error formatter',
45 | function () {
46 | $any = Any::validationsWithFormatter([
47 | new IsString(),
48 | new IsBool()
49 | ], function (array $messages) {
50 | return $messages;
51 | });
52 |
53 | expect($any->validate(42)->equals(ValidationResult::errors([
54 | IsString::MESSAGE,
55 | IsBool::MESSAGE
56 | ])))->toBeTruthy();
57 | }
58 | );
59 |
60 | it(
61 | 'returns a translated error result if every validator fails with the errors combined by the translator',
62 | function () {
63 | $any = Any::validationsWithTranslator([
64 | new IsString(),
65 | new IsBool()
66 | ], KeyValueTranslator::withDictionary([
67 | Any::NOT_EVEN_ONE => 'NOT EVEN ONE!'
68 | ]));
69 |
70 | expect($any->validate(42)->equals(ValidationResult::errors([
71 | 'NOT EVEN ONE!' => [
72 | IsString::MESSAGE,
73 | IsBool::MESSAGE
74 | ]
75 | ])))->toBeTruthy();
76 | }
77 | );
78 | });
79 |
--------------------------------------------------------------------------------
/spec/Combinator/ApplySpec.php:
--------------------------------------------------------------------------------
1 | validate('abc')->equals(ValidationResult::valid(3)))->toBeTruthy();
18 | });
19 |
20 | it('does not modify the result of a failed validation', function () {
21 | $f = ValidationResult::valid(function (string $x) {
22 | return strlen($x);
23 | });
24 |
25 | expect(
26 | Apply::to(new IsString(), $f)->validate(42)->equals(ValidationResult::errors([IsString::MESSAGE]))
27 | )->toBeTruthy();
28 | });
29 |
30 | it('does return a failed validation if the applied function is already failed', function () {
31 | $f = ValidationResult::errors(['gigi']);
32 |
33 | expect(Apply::to(new IsString(), $f)->validate('abc')->equals($f))->toBeTruthy();
34 | });
35 |
36 | it(
37 | 'does return a failed validation cumulating errors if both the validation and the applied function are failed',
38 | function () {
39 | $f = ValidationResult::errors(['gigi']);
40 |
41 | expect(Apply::to(new IsString(), $f)->validate(42)->equals(ValidationResult::errors([
42 | 'gigi',
43 | IsString::MESSAGE
44 | ])))->toBeTruthy();
45 | }
46 | );
47 | });
48 |
--------------------------------------------------------------------------------
/spec/Combinator/AssociativeSpec.php:
--------------------------------------------------------------------------------
1 | Sequence::validations([
20 | new IsString(),
21 | new NonEmpty()
22 | ]),
23 | 'age' => Sequence::validations([
24 | new IsInteger(),
25 | IsGreaterThan::withBound(0)
26 | ])
27 | ]);
28 |
29 | it('returns a valid result is the data pass validation', function () use ($validation) {
30 | $data = [
31 | 'name' => 'gigi',
32 | 'age' => 42
33 | ];
34 |
35 | expect($validation->validate($data)->equals(ValidationResult::valid($data)))->toBeTruthy();
36 | });
37 |
38 | it('returns an error result if the data is not an array', function () use ($validation) {
39 | expect($validation->validate('gigi')->equals(ValidationResult::errors([IsArray::MESSAGE])))->toBeTruthy();
40 | });
41 |
42 | it('returns an error result if a key is missing', function () use ($validation) {
43 | $errors = [
44 | 'name' => [HasKey::MISSING_KEY],
45 | 'age' => [HasKey::MISSING_KEY]
46 | ];
47 |
48 | expect($validation->validate([])->equals(ValidationResult::errors($errors)))->toBeTruthy();
49 | });
50 |
51 | it('returns an error result if a value fails its own validation', function () use ($validation) {
52 | $data = [
53 | 'name' => 42,
54 | 'age' => 'gigi'
55 | ];
56 |
57 | $errors = [
58 | 'name' => [IsString::MESSAGE],
59 | 'age' => [IsInteger::MESSAGE]
60 | ];
61 |
62 | expect($validation->validate($data)->equals(ValidationResult::errors($errors)))->toBeTruthy();
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/spec/Combinator/BindSpec.php:
--------------------------------------------------------------------------------
1 | validate('abc');
15 | };
16 |
17 | it('does modify the result of a correct validation', function () use ($f) {
18 | expect(Bind::to(new IsString(), $f)->validate('/[abc]+/')->equals(ValidationResult::valid('abc')))
19 | ->toBeTruthy();
20 | });
21 |
22 | it('does not modify the result of a failed validation', function () use ($f) {
23 | expect(
24 | Bind::to(new IsString(), $f)->validate('/[def]+/')->equals(ValidationResult::errors([Regex::MESSAGE]))
25 | )->toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/spec/Combinator/EveryElementSpec.php:
--------------------------------------------------------------------------------
1 | validate([])->equals(ValidationResult::valid([])))->toBeTruthy();
16 | });
17 |
18 | it('returns a valid result if the validation on every element succeeds', function () {
19 | $everyElement = EveryElement::validation(new IsString());
20 |
21 | expect($everyElement->validate(['gigi', 'bepi'])->equals(ValidationResult::valid(['gigi', 'bepi'])))
22 | ->toBeTruthy();
23 | });
24 |
25 | it('returns an error result if one element fails the validation', function () {
26 | $everyElement = EveryElement::validation(new IsString());
27 |
28 | expect($everyElement->validate(['gigi', 42])->equals(ValidationResult::errors([
29 | 1 => [IsString::MESSAGE]
30 | ])))->toBeTruthy();
31 | });
32 |
33 | it(
34 | 'returns a custom error result if one element fails the validation and a custom formatter is passed',
35 | function () {
36 | $everyElement = EveryElement::validationWithFormatter(
37 | new IsString(),
38 | function ($key, $resultMessages, $validationMessages) {
39 | $resultMessages[] = $key . $validationMessages[0];
40 |
41 | return $resultMessages;
42 | }
43 | );
44 |
45 | expect($everyElement->validate([true, 42])->equals(ValidationResult::errors([
46 | 0 . IsString::MESSAGE,
47 | 1 . IsString::MESSAGE
48 | ])))->toBeTruthy();
49 | }
50 | );
51 | });
52 |
--------------------------------------------------------------------------------
/spec/Combinator/FocusSpec.php:
--------------------------------------------------------------------------------
1 | validate(['nested' => 'gigi'])->equals(ValidationResult::valid(['nested' => 'gigi'])))
21 | ->toBeTruthy();
22 | });
23 |
24 | it('returns an error result if the mapped data is not valid', function () {
25 | $focus = Focus::on(
26 | function ($data) {
27 | return $data['nested'];
28 | },
29 | new IsString()
30 | );
31 |
32 | expect($focus->validate(['nested' => 42])->equals(ValidationResult::errors([IsString::MESSAGE])))
33 | ->toBeTruthy();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/spec/Combinator/MapErrorsSpec.php:
--------------------------------------------------------------------------------
1 | validate('gigi')->equals(ValidationResult::valid('gigi')))
18 | ->toBeTruthy();
19 | });
20 |
21 | it('does modify the result of a failed validation', function () use ($map) {
22 | expect(
23 | MapErrors::to(new IsString(), $map)->validate(42)->equals(ValidationResult::errors([
24 | strtoupper(IsString::MESSAGE)
25 | ]))
26 | )->toBeTruthy();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/spec/Combinator/MapSpec.php:
--------------------------------------------------------------------------------
1 | validate(42)->equals((new IsString())->validate(42)))
14 | ->toBeTruthy();
15 | });
16 |
17 | it('does modify the result of a correct validation', function () {
18 | expect(Map::to(new IsString(), 'strtolower')->validate('GIGI')->equals(ValidationResult::valid('gigi')))
19 | ->toBeTruthy();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/spec/Combinator/SequenceSpec.php:
--------------------------------------------------------------------------------
1 | validate('gigi')->equals(ValidationResult::valid('gigi')))->toBeTruthy();
18 | });
19 |
20 | it('returns a valid result if every validator succeeds', function () {
21 | $sequence = Sequence::validations([
22 | new IsString(),
23 | Regex::withPattern('/^[\p{L} ]*$/u')
24 | ]);
25 |
26 | expect($sequence->validate('gigi')->equals(ValidationResult::valid('gigi')))->toBeTruthy();
27 | });
28 |
29 | it('returns an error result if one validator fails with just the first error', function () {
30 | $sequence = Sequence::validations([
31 | new IsString(),
32 | new IsBool()
33 | ]);
34 |
35 | expect($sequence->validate(42)->equals(ValidationResult::errors([
36 | IsString::MESSAGE
37 | ])))->toBeTruthy();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/spec/Combinator/TranslateErrorsSpec.php:
--------------------------------------------------------------------------------
1 | 'Only strings here!',
19 | IsInteger::MESSAGE => 'Only integers here!'
20 | ]);
21 |
22 | $validator = TranslateErrors::validationWithTranslator(new IsString(), $translator);
23 |
24 | it('does not modify the result of a correct validation', function () use ($validator) {
25 | expect($validator->validate('gigi')->equals(ValidationResult::valid('gigi')))->toBeTruthy();
26 | });
27 |
28 | it('translates the result of a failed validation', function () use ($validator) {
29 | expect($validator->validate(42)->equals(ValidationResult::errors(['Only strings here!'])))->toBeTruthy();
30 | });
31 |
32 | it('translates nested results of a failed validation', function () use ($translator) {
33 | $validator = TranslateErrors::validationWithTranslator(
34 | All::validations([
35 | Focus::on(
36 | function ($data) {
37 | return $data['a'];
38 | },
39 | MapErrors::to(
40 | new IsString(),
41 | function (array $messages) {
42 | return ['a' => $messages];
43 | }
44 | )
45 | ),
46 | Focus::on(
47 | function ($data) {
48 | return $data['b'];
49 | },
50 | MapErrors::to(
51 | new IsInteger(),
52 | function (array $messages) {
53 | return ['b' => $messages];
54 | }
55 | )
56 | )
57 | ]),
58 | $translator
59 | );
60 |
61 | $data = [
62 | 'a' => 42,
63 | 'b' => 'gigi'
64 | ];
65 |
66 | $errors = [
67 | 'a' => ['Only strings here!'],
68 | 'b' => ['Only integers here!']
69 | ];
70 |
71 | expect($validator->validate($data)->equals(ValidationResult::errors($errors)))->toBeTruthy();
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/spec/Example/ApplicativeMonadicSpec.php:
--------------------------------------------------------------------------------
1 | something
17 | * @return callable $a -> ($b -> something)
18 | */
19 | function curry($f)
20 | {
21 | return function ($a) use ($f) {
22 | return function ($b) use ($a, $f) {
23 | return $f($a, $b);
24 | };
25 | };
26 | }
27 |
28 | /**
29 | * This example is replicated from https://fsharpforfunandprofit.com/posts/elevated-world-3/#validation
30 | */
31 |
32 | class CustomerId implements Equality
33 | {
34 | /**
35 | * @var int should be positive
36 | */
37 | private $id;
38 |
39 | public function __construct(int $id)
40 | {
41 | $this->id = $id;
42 | }
43 |
44 | /**
45 | * @param int $id
46 | * @return ValidationResult containing a CustomerId
47 | */
48 | public static function buildValid(int $id): ValidationResult
49 | {
50 | return IsGreaterThan::withBound(0)
51 | ->validate($id)
52 | ->map(function (int $id) {
53 | return new self($id);
54 | });
55 | }
56 |
57 | public function id(): int
58 | {
59 | return $this->id;
60 | }
61 |
62 | public function equals($that): bool
63 | {
64 | return $that instanceof self && $this->id === $that->id;
65 | }
66 | }
67 |
68 | // phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
69 | class EmailAddress implements Equality
70 | {
71 | /**
72 | * @var string should contain "@"
73 | */
74 | private $email;
75 |
76 | public function __construct(string $email)
77 | {
78 | $this->email = $email;
79 | }
80 |
81 | /**
82 | * @param string $email
83 | * @return ValidationResult containing an EmailAddress
84 | */
85 | public static function buildValid(string $email): ValidationResult
86 | {
87 | return Regex::withPattern('/^[\w.]+@[\w.]+$/u')
88 | ->validate($email)
89 | ->map(function (string $email) {
90 | return new self($email);
91 | });
92 | }
93 |
94 | public function email(): string
95 | {
96 | return $this->email;
97 | }
98 |
99 | public function equals($that): bool
100 | {
101 | return $that instanceof self && $this->email === $that->email;
102 | }
103 | }
104 |
105 | // phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
106 | class CustomerInfo implements Equality
107 | {
108 | /**
109 | * @var CustomerId
110 | */
111 | private $id;
112 |
113 | /**
114 | * @var EmailAddress
115 | */
116 | private $emailAddress;
117 |
118 | public function __construct(
119 | CustomerId $id,
120 | EmailAddress $emailAddress
121 | ) {
122 | $this->id = $id;
123 | $this->emailAddress = $emailAddress;
124 | }
125 |
126 | /**
127 | * @param int $id
128 | * @param string $email
129 | * @return ValidationResult containing a CustomerInfo
130 | */
131 | public static function buildValidApplicative(int $id, string $email): ValidationResult
132 | {
133 | $idResult = CustomerId::buildValid($id);
134 | $emailResult = EmailAddress::buildValid($email);
135 |
136 | return $emailResult->apply($idResult->map(curry(function (CustomerId $id, EmailAddress $emailAddress) {
137 | return new self($id, $emailAddress);
138 | })));
139 | }
140 |
141 | /**
142 | * we can rewrite buildValidApplicative using the lift function
143 | *
144 | * @param int $id
145 | * @param string $email
146 | * @return ValidationResult containing a CustomerInfo
147 | */
148 | public static function buildValidApplicativeWithLift(int $id, string $email): ValidationResult
149 | {
150 | $idResult = CustomerId::buildValid($id);
151 | $emailResult = EmailAddress::buildValid($email);
152 |
153 | return lift(static function (CustomerId $id, EmailAddress $emailAddress) {
154 | return new self($id, $emailAddress);
155 | })($idResult, $emailResult);
156 | }
157 |
158 | /**
159 | * @param int $id
160 | * @param string $email
161 | * @return ValidationResult containing a CustomerInfo
162 | */
163 | public static function buildValidMonadic(int $id, string $email): ValidationResult
164 | {
165 | $idResult = CustomerId::buildValid($id);
166 | $emailResult = EmailAddress::buildValid($email);
167 |
168 | return $idResult->bind(function (CustomerId $id) use ($emailResult) {
169 | return $emailResult->bind(function (EmailAddress $email) use ($id) {
170 | return ValidationResult::valid(new self($id, $email));
171 | });
172 | });
173 | }
174 |
175 | /**
176 | * we can rewrite tbuildValidMonadic using the do_ function
177 | *
178 | * @param int $id
179 | * @param string $email
180 | * @return ValidationResult
181 | */
182 | public static function buildValidMonadicWithDo(int $id, string $email): ValidationResult
183 | {
184 | return mdo(
185 | static function () use ($id) {
186 | return CustomerId::buildValid($id);
187 | },
188 | static function () use ($email) {
189 | return EmailAddress::buildValid($email);
190 | },
191 | static function ($id, $email) {
192 | return ValidationResult::valid(new self($id, $email));
193 | }
194 | );
195 | }
196 |
197 | public function id(): CustomerId
198 | {
199 | return $this->id;
200 | }
201 |
202 | public function emailAddress(): EmailAddress
203 | {
204 | return $this->emailAddress;
205 | }
206 |
207 | public function equals($that): bool
208 | {
209 | return $that instanceof self &&
210 | $this->id->equals($that->id) &&
211 | $this->emailAddress->equals($that->emailAddress);
212 | }
213 | }
214 |
215 | describe('applicative style', function () {
216 | it('validates correctly a customer with correct id and email', function () {
217 | expect(
218 | CustomerInfo::buildValidApplicative(42, 'gigi@zucon.it')->equals(
219 | ValidationResult::valid(new CustomerInfo(new CustomerId(42), new EmailAddress('gigi@zucon.it')))
220 | )
221 | )->toBeTruthy();
222 | });
223 |
224 | it('returns the correct error if the id is negative', function () {
225 | expect(
226 | CustomerInfo::buildValidApplicative(-42, 'gigi@zucon.it')->equals(
227 | ValidationResult::errors([IsGreaterThan::MESSAGE])
228 | )
229 | )->toBeTruthy();
230 | });
231 |
232 | it('returns the correct error if the email is not valid', function () {
233 | expect(
234 | CustomerInfo::buildValidApplicative(42, 'gigi')->equals(
235 | ValidationResult::errors([Regex::MESSAGE])
236 | )
237 | )->toBeTruthy();
238 | });
239 |
240 | it('returns the correct error messages if both is and email are not valid', function () {
241 | expect(
242 | CustomerInfo::buildValidApplicative(-42, 'gigi')->equals(
243 | ValidationResult::errors([IsGreaterThan::MESSAGE, Regex::MESSAGE])
244 | )
245 | )->toBeTruthy();
246 | });
247 | });
248 |
249 | describe('applicative style with lift', function () {
250 | it('validates correctly a customer with correct id and email', function () {
251 | expect(
252 | CustomerInfo::buildValidApplicativeWithLift(42, 'gigi@zucon.it')->equals(
253 | ValidationResult::valid(new CustomerInfo(new CustomerId(42), new EmailAddress('gigi@zucon.it')))
254 | )
255 | )->toBeTruthy();
256 | });
257 |
258 | it('returns the correct error if the id is negative', function () {
259 | expect(
260 | CustomerInfo::buildValidApplicativeWithLift(-42, 'gigi@zucon.it')->equals(
261 | ValidationResult::errors([IsGreaterThan::MESSAGE])
262 | )
263 | )->toBeTruthy();
264 | });
265 |
266 | it('returns the correct error if the email is not valid', function () {
267 | expect(
268 | CustomerInfo::buildValidApplicativeWithLift(42, 'gigi')->equals(
269 | ValidationResult::errors([Regex::MESSAGE])
270 | )
271 | )->toBeTruthy();
272 | });
273 |
274 | it('returns the correct error messages if both is and email are not valid', function () {
275 | expect(
276 | CustomerInfo::buildValidApplicativeWithLift(-42, 'gigi')->equals(
277 | ValidationResult::errors([IsGreaterThan::MESSAGE, Regex::MESSAGE])
278 | )
279 | )->toBeTruthy();
280 | });
281 | });
282 |
283 | describe('monadic style', function () {
284 | it('validates correctly a customer with correct id and email', function () {
285 | expect(
286 | CustomerInfo::buildValidMonadic(42, 'gigi@zucon.it')->equals(
287 | ValidationResult::valid(new CustomerInfo(new CustomerId(42), new EmailAddress('gigi@zucon.it')))
288 | )
289 | )->toBeTruthy();
290 | });
291 |
292 | it('returns the correct error if the id is negative', function () {
293 | expect(
294 | CustomerInfo::buildValidMonadic(-42, 'gigi@zucon.it')->equals(
295 | ValidationResult::errors([IsGreaterThan::MESSAGE])
296 | )
297 | )->toBeTruthy();
298 | });
299 |
300 | it('returns the correct error if the email is not valid', function () {
301 | expect(
302 | CustomerInfo::buildValidMonadic(42, 'gigi')->equals(
303 | ValidationResult::errors([Regex::MESSAGE])
304 | )
305 | )->toBeTruthy();
306 | });
307 |
308 | it('returns the correct error messages if both is and email are not valid', function () {
309 | expect(
310 | CustomerInfo::buildValidMonadic(-42, 'gigi')->equals(
311 | ValidationResult::errors([IsGreaterThan::MESSAGE])
312 | )
313 | )->toBeTruthy();
314 | });
315 | });
316 |
317 | describe('monadic style with do_', function () {
318 | it('validates correctly a customer with correct id and email', function () {
319 | expect(
320 | CustomerInfo::buildValidMonadicWithDo(42, 'gigi@zucon.it')->equals(
321 | ValidationResult::valid(new CustomerInfo(new CustomerId(42), new EmailAddress('gigi@zucon.it')))
322 | )
323 | )->toBeTruthy();
324 | });
325 |
326 | it('returns the correct error if the id is negative', function () {
327 | expect(
328 | CustomerInfo::buildValidMonadicWithDo(-42, 'gigi@zucon.it')->equals(
329 | ValidationResult::errors([IsGreaterThan::MESSAGE])
330 | )
331 | )->toBeTruthy();
332 | });
333 |
334 | it('returns the correct error if the email is not valid', function () {
335 | expect(
336 | CustomerInfo::buildValidMonadicWithDo(42, 'gigi')->equals(
337 | ValidationResult::errors([Regex::MESSAGE])
338 | )
339 | )->toBeTruthy();
340 | });
341 |
342 | it('returns the correct error messages if both is and email are not valid', function () {
343 | expect(
344 | CustomerInfo::buildValidMonadicWithDo(-42, 'gigi')->equals(
345 | ValidationResult::errors([IsGreaterThan::MESSAGE])
346 | )
347 | )->toBeTruthy();
348 | });
349 | });
350 |
--------------------------------------------------------------------------------
/spec/Result/ValidationResultSpec.php:
--------------------------------------------------------------------------------
1 | join($result2, $joinValid, 'array_merge'))->toEqual(ValidationResult::valid('gigibepi'));
20 | });
21 |
22 | it('joins a valid result with an invalid one to an invalid result preserving errors', function () {
23 | $result1 = ValidationResult::valid('gigi');
24 | $result2 = ValidationResult::errors(['bepi']);
25 |
26 | $joinValid = function ($a, $b) {
27 | return $a . $b;
28 | };
29 |
30 | expect($result1->join($result2, $joinValid, 'array_merge'))->toEqual(ValidationResult::errors(['bepi']));
31 | });
32 |
33 | it('joins an invalid result with a valid one to an invalid result preserving errors', function () {
34 | $result1 = ValidationResult::errors(['gigi']);
35 | $result2 = ValidationResult::valid('bepi');
36 |
37 | $joinValid = function ($a, $b) {
38 | return $a . $b;
39 | };
40 |
41 | expect($result1->join($result2, $joinValid, 'array_merge'))->toEqual(ValidationResult::errors(['gigi']));
42 | });
43 |
44 | it('joins two invalid results to an invalid result merging errors', function () {
45 | $result1 = ValidationResult::errors(['gigi']);
46 | $result2 = ValidationResult::errors(['bepi']);
47 |
48 | $joinValid = function ($a, $b) {
49 | return $a . $b;
50 | };
51 |
52 | expect($result1->join($result2, $joinValid, 'array_merge'))
53 | ->toEqual(ValidationResult::errors(['gigi', 'bepi']));
54 | });
55 |
56 | it('meets two valid result to a valid result joining the results', function () {
57 | $result1 = ValidationResult::valid('gigi');
58 | $result2 = ValidationResult::valid('bepi');
59 |
60 | expect($result1->meet(
61 | $result2,
62 | function ($x, $y) {
63 | return $x . $y;
64 | },
65 | function ($x) {
66 | return $x;
67 | },
68 | function ($y) {
69 | return $y;
70 | },
71 | 'array_merge'
72 | ))->toEqual(ValidationResult::valid('gigibepi'));
73 | });
74 |
75 | it('meets a valid result with an invalid one to an valid result preserving value', function () {
76 | $result1 = ValidationResult::valid('gigi');
77 | $result2 = ValidationResult::errors(['bepi']);
78 |
79 | expect($result1->meet(
80 | $result2,
81 | function ($x, $y) {
82 | return $x . $y;
83 | },
84 | function ($x) {
85 | return $x;
86 | },
87 | function ($y) {
88 | return $y;
89 | },
90 | 'array_merge'
91 | ))->toEqual(ValidationResult::valid('gigi'));
92 | });
93 |
94 | it('meets an invalid result with a valid one to a valid result preserving value', function () {
95 | $result1 = ValidationResult::errors(['gigi']);
96 | $result2 = ValidationResult::valid('bepi');
97 |
98 | expect($result1->meet(
99 | $result2,
100 | function ($x, $y) {
101 | return $x . $y;
102 | },
103 | function ($x) {
104 | return $x;
105 | },
106 | function ($y) {
107 | return $y;
108 | },
109 | 'array_merge'
110 | ))->toEqual(ValidationResult::valid('bepi'));
111 | });
112 |
113 | it('meets two invalid results to an invalid result merging errors', function () {
114 | $result1 = ValidationResult::errors(['gigi']);
115 | $result2 = ValidationResult::errors(['bepi']);
116 |
117 | expect($result1->meet(
118 | $result2,
119 | function ($x, $y) {
120 | return $x . $y;
121 | },
122 | function ($x) {
123 | return $x;
124 | },
125 | function ($y) {
126 | return $y;
127 | },
128 | 'array_merge'
129 | ))->toEqual(ValidationResult::errors(['gigi', 'bepi']));
130 | });
131 |
132 | it('processes correctly a valid result', function () {
133 | $result = ValidationResult::valid(42);
134 |
135 | $f = function ($n) {
136 | return $n + 1;
137 | };
138 |
139 | $id = function ($n) {
140 | return $n;
141 | };
142 |
143 | expect($result->process($f, $id))->toEqual(43);
144 | });
145 |
146 | it('processes correctly an invalid result', function () {
147 | $result = ValidationResult::errors(['gigi']);
148 |
149 | $id = function ($n) {
150 | return $n;
151 | };
152 |
153 | $arrayUp = function ($a) {
154 | return array_map('strtoupper', $a);
155 | };
156 |
157 | expect($result->process($id, $arrayUp))->toEqual(['GIGI']);
158 | });
159 |
160 | it('maps a valid result to a valid result with a mapped value', function () {
161 | $result = ValidationResult::valid(42);
162 |
163 | $f = function ($n) {
164 | return $n + 1;
165 | };
166 |
167 | expect($result->map($f))->toEqual(ValidationResult::valid(43));
168 | });
169 |
170 | it('maps an invalid result to itself', function () {
171 | $result = ValidationResult::errors(['gigi']);
172 |
173 | $f = function ($n) {
174 | return $n + 1;
175 | };
176 |
177 | expect($result->map($f))->toEqual($result);
178 | });
179 |
180 | it('mapErrors a valid result to itself', function () {
181 | $result = ValidationResult::valid(42);
182 |
183 | $arrayUp = function ($a) {
184 | return array_map('strtoupper', $a);
185 | };
186 |
187 | expect($result->mapErrors($arrayUp))->toEqual($result);
188 | });
189 |
190 | it('mapErrors an invalid result to an invalid result with mapped messages', function () {
191 | $result = ValidationResult::errors(['gigi']);
192 |
193 | $arrayUp = function ($a) {
194 | return array_map('strtoupper', $a);
195 | };
196 |
197 | expect($result->mapErrors($arrayUp))->toEqual(ValidationResult::errors(['GIGI']));
198 | });
199 |
200 | it('binds correctly a valid result', function () {
201 | $result = ValidationResult::valid(42);
202 |
203 | $bind = function (int $n) {
204 | return IsAsAsserted::withAssertion(function (int $m) use ($n) {
205 | return $m < $n;
206 | })->validate(37);
207 | };
208 |
209 | expect($result->bind($bind))->toEqual(ValidationResult::valid(37));
210 | });
211 |
212 | it('binds correctly an invalid result', function () {
213 | $result = ValidationResult::errors(['gigi']);
214 |
215 | $bind = function (int $n) {
216 | return IsAsAsserted::withAssertion(function (int $m) use ($n) {
217 | return $m < $n;
218 | })->validate(37);
219 | };
220 |
221 | expect($result->bind($bind))->toEqual($result);
222 | });
223 |
224 | it('applies correctly a valid function to a valid result', function () {
225 | $result = ValidationResult::valid(42);
226 |
227 | $apply = ValidationResult::valid(function (int $x) {
228 | return $x + 3;
229 | });
230 |
231 | expect($result->apply($apply))->toEqual(ValidationResult::valid(45));
232 | });
233 |
234 | it('applies a valid function to an invalid result producing the same invalid result', function () {
235 | $result = ValidationResult::errors(['gigi']);
236 |
237 | $apply = ValidationResult::valid(function (int $x) {
238 | return $x + 3;
239 | });
240 |
241 | expect($result->apply($apply))->toEqual($result);
242 | });
243 |
244 | it('applies an invalid function to a valid result producing an invalid result', function () {
245 | $result = ValidationResult::valid(42);
246 |
247 | $apply = ValidationResult::errors(['gigi']);
248 |
249 | expect($result->apply($apply))->toEqual($apply);
250 | });
251 |
252 | it('applies an invalid function to an invalid result producing an invalid result with both error messages', function () {
253 | $result = ValidationResult::errors(['gigi']);
254 |
255 | $apply = ValidationResult::errors(['toni']);
256 |
257 | expect($result->apply($apply))->toEqual(ValidationResult::errors(['toni', 'gigi']));
258 | });
259 |
260 | it('is equal to another result with the same valid content', function () {
261 | $result1 = ValidationResult::valid(42);
262 | $result2 = ValidationResult::valid(42);
263 |
264 | expect($result1->equals($result2))->toBeTruthy();
265 | });
266 |
267 | it('is equal to another invalid result with the same error message', function () {
268 | $result1 = ValidationResult::errors(['gigi']);
269 | $result2 = ValidationResult::errors(['gigi']);
270 |
271 | expect($result1->equals($result2))->toBeTruthy();
272 | });
273 |
274 | it('is not equal to an invalid result if valid', function () {
275 | $result1 = ValidationResult::valid(42);
276 | $result2 = ValidationResult::errors(['gigi']);
277 |
278 | expect($result1->equals($result2))->toBeFalsy();
279 | });
280 |
281 | it('is not equal to a valid result if invalid', function () {
282 | $result1 = ValidationResult::errors(['gigi']);
283 | $result2 = ValidationResult::valid(42);
284 |
285 | expect($result1->equals($result2))->toBeFalsy();
286 | });
287 | });
288 |
--------------------------------------------------------------------------------
/spec/Result/functionsSpec.php:
--------------------------------------------------------------------------------
1 | equals(ValidationResult::valid(42)))->toBeTruthy();
20 | });
21 |
22 | it('lifts a function with one argument, handling the valid case', function () {
23 | $f = static function (int $a) {
24 | return $a + 3;
25 | };
26 |
27 | expect((lift($f)(ValidationResult::valid(42)))->equals(ValidationResult::valid(45)))->toBeTruthy();
28 | });
29 |
30 | it('lifts a function with one argument, handling the failure case', function () {
31 | $f = static function (int $a) {
32 | return $a + 3;
33 | };
34 |
35 | expect(
36 | (lift($f)(ValidationResult::errors(['nope'])))->equals(ValidationResult::errors(['nope']))
37 | )->toBeTruthy();
38 | });
39 |
40 | it('lifts a function with two arguments, handling the valid case', function () {
41 | $f = static function ($a, $b) {
42 | return $a + $b;
43 | };
44 |
45 | expect(
46 | (lift($f)(ValidationResult::valid(42), ValidationResult::valid(23)))->equals(ValidationResult::valid(65))
47 | )->toBeTruthy();
48 | });
49 |
50 | it('lifts a function with two arguments, handling the failure of the first argument', function () {
51 | $f = static function ($a, $b) {
52 | return $a + $b;
53 | };
54 |
55 | expect(
56 | (lift($f)(ValidationResult::errors(['nope']), ValidationResult::valid(23)))
57 | ->equals(ValidationResult::errors(['nope']))
58 | )->toBeTruthy();
59 | });
60 |
61 | it('lifts a function with two arguments, handling the failure of the second argument', function () {
62 | $f = static function ($a, $b) {
63 | return $a + $b;
64 | };
65 |
66 | expect(
67 | (lift($f)(ValidationResult::valid(23), ValidationResult::errors(['nope'])))
68 | ->equals(ValidationResult::errors(['nope']))
69 | )->toBeTruthy();
70 | });
71 |
72 | it('lifts a function with two arguments, handling the failure of both arguments', function () {
73 | $f = static function ($a, $b) {
74 | return $a + $b;
75 | };
76 |
77 | expect(
78 | (lift($f)(ValidationResult::errors(['nope1']), ValidationResult::errors(['nope2'])))
79 | ->equals(ValidationResult::errors(['nope1', 'nope2']))
80 | )->toBeTruthy();
81 | });
82 |
83 | it('lifts a function with three arguments, handling the valid case', function () {
84 | $f = static function ($a, $b, $c) {
85 | return $a + $b + $c;
86 | };
87 |
88 | expect(
89 | (lift($f)(ValidationResult::valid(42), ValidationResult::valid(23), ValidationResult::valid(67)))
90 | ->equals(ValidationResult::valid(132))
91 | )->toBeTruthy();
92 | });
93 |
94 | it('lifts a function with three arguments, handling the failure of the first argument', function () {
95 | $f = static function ($a, $b, $c) {
96 | return $a + $b + $c;
97 | };
98 |
99 | expect(
100 | (lift($f)(ValidationResult::errors(['nope']), ValidationResult::valid(23), ValidationResult::valid(67)))
101 | ->equals(ValidationResult::errors(['nope']))
102 | )->toBeTruthy();
103 | });
104 |
105 | it('lifts a function with three arguments, handling the failure of the second argument', function () {
106 | $f = static function ($a, $b, $c) {
107 | return $a + $b + $c;
108 | };
109 |
110 | expect(
111 | (lift($f)(ValidationResult::valid(42), ValidationResult::errors(['nope']), ValidationResult::valid(67)))
112 | ->equals(ValidationResult::errors(['nope']))
113 | )->toBeTruthy();
114 | });
115 |
116 | it('lifts a function with three arguments, handling the failure of the third argument', function () {
117 | $f = static function ($a, $b, $c) {
118 | return $a + $b + $c;
119 | };
120 |
121 | expect(
122 | (lift($f)(ValidationResult::valid(42), ValidationResult::valid(23), ValidationResult::errors(['nope'])))
123 | ->equals(ValidationResult::errors(['nope']))
124 | )->toBeTruthy();
125 | });
126 |
127 | it('lifts a function with three arguments, handling the failure of the first and second argument', function () {
128 | $f = static function ($a, $b, $c) {
129 | return $a + $b + $c;
130 | };
131 |
132 | expect(
133 | (lift($f)(ValidationResult::errors(['nope1']), ValidationResult::errors(['nope2']), ValidationResult::valid(67)))
134 | ->equals(ValidationResult::errors(['nope1', 'nope2']))
135 | )->toBeTruthy();
136 | });
137 |
138 | it('lifts a function with three arguments, handling the failure of the first and third argument', function () {
139 | $f = static function ($a, $b, $c) {
140 | return $a + $b + $c;
141 | };
142 |
143 | expect(
144 | (lift($f)(ValidationResult::errors(['nope1']), ValidationResult::valid(23), ValidationResult::errors(['nope3'])))
145 | ->equals(ValidationResult::errors(['nope1', 'nope3']))
146 | )->toBeTruthy();
147 | });
148 |
149 | it('lifts a function with three arguments, handling the failure of the second and third argument', function () {
150 | $f = static function ($a, $b, $c) {
151 | return $a + $b + $c;
152 | };
153 |
154 | expect(
155 | (lift($f)(ValidationResult::valid(42), ValidationResult::errors(['nope2']), ValidationResult::errors(['nope3'])))
156 | ->equals(ValidationResult::errors(['nope2', 'nope3']))
157 | )->toBeTruthy();
158 | });
159 |
160 | it('lifts a function with three arguments, handling the failure of all arguments', function () {
161 | $f = static function ($a, $b, $c) {
162 | return $a + $b + $c;
163 | };
164 |
165 | expect(
166 | (lift($f)(ValidationResult::errors(['nope1']), ValidationResult::errors(['nope2']), ValidationResult::errors(['nope3'])))
167 | ->equals(ValidationResult::errors(['nope1', 'nope2', 'nope3']))
168 | )->toBeTruthy();
169 | });
170 | });
171 |
172 | describe('do_ function', function () {
173 | it('sums two numbers', function () {
174 | $sumResult = sdo(
175 | static function () {
176 | return ValidationResult::valid(42);
177 | },
178 | static function ($arg) {
179 | return ValidationResult::valid(['first' => $arg, 'second' => 23]);
180 | },
181 | static function ($args) {
182 | return ValidationResult::valid($args['first'] + $args['second']);
183 | }
184 | );
185 |
186 | expect($sumResult->equals(ValidationResult::valid(65)))->toBeTruthy();
187 | });
188 |
189 | it('fails if the first operation fails', function () {
190 | $sumResult = sdo(
191 | static function () {
192 | return ValidationResult::errors(['nope']);
193 | },
194 | static function ($arg) {
195 | return ValidationResult::valid(['first' => $arg, 'second' => 23]);
196 | },
197 | static function ($args) {
198 | return ValidationResult::valid($args['first'] + $args['second']);
199 | }
200 | );
201 |
202 | expect($sumResult->equals(ValidationResult::errors(['nope'])))->toBeTruthy();
203 | });
204 |
205 | it('fails if the second operation fails', function () {
206 | $sumResult = sdo(
207 | static function () {
208 | return ValidationResult::valid(42);
209 | },
210 | static function () {
211 | return ValidationResult::errors(['nope']);
212 | },
213 | static function ($args) {
214 | return ValidationResult::valid($args['first'] + $args['second']);
215 | }
216 | );
217 |
218 | expect($sumResult->equals(ValidationResult::errors(['nope'])))->toBeTruthy();
219 | });
220 |
221 | it('fails if both operation fails with just the first error', function () {
222 | $sumResult = sdo(
223 | static function () {
224 | return ValidationResult::errors(['nope1']);
225 | },
226 | static function () {
227 | return ValidationResult::errors(['nope2']);
228 | },
229 | static function ($args) {
230 | return ValidationResult::valid($args['first'] + $args['second']);
231 | }
232 | );
233 |
234 | expect($sumResult->equals(ValidationResult::errors(['nope1'])))->toBeTruthy();
235 | });
236 | });
237 |
238 | describe('do__ function', function () {
239 | it('sums two numbers', function () {
240 | $sumResult = mdo(
241 | static function () {
242 | return ValidationResult::valid(42);
243 | },
244 | static function () {
245 | return ValidationResult::valid(23);
246 | },
247 | static function ($arg1, $arg2) {
248 | return ValidationResult::valid($arg1 + $arg2);
249 | }
250 | );
251 |
252 | expect($sumResult->equals(ValidationResult::valid(65)))->toBeTruthy();
253 | });
254 |
255 | it('fails if the first operation fails', function () {
256 | $sumResult = mdo(
257 | static function () {
258 | return ValidationResult::errors(['nope']);
259 | },
260 | static function () {
261 | return ValidationResult::valid(23);
262 | },
263 | static function ($arg1, $arg2) {
264 | return ValidationResult::valid($arg1 + $arg2);
265 | }
266 | );
267 |
268 | expect($sumResult->equals(ValidationResult::errors(['nope'])))->toBeTruthy();
269 | });
270 |
271 | it('fails if the second operation fails', function () {
272 | $sumResult = mdo(
273 | static function () {
274 | return ValidationResult::valid(42);
275 | },
276 | static function () {
277 | return ValidationResult::errors(['nope']);
278 | },
279 | static function ($arg1, $arg2) {
280 | return ValidationResult::valid($arg1 + $arg2);
281 | }
282 | );
283 |
284 | expect($sumResult->equals(ValidationResult::errors(['nope'])))->toBeTruthy();
285 | });
286 |
287 | it('fails if both operation fails with just the first error', function () {
288 | $sumResult = mdo(
289 | static function () {
290 | return ValidationResult::errors(['nope1']);
291 | },
292 | static function () {
293 | return ValidationResult::errors(['nope2']);
294 | },
295 | static function ($arg1, $arg2) {
296 | return ValidationResult::valid($arg1 + $arg2);
297 | }
298 | );
299 |
300 | expect($sumResult->equals(ValidationResult::errors(['nope1'])))->toBeTruthy();
301 | });
302 | });
303 |
--------------------------------------------------------------------------------
/spec/Translator/Combinator/CoalesceSpec.php:
--------------------------------------------------------------------------------
1 | translate('gigi'))->toBe('gigi');
15 | });
16 |
17 | it('returns the received string if it receives only null translators', function () {
18 | $translator = Coalesce::withTranslators(null, null, null);
19 |
20 | expect($translator->translate('gigi'))->toBe('gigi');
21 | });
22 |
23 | it('returns the translated string according to the first non-null translator', function () {
24 | $translator1 = ConstantTranslator::withTranslation('bepi');
25 | $translator2 = ConstantTranslator::withTranslation('toni');
26 | $translator = Coalesce::withTranslators(null, $translator1, $translator2);
27 |
28 | expect($translator->translate('gigi'))->toBe('bepi');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/spec/Translator/ConstantTranslatorSpec.php:
--------------------------------------------------------------------------------
1 | translate('gigi'))->toBe('bepi');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/spec/Translator/IdentityTranslatorSpec.php:
--------------------------------------------------------------------------------
1 | translate('gigi'))->toBe('gigi');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/spec/Translator/KeyValueTranslatorSpec.php:
--------------------------------------------------------------------------------
1 | 'bepi']);
12 |
13 | expect($translator->translate('gigi'))->toBe('bepi');
14 | });
15 |
16 | it('returns the received string if it is not in the dictionary', function () {
17 | $translator = KeyValueTranslator::withDictionary(['gigi' => 'bepi']);
18 |
19 | expect($translator->translate('toni'))->toBe('toni');
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/spec/functionsSpec.php:
--------------------------------------------------------------------------------
1 | toBe(42);
25 | });
26 |
27 | it('does not modify a function with one argument', function () {
28 | $f = function (int $a) {
29 | return $a + 3;
30 | };
31 |
32 | expect(curry($f)(2))->toBe(5);
33 | });
34 |
35 | it('curries a function with two arguments', function () {
36 | $f = function (int $a, int $b) {
37 | return $a + $b;
38 | };
39 |
40 | expect(curry($f)(2)(3))->toBe(5);
41 | });
42 |
43 | it('curries a function with three arguments', function () {
44 | $f = function (int $a, int $b, int $c) {
45 | return $a + $b + $c;
46 | };
47 |
48 | expect(curry($f)(2)(3)(4))->toBe(9);
49 | });
50 |
51 | it('curries an object method', function () {
52 | $foo = new class {
53 | public function bar(int $a, int $b): int
54 | {
55 | return $a + $b;
56 | }
57 | };
58 |
59 | $f = [$foo, 'bar'];
60 |
61 | expect(curry($f)(2)(3))->toBe(5);
62 | });
63 |
64 | it('curries a static method', function () {
65 | $f = [Adder::class, 'sum'];
66 |
67 | expect(curry($f)(2)(3))->toBe(5);
68 | });
69 |
70 | it('curries as invokable object', function () {
71 | $f = new class {
72 | public function __invoke(int $a, int $b): int
73 | {
74 | return $a + $b;
75 | }
76 | };
77 |
78 | expect(curry($f)(2)(3))->toBe(5);
79 | });
80 | });
81 |
82 | describe('uncurry function', function () {
83 | it('does not modify a function with no arguments', function () {
84 | $f = function () {
85 | return 42;
86 | };
87 |
88 | expect(uncurry($f)())->toBe(42);
89 | });
90 |
91 | it('does not modify a function with one argument', function () {
92 | $f = function (int $a) {
93 | return $a + 3;
94 | };
95 |
96 | expect(uncurry($f)(2))->toBe(5);
97 | });
98 |
99 | it('uncurries a curried function with two arguments', function () {
100 | $f = function (int $a) {
101 | return function (int $b) use ($a) {
102 | return $a + $b;
103 | };
104 | };
105 |
106 | expect(uncurry($f)(2, 3))->toBe(5);
107 | });
108 |
109 | it('uncurries a curried function with three arguments', function () {
110 | $f = function (int $a) {
111 | return function ($b) use ($a) {
112 | return function (int $c) use ($a, $b) {
113 | return $a + $b + $c;
114 | };
115 | };
116 | };
117 |
118 | expect(uncurry($f)(2, 3, 4))->toBe(9);
119 | });
120 | });
121 |
--------------------------------------------------------------------------------
/src/Basic/Compare.php:
--------------------------------------------------------------------------------
1 | comparisonBasis = $comparisonBasis;
31 | $this->errorFormatter = $errorFormatter;
32 | }
33 |
34 | /**
35 | * @template B
36 | * @param B $comparisonBasis
37 | * @return self
38 | */
39 | public static function withBound($comparisonBasis): self
40 | {
41 | return self::withBoundAndFormatter(
42 | $comparisonBasis,
43 | /**
44 | * @param B $comparisonBasis
45 | * @param B $data
46 | * @return string[]
47 | */
48 | function ($comparisonBasis, $data): array {
49 | /** @var string $message */
50 | $message = static::MESSAGE;
51 |
52 | return [$message];
53 | }
54 | );
55 | }
56 |
57 | /**
58 | * @template B
59 | * @template F
60 | * @param B $comparisonBasis
61 | * @param callable(B, B): F[] $errorFormatter
62 | * @return self
63 | */
64 | public static function withBoundAndFormatter($comparisonBasis, callable $errorFormatter): self
65 | {
66 | /** @psalm-suppress UnsafeInstantiation */
67 | return new static($comparisonBasis, $errorFormatter);
68 | }
69 |
70 | /**
71 | * @template B
72 | * @param B $comparisonBasis
73 | * @param Translator $translator
74 | * @return Compare
75 | */
76 | public static function withBoundAndTranslator($comparisonBasis, Translator $translator): self
77 | {
78 | return self::withBoundAndFormatter(
79 | $comparisonBasis,
80 | /**
81 | * @param B $comparisonBasis
82 | * @param B $data
83 | * @return string[]
84 | */
85 | function ($comparisonBasis, $data) use ($translator): array {
86 | /** @var string $message */
87 | $message = static::MESSAGE;
88 |
89 | return [$translator->translate($message)];
90 | }
91 | );
92 | }
93 |
94 | /**
95 | * @param A $data
96 | * @return ValidationResult
97 | */
98 | abstract public function validate($data, array $context = []): ValidationResult;
99 |
100 | /**
101 | * @param callable(A, A): bool $assertion
102 | * @param A $data
103 | * @return ValidationResult
104 | */
105 | protected function validateAssertion(callable $assertion, $data, array $context = []): ValidationResult
106 | {
107 | $comparisonBasis = $this->comparisonBasis;
108 | $errorFormatter = $this->errorFormatter;
109 |
110 | return IsAsAsserted::withAssertionAndErrorFormatter(
111 | /**
112 | * @param A $data
113 | */
114 | function ($data) use ($comparisonBasis, $assertion): bool {
115 | return $assertion($comparisonBasis, $data);
116 | },
117 | /**
118 | * @param A $data
119 | */
120 | function ($data) use ($comparisonBasis, $errorFormatter): array {
121 | return $errorFormatter($comparisonBasis, $data);
122 | }
123 | )->validate($data, $context);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Basic/ComposingAssertion.php:
--------------------------------------------------------------------------------
1 | errorFormatter = $errorFormatter;
29 | }
30 |
31 | /**
32 | * @template B
33 | * @template F
34 | * @param callable(B): F[] $errorFormatter
35 | * @return self
36 | */
37 | public static function withFormatter(callable $errorFormatter): self
38 | {
39 | /** @psalm-suppress UnsafeInstantiation */
40 | return new static($errorFormatter);
41 | }
42 |
43 | /**
44 | * @template B
45 | * @return self
46 | */
47 | public static function withTranslator(Translator $translator): self
48 | {
49 | /** @psalm-suppress UnsafeInstantiation */
50 | return new static(
51 | /**
52 | * @param B $data
53 | * @return string[]
54 | */
55 | function ($data) use ($translator): array {
56 | /** @var string $message */
57 | $message = static::MESSAGE;
58 |
59 | return [$translator->translate($message)];
60 | }
61 | );
62 | }
63 |
64 | /**
65 | * @param A $data
66 | * @return ValidationResult
67 | */
68 | abstract public function validate($data, array $context = []): ValidationResult;
69 |
70 | /**
71 | * @param callable(A): bool $assertion
72 | * @param A $data
73 | * @return ValidationResult
74 | */
75 | protected function validateAssertion(callable $assertion, $data, array $context = []): ValidationResult
76 | {
77 | return IsAsAsserted::withAssertionAndErrorFormatter(
78 | $assertion,
79 | is_callable($this->errorFormatter) ?
80 | $this->errorFormatter :
81 | /**
82 | * @param A $data
83 | * @return string[]
84 | */
85 | function ($data): array {
86 | /** @var string $message */
87 | $message = static::MESSAGE;
88 |
89 | return [$message];
90 | }
91 | )->validate($data, $context);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Basic/Errors.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class Errors extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'errors.invalid-data';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | $alwaysFalse =
27 | /**
28 | * @param A $data
29 | * @return false
30 | */
31 | function ($data): bool {
32 | return false;
33 | };
34 |
35 | return $this->validateAssertion($alwaysFalse, $data, $context);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Basic/HasKey.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | final class HasKey implements Validation
20 | {
21 | public const MISSING_KEY = 'has-key.missing-key';
22 |
23 | /** @var array-key */
24 | private $key;
25 |
26 | /** @var callable(array-key, A): E[] */
27 | private $errorFormatter;
28 |
29 | /**
30 | * @param array-key $key
31 | * @param null|callable(array-key, A): E[] $errorFormatter
32 | */
33 | private function __construct($key, ?callable $errorFormatter = null)
34 | {
35 | $this->key = $key;
36 |
37 | /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
38 | $this->errorFormatter = is_callable($errorFormatter) ?
39 | $errorFormatter :
40 | /**
41 | * @param array-key $key
42 | * @param A $data
43 | * @return string[]
44 | */
45 | function ($key, array $data): array {
46 | return [self::MISSING_KEY];
47 | };
48 | }
49 |
50 | /**
51 | * @param array-key $key
52 | */
53 | public static function withKey(string $key): self
54 | {
55 | return new self($key);
56 | }
57 |
58 | /**
59 | * @template F
60 | * @param array-key $key
61 | * @param callable(array-key, array): F[] $errorFormatter
62 | * @return self
63 | */
64 | public static function withKeyAndFormatter($key, callable $errorFormatter): self
65 | {
66 | return new self($key, $errorFormatter);
67 | }
68 |
69 | /**
70 | * @param array-key $key
71 | * @param Translator $translator
72 | * @return self
73 | * @psalm-suppress MixedReturnTypeCoercion
74 | */
75 | public static function withKeyAndTranslator($key, Translator $translator): self
76 | {
77 | return new self(
78 | $key,
79 | /**
80 | * @param array-key $key
81 | * @param array $data
82 | * @return string[]
83 | */
84 | function ($key, array $data) use ($translator): array {
85 | return [$translator->translate(self::MISSING_KEY)];
86 | }
87 | );
88 | }
89 |
90 | /**
91 | * @param A $data
92 | * @return ValidationResult
93 | * @psalm-suppress MoreSpecificImplementedParamType
94 | */
95 | public function validate($data, array $context = []): ValidationResult
96 | {
97 | if (! array_key_exists($this->key, $data)) {
98 | /** @var ValidationResult $ret */
99 | $ret = ValidationResult::errors(($this->errorFormatter)($this->key, $data));
100 |
101 | return $ret;
102 | }
103 |
104 | return ValidationResult::valid($data);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Basic/HasNotKey.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | final class HasNotKey implements Validation
19 | {
20 | public const PRESENT_KEY = 'has-not-key.present-key';
21 |
22 | /** @var array-key */
23 | private $key;
24 |
25 | /** @var callable(array-key, A): E[] */
26 | private $errorFormatter;
27 |
28 | /**
29 | * @param array-key $key
30 | * @param null|callable(array-key, A): E[] $errorFormatter
31 | */
32 | private function __construct($key, ?callable $errorFormatter = null)
33 | {
34 | $this->key = $key;
35 |
36 | /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
37 | $this->errorFormatter = is_callable($errorFormatter) ?
38 | $errorFormatter :
39 | /**
40 | * @param array-key $key
41 | * @param A $data
42 | * @return string[]
43 | */
44 | function (string $key, $data): array {
45 | return [self::PRESENT_KEY];
46 | };
47 | }
48 |
49 | /**
50 | * @param array-key $key
51 | */
52 | public static function withKey(string $key): self
53 | {
54 | return new self($key);
55 | }
56 |
57 | /**
58 | * @template B of array
59 | * @param array-key $key
60 | * @param callable(array-key, B): E[] $errorFormatter
61 | */
62 | public static function withKeyAndFormatter(string $key, callable $errorFormatter): self
63 | {
64 | return new self($key, $errorFormatter);
65 | }
66 |
67 | /**
68 | * @param array-key $key
69 | * @param Translator $translator
70 | * @return self
71 | * @psalm-suppress MixedReturnTypeCoercion
72 | */
73 | public static function withKeyAndTranslator($key, Translator $translator): self
74 | {
75 | return new self(
76 | $key,
77 | /**
78 | * @param array-key $key
79 | * @param array $data
80 | * @return string[]
81 | */
82 | function (string $key, $data) use ($translator): array {
83 | return [$translator->translate(self::PRESENT_KEY)];
84 | }
85 | );
86 | }
87 |
88 | /**
89 | * @param A $data
90 | * @return ValidationResult
91 | * @psalm-suppress MoreSpecificImplementedParamType
92 | */
93 | public function validate($data, array $context = []): ValidationResult
94 | {
95 | if (array_key_exists($this->key, $data)) {
96 | /** @var ValidationResult $ret */
97 | $ret = ValidationResult::errors(($this->errorFormatter)($this->key, $data));
98 |
99 | return $ret;
100 | }
101 |
102 | return ValidationResult::valid($data);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Basic/InArray.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | final class InArray implements Validation
20 | {
21 | public const NOT_IN_ARRAY = 'in-array.not-in-array';
22 |
23 | /** @var array */
24 | private $values;
25 |
26 | /** @var callable(array, A): E[] */
27 | private $errorFormatter;
28 |
29 | /**
30 | * @param null|callable(array, A): E[] $errorFormatter
31 | */
32 | private function __construct(array $values, ?callable $errorFormatter = null)
33 | {
34 | $this->values = $values;
35 |
36 | /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
37 | $this->errorFormatter = is_callable($errorFormatter) ?
38 | $errorFormatter :
39 | /**
40 | * @param A $data
41 | * @return string[]
42 | */
43 | function (array $values, $data): array {
44 | return [self::NOT_IN_ARRAY];
45 | };
46 | }
47 |
48 | /**
49 | * @template B
50 | * @return self
51 | * @psalm-suppress MixedReturnTypeCoercion
52 | */
53 | public static function withValues(array $values): self
54 | {
55 | return new self($values);
56 | }
57 |
58 | /**
59 | * @template F
60 | * @template B
61 | * @param callable(array, B): F[] $errorFormatter
62 | * @return self
63 | */
64 | public static function withValuesAndFormatter(array $values, callable $errorFormatter): self
65 | {
66 | return new self($values, $errorFormatter);
67 | }
68 |
69 | /**
70 | * @template B
71 | * @param array $values
72 | * @return self
73 | * @psalm-suppress MixedReturnTypeCoercion
74 | */
75 | public static function withValuesAndTranslator(array $values, Translator $translator): self
76 | {
77 | return new self(
78 | $values,
79 | /**
80 | * @param A $data
81 | * @return string[]
82 | */
83 | function (array $values, $data) use ($translator): array {
84 | return [$translator->translate(self::NOT_IN_ARRAY)];
85 | }
86 | );
87 | }
88 |
89 | /**
90 | * @param A $data
91 | * @return ValidationResult
92 | */
93 | public function validate($data, array $context = []): ValidationResult
94 | {
95 | if (! in_array($data, $this->values, true)) {
96 | return ValidationResult::errors(($this->errorFormatter)($this->values, $data));
97 | }
98 |
99 | return ValidationResult::valid($data);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Basic/IsArray.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsArray extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-array.no-an-array';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_array', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsAsAsserted.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | final class IsAsAsserted implements Validation
19 | {
20 | public const NOT_AS_ASSERTED = 'is-as-asserted.not-as-asserted';
21 |
22 | /** @var callable(A): bool */
23 | private $assertion;
24 |
25 | /** @var callable(A): E[] */
26 | private $errorFormatter;
27 |
28 | /**
29 | * @param callable(A): bool $assertion
30 | * @param null|callable(A): E[] $errorFormatter
31 | */
32 | private function __construct(callable $assertion, ?callable $errorFormatter = null)
33 | {
34 | $this->assertion = $assertion;
35 |
36 | /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
37 | $this->errorFormatter = is_callable($errorFormatter) ?
38 | $errorFormatter :
39 | /**
40 | * @param A $data
41 | * @return string[]
42 | */
43 | function ($data): array {
44 | return [self::NOT_AS_ASSERTED];
45 | };
46 | }
47 |
48 | /**
49 | * @template B
50 | * @param callable(B): bool $assertion
51 | * @return self
52 | * @psalm-suppress MixedReturnTypeCoercion
53 | */
54 | public static function withAssertion(callable $assertion): self
55 | {
56 | return new self($assertion);
57 | }
58 |
59 | /**
60 | * @template B
61 | * @param callable(B): bool $assertion
62 | * @param callable(B): E[] $errorFormatter
63 | * @return self
64 | */
65 | public static function withAssertionAndErrorFormatter(callable $assertion, callable $errorFormatter): self
66 | {
67 | return new self($assertion, $errorFormatter);
68 | }
69 |
70 | /**
71 | * @template B
72 | * @param callable(B): bool $assertion
73 | * @param Translator $translator
74 | * @return self
75 | * @psalm-suppress MixedReturnTypeCoercion
76 | */
77 | public static function withAssertionAndTranslator(callable $assertion, Translator $translator): self
78 | {
79 | return new self(
80 | $assertion,
81 | /**
82 | * @param A $data
83 | * @return string[]
84 | */
85 | function ($data) use ($translator): array {
86 | return [$translator->translate(self::NOT_AS_ASSERTED)];
87 | }
88 | );
89 | }
90 |
91 | /**
92 | * @param A $data
93 | * @return ValidationResult
94 | */
95 | public function validate($data, array $context = []): ValidationResult
96 | {
97 | if (! ($this->assertion)($data)) {
98 | return ValidationResult::errors(($this->errorFormatter)($data));
99 | }
100 |
101 | return ValidationResult::valid($data);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Basic/IsBool.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsBool extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-bool.not-a-bool';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_bool', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsCallable.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsCallable extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-callable.not-a-callable';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_callable', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsFloat.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsFloat extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-float.not-a-float';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_float', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsGreaterThan.php:
--------------------------------------------------------------------------------
1 |
16 | * @implements Validation
17 | */
18 | final class IsGreaterThan extends Compare implements Validation
19 | {
20 | public const MESSAGE = 'is-greater-than.not-greater-than';
21 |
22 | /**
23 | * @param A $data
24 | * @return ValidationResult
25 | */
26 | public function validate($data, array $context = []): ValidationResult
27 | {
28 | return $this->validateAssertion(
29 | /**
30 | * @param A $bound
31 | * @param A $data
32 | */
33 | function ($bound, $data): bool {
34 | return $data > $bound;
35 | },
36 | $data,
37 | $context
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Basic/IsInstanceOf.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | final class IsInstanceOf implements Validation
19 | {
20 | public const NOT_AN_INSTANCE = 'is-instance-of.not-an-instance';
21 |
22 | /**
23 | * @var class-string
24 | */
25 | private $className;
26 |
27 | /**
28 | * @var callable(class-string, A): E[]
29 | */
30 | private $errorFormatter;
31 |
32 | /**
33 | * @param class-string $className
34 | * @param null|callable(class-string, A): E[] $errorFormatter
35 | */
36 | private function __construct(string $className, ?callable $errorFormatter = null)
37 | {
38 | $this->className = $className;
39 |
40 | /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
41 | $this->errorFormatter = is_callable($errorFormatter) ?
42 | $errorFormatter :
43 | /**
44 | * @param class-string $className
45 | * @param A $data
46 | * @return string[]
47 | */
48 | function (string $className, $data): array {
49 | return [self::NOT_AN_INSTANCE];
50 | };
51 | }
52 |
53 | /**
54 | * @template B
55 | * @param class-string $className
56 | * @return self
57 | * @psalm-suppress MixedReturnTypeCoercion
58 | */
59 | public static function withClassName(string $className): self
60 | {
61 | return new self($className);
62 | }
63 |
64 | /**
65 | * @template F
66 | * @template B
67 | * @param class-string $className
68 | * @param callable(class-string, B): F[] $errorFormatter
69 | * @return self
70 | */
71 | public static function withClassNameAndFormatter(string $className, callable $errorFormatter): self
72 | {
73 | return new self($className, $errorFormatter);
74 | }
75 |
76 | /**
77 | * @template B
78 | * @param class-string $className
79 | * @param Translator $translator
80 | * @return self
81 | * @psalm-suppress MixedReturnTypeCoercion
82 | */
83 | public static function withClassNameAndTranslator(string $className, Translator $translator): self
84 | {
85 | return new self(
86 | $className,
87 | /**
88 | * @param class-string $className
89 | * @param A $data
90 | * @return string[]
91 | */
92 | function (string $className, $data) use ($translator): array {
93 | return [$translator->translate(self::NOT_AN_INSTANCE)];
94 | }
95 | );
96 | }
97 |
98 | /**
99 | * @param A $data
100 | * @return ValidationResult
101 | */
102 | public function validate($data, array $context = []): ValidationResult
103 | {
104 | if (! $data instanceof $this->className) {
105 | return ValidationResult::errors(($this->errorFormatter)($this->className, $data));
106 | }
107 |
108 | return ValidationResult::valid($data);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Basic/IsInteger.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsInteger extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-integer.not-an-integer';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_int', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsIterable.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsIterable extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-iterable.not-an-iterable';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_iterable', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsLessThan.php:
--------------------------------------------------------------------------------
1 |
15 | * @implements Validation
16 | */
17 | final class IsLessThan extends Compare implements Validation
18 | {
19 | public const MESSAGE = 'is-less-than.not-less-than';
20 |
21 | /**
22 | * @param A $data
23 | * @return ValidationResult
24 | */
25 | public function validate($data, array $context = []): ValidationResult
26 | {
27 | return $this->validateAssertion(
28 | /**
29 | * @param A $bound
30 | * @param A $data
31 | */
32 | function ($bound, $data): bool {
33 | return $data < $bound;
34 | },
35 | $data,
36 | $context
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Basic/IsNotNull.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsNotNull extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-not-null.not-not-null';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion(
27 | /**
28 | * @param A $data
29 | */
30 | function ($data): bool {
31 | return null !== $data;
32 | },
33 | $data,
34 | $context
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Basic/IsNull.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsNull extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-null.not-null';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion(
27 | /**
28 | * @param A $data
29 | */
30 | function ($data): bool {
31 | return null === $data;
32 | },
33 | $data,
34 | $context
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Basic/IsNumeric.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsNumeric extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-numeric.not-numeric';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_numeric', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsObject.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsObject extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-object.not-an-object';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_object', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsResource.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsResource extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-resource.not-a-resource';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_resource', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/IsString.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class IsString extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'is-string.not-a-string';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion('is_string', $data, $context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Basic/NonEmpty.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements Validation
15 | */
16 | final class NonEmpty extends ComposingAssertion implements Validation
17 | {
18 | public const MESSAGE = 'non-empty.empty';
19 |
20 | /**
21 | * @param A $data
22 | * @return ValidationResult
23 | */
24 | public function validate($data, array $context = []): ValidationResult
25 | {
26 | return $this->validateAssertion(
27 | /**
28 | * @param A $data
29 | */
30 | function ($data): bool {
31 | return !empty($data);
32 | },
33 | $data,
34 | $context
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Basic/Regex.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | final class Regex implements Validation
19 | {
20 | public const MESSAGE = 'regex.match-failed';
21 |
22 | /** @var string */
23 | private $pattern;
24 |
25 | /** @var callable(string, string): E[] */
26 | private $errorFormatter;
27 |
28 | /**
29 | * @param null|callable(string, string): E[] $errorFormatter
30 | */
31 | private function __construct(string $pattern, ?callable $errorFormatter = null)
32 | {
33 | $this->pattern = $pattern;
34 |
35 | /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
36 | $this->errorFormatter = is_callable($errorFormatter) ?
37 | $errorFormatter :
38 | /**
39 | * @return string[]
40 | */
41 | function (string $pattern, string $data): array {
42 | return [self::MESSAGE];
43 | };
44 | }
45 |
46 | /**
47 | * @return self
48 | * @psalm-suppress MixedReturnTypeCoercion
49 | */
50 | public static function withPattern(string $pattern): self
51 | {
52 | return new self($pattern);
53 | }
54 |
55 | /**
56 | * @template F
57 | * @param callable(string, string): F[] $errorFormatter
58 | * @return self
59 | */
60 | public static function withPatternAndFormatter(string $pattern, callable $errorFormatter): self
61 | {
62 | return new self($pattern, $errorFormatter);
63 | }
64 |
65 | /**
66 | * @return self
67 | * @psalm-suppress MixedReturnTypeCoercion
68 | */
69 | public static function withPatternAndTranslator(string $pattern, Translator $translator): self
70 | {
71 | return new self(
72 | $pattern,
73 | /**
74 | * @return string[]
75 | */
76 | function (string $pattern, string $data) use ($translator): array {
77 | return [$translator->translate(self::MESSAGE)];
78 | }
79 | );
80 | }
81 |
82 | /**
83 | * @param string $data
84 | * @return ValidationResult
85 | */
86 | public function validate($data, array $context = []): ValidationResult
87 | {
88 | $match = preg_match($this->pattern, $data);
89 |
90 | if (false === $match || 0 === $match) {
91 | /** @var ValidationResult $ret */
92 | $ret = ValidationResult::errors(($this->errorFormatter)($this->pattern, $data));
93 |
94 | return $ret;
95 | }
96 |
97 | return ValidationResult::valid($data);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Basic/Valid.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | final class Valid implements Validation
16 | {
17 | /**
18 | * @param A $data
19 | * @return ValidationResult
20 | */
21 | public function validate($data, array $context = []): ValidationResult
22 | {
23 | return ValidationResult::valid($data);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Combinator/All.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | final class All implements Validation
21 | {
22 | /** @var Validation[] */
23 | private $validations;
24 |
25 | /**
26 | * @var callable(E[], E[]): E[]
27 | */
28 | private $errorFormatter;
29 |
30 | /**
31 | * @param Validation[] $validations
32 | * @param null|callable(E[], E[]): E[] $errorFormatter
33 | * @throws InvalidArgumentException
34 | */
35 | private function __construct(array $validations, ?callable $errorFormatter = null)
36 | {
37 | Assert::allIsInstanceOf($validations, Validation::class);
38 |
39 | $this->validations = $validations;
40 | $this->errorFormatter = is_callable($errorFormatter) ?
41 | $errorFormatter :
42 | 'array_merge';
43 | }
44 |
45 | /**
46 | * @template C
47 | * @template F
48 | * @template D
49 | * @param Validation[] $validations
50 | * @return self
51 | * @throws InvalidArgumentException
52 | */
53 | public static function validations(array $validations): self
54 | {
55 | return new self($validations);
56 | }
57 |
58 | /**
59 | * @template C
60 | * @template F
61 | * @template D
62 | * @param Validation[] $validations
63 | * @param callable(F[], F[]): F[] $errorFormatter
64 | * @return self
65 | * @throws InvalidArgumentException
66 | */
67 | public static function validationsWithFormatter(array $validations, callable $errorFormatter)
68 | {
69 | return new self($validations, $errorFormatter);
70 | }
71 |
72 | /**
73 | * @param A $data
74 | * @param array $context
75 | * @return ValidationResult
76 | */
77 | public function validate($data, array $context = []): ValidationResult
78 | {
79 | /** @var ValidationResult $result */
80 | $result = ValidationResult::valid($data);
81 |
82 | foreach ($this->validations as $validation) {
83 | $result = $result->join(
84 | $validation->validate($data, $context),
85 | /**
86 | * @param B $a
87 | * @param B $b
88 | * @return B
89 | */
90 | function ($a, $b) {
91 | return $a;
92 | },
93 | $this->errorFormatter
94 | );
95 | }
96 |
97 | return $result;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Combinator/Any.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | final class Any implements Validation
21 | {
22 | public const NOT_EVEN_ONE = 'any.not-even-one';
23 |
24 | /** @var Validation[] */
25 | private $validations;
26 |
27 | /** @var callable(E[]): E[][] */
28 | private $errorFormatter;
29 |
30 | /**
31 | * @param Validation[] $validations
32 | * @param null|callable(E[]): E[][] $errorFormatter
33 | * @throws InvalidArgumentException
34 | */
35 | private function __construct(array $validations, ?callable $errorFormatter = null)
36 | {
37 | Assert::allIsInstanceOf($validations, Validation::class);
38 |
39 | $this->validations = $validations;
40 | $this->errorFormatter = is_callable($errorFormatter) ?
41 | $errorFormatter :
42 | /**
43 | * @param E[] $messages
44 | * @return E[][]
45 | */
46 | function (array $messages): array {
47 | return [
48 | self::NOT_EVEN_ONE => $messages
49 | ];
50 | };
51 | }
52 |
53 | /**
54 | * @template C
55 | * @template F
56 | * @param Validation[] $validations
57 | * @return self
58 | * @throws InvalidArgumentException
59 | */
60 | public static function validations(array $validations): self
61 | {
62 | return new self($validations);
63 | }
64 |
65 | /**
66 | * @template C
67 | * @template F
68 | * @param Validation[] $validations
69 | * @param callable(F[]): F[][] $errorFormatter
70 | * @return self
71 | * @throws InvalidArgumentException
72 | */
73 | public static function validationsWithFormatter(array $validations, callable $errorFormatter): self
74 | {
75 | return new self($validations, $errorFormatter);
76 | }
77 |
78 | /**
79 | * @template C
80 | * @template F
81 | * @param Validation[] $validations
82 | * @param Translator $translator
83 | * @return self
84 | * @throws InvalidArgumentException
85 | */
86 | public static function validationsWithTranslator(array $validations, Translator $translator): self
87 | {
88 | return new self(
89 | $validations,
90 | /**
91 | * @param F[] $messages
92 | * @return F[][]
93 | */
94 | function (array $messages) use ($translator): array {
95 | return [
96 | $translator->translate(self::NOT_EVEN_ONE) => $messages
97 | ];
98 | }
99 | );
100 | }
101 |
102 | /**
103 | * @param A $data
104 | * @param array $context
105 | * @return ValidationResult
106 | */
107 | public function validate($data, array $context = []): ValidationResult
108 | {
109 | /** @var ValidationResult $result */
110 | $result = ValidationResult::errors([]);
111 |
112 | foreach ($this->validations as $validation) {
113 | $result = $result->meet(
114 | $validation->validate($data, $context),
115 | /**
116 | * @param A $x
117 | * @param A $y
118 | * @return A
119 | */
120 | function ($x, $y) {
121 | return $x;
122 | },
123 | /**
124 | * @param A $x
125 | * @return A
126 | */
127 | function ($x) {
128 | return $x;
129 | },
130 | /**
131 | * @param A $x
132 | * @return A
133 | */
134 | function ($x) {
135 | return $x;
136 | },
137 | 'array_merge'
138 | );
139 | }
140 |
141 | return $result
142 | ->mapErrors($this->errorFormatter)
143 | ->map(
144 | function () use ($data) {
145 | return $data;
146 | }
147 | );
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/Combinator/AnyElement.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | final class AnyElement implements Validation
18 | {
19 | /** @var Validation */
20 | private $elementValidation;
21 |
22 | /** @var callable(array-key, E[][], E[]): E[][] */
23 | private $errorFormatter;
24 |
25 | /**
26 | * @param Validation $validation
27 | * @param null|callable(array-key, E[][], E[]): E[][] $errorFormatter
28 | */
29 | private function __construct(Validation $validation, ?callable $errorFormatter = null)
30 | {
31 | $this->elementValidation = $validation;
32 | $this->errorFormatter = is_callable($errorFormatter) ?
33 | $errorFormatter :
34 | /**
35 | * @param array-key $key
36 | * @param E[][] $resultMessages
37 | * @param E[] $validationMessages
38 | * @return E[][]
39 | */
40 | function ($key, array $resultMessages, array $validationMessages): array {
41 | $resultMessages[$key] = $validationMessages;
42 |
43 | return $resultMessages;
44 | };
45 | }
46 |
47 | /**
48 | * @template C
49 | * @template F
50 | * @param Validation $validation
51 | * @return self
52 | */
53 | public static function validation(Validation $validation): self
54 | {
55 | return new self($validation);
56 | }
57 |
58 | /**
59 | * @template C
60 | * @template F
61 | * @param Validation $validation
62 | * @param callable(array-key, F[][], F[]): F[][] $errorFormatter
63 | * @return self
64 | */
65 | public static function validationWithFormatter(Validation $validation, callable $errorFormatter): self
66 | {
67 | return new self($validation, $errorFormatter);
68 | }
69 |
70 | /**
71 | * @param A[] $data
72 | * @param array $context
73 | * @return ValidationResult
74 | */
75 | public function validate($data, array $context = []): ValidationResult
76 | {
77 | $errorFormatter = $this->errorFormatter;
78 |
79 | /** @var ValidationResult $result */
80 | $result = ValidationResult::errors([]);
81 |
82 | foreach ($data as $key => $element) {
83 | $result = $result->meet(
84 | $this->elementValidation->validate($element, $context),
85 | /**
86 | * @param A[] $x
87 | * @param A $y
88 | * @return A[]
89 | */
90 | function (array $x, $y) {
91 | return $x;
92 | },
93 | /**
94 | * @param A[] $x
95 | * @return A[]
96 | */
97 | function (array $x) {
98 | return $x;
99 | },
100 | /**
101 | * @param A $x
102 | * @return A[]
103 | */
104 | function ($x) {
105 | return [$x];
106 | },
107 | /**
108 | * @param E[][] $resultMessages
109 | * @param E[] $validationMessages
110 | * @return E[][]
111 | */
112 | function (array $resultMessages, array $validationMessages) use ($key, $errorFormatter) {
113 | return $errorFormatter($key, $resultMessages, $validationMessages);
114 | }
115 | );
116 | }
117 |
118 | return $result->map(
119 | function () use ($data) {
120 | return $data;
121 | }
122 | );
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Combinator/Apply.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | final class Apply implements Validation
18 | {
19 | /** @var Validation */
20 | private $validation;
21 |
22 | /**
23 | * @var ValidationResult
24 | */
25 | private $validationFunction;
26 |
27 | /**
28 | * @param Validation $validation
29 | * @param ValidationResult $validationFunction
30 | */
31 | private function __construct(Validation $validation, ValidationResult $validationFunction)
32 | {
33 | $this->validation = $validation;
34 | $this->validationFunction = $validationFunction;
35 | }
36 |
37 | /**
38 | * @template D
39 | * @template H
40 | * @template F
41 | * @template G
42 | * @param Validation $validation
43 | * @param ValidationResult $validationFunction
44 | * @return self
45 | */
46 | public static function to(Validation $validation, ValidationResult $validationFunction): self
47 | {
48 | return new self($validation, $validationFunction);
49 | }
50 |
51 | /**
52 | * @param A $data
53 | * @param array $context
54 | * @return ValidationResult
55 | */
56 | public function validate($data, array $context = []): ValidationResult
57 | {
58 | return $this->validation->validate($data, $context)->apply($this->validationFunction);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Combinator/Associative.php:
--------------------------------------------------------------------------------
1 | validations = $validations;
28 | }
29 |
30 | /**
31 | * @param Validation[] $validations
32 | * @return self
33 | */
34 | public static function validations(array $validations)
35 | {
36 | return new self($validations);
37 | }
38 |
39 | public function validate($data, array $context = []): ValidationResult
40 | {
41 | $wholeValidation = Sequence::validations([
42 | new IsArray(),
43 | All::validations(array_map(
44 | /**
45 | * @param array-key $key
46 | * @param Validation $validation
47 | * @return Validation
48 | */
49 | static function ($key, Validation $validation) {
50 | return MapErrors::to(
51 | Sequence::validations([
52 | HasKey::withKey($key),
53 | Focus::on(
54 | /**
55 | * @param array $wholeData
56 | * @return mixed
57 | */
58 | static function (array $wholeData) use ($key) {
59 | return $wholeData[$key];
60 | },
61 | $validation
62 | )
63 | ]),
64 | /**
65 | * @param array $messages
66 | * @return array
67 | */
68 | static function (array $messages) use ($key): array {
69 | return [$key => $messages];
70 | }
71 | );
72 | },
73 | array_keys($this->validations),
74 | $this->validations
75 | ))
76 | ]);
77 |
78 | return $wholeValidation->validate($data, $context);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Combinator/Bind.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | final class Bind implements Validation
18 | {
19 | /** @var Validation */
20 | private $validation;
21 |
22 | /**
23 | * @var callable(B): ValidationResult
24 | */
25 | private $function;
26 |
27 | /**
28 | * @param Validation $validation
29 | * @param callable(B): ValidationResult $function
30 | */
31 | private function __construct(Validation $validation, callable $function)
32 | {
33 | $this->validation = $validation;
34 | $this->function = $function;
35 | }
36 |
37 | /**
38 | * @template D
39 | * @template H
40 | * @template F
41 | * @template G
42 | * @param Validation $validation
43 | * @param callable(F): ValidationResult $function
44 | * @return self
45 | */
46 | public static function to(Validation $validation, callable $function): self
47 | {
48 | return new self($validation, $function);
49 | }
50 |
51 | /**
52 | * @param A $data
53 | * @param array $context
54 | * @return ValidationResult
55 | */
56 | public function validate($data, array $context = []): ValidationResult
57 | {
58 | return $this->validation->validate($data, $context)->bind($this->function);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Combinator/EveryElement.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | final class EveryElement implements Validation
19 | {
20 | /** @var Validation */
21 | private $elementValidation;
22 |
23 | /** @var callable callable(array-key, E[][], E[]): E[][] */
24 | private $errorFormatter;
25 |
26 | /**
27 | * @param Validation $validation
28 | * @param null|callable(array-key, E[][], E[]): E[][] $errorFormatter
29 | */
30 | private function __construct(Validation $validation, ?callable $errorFormatter = null)
31 | {
32 | $this->elementValidation = $validation;
33 | $this->errorFormatter = is_callable($errorFormatter) ?
34 | $errorFormatter :
35 | /**
36 | * @param array-key $key
37 | * @param E[][] $resultMessages
38 | * @param E[] $validationMessages
39 | * @return E[][]
40 | */
41 | function ($key, array $resultMessages, array $validationMessages): array {
42 | $resultMessages[$key] = $validationMessages;
43 |
44 | return $resultMessages;
45 | };
46 | }
47 |
48 | /**
49 | * @template C
50 | * @template F
51 | * @template D
52 | * @param Validation $validation
53 | * @return self
54 | */
55 | public static function validation(Validation $validation): self
56 | {
57 | return new self($validation);
58 | }
59 |
60 | /**
61 | * @template C
62 | * @template F
63 | * @template D
64 | * @param Validation $validation
65 | * @param callable(array-key, F[][], F[]): F[][] $errorFormatter
66 | * @return self
67 | */
68 | public static function validationWithFormatter(Validation $validation, callable $errorFormatter): self
69 | {
70 | return new self($validation, $errorFormatter);
71 | }
72 |
73 | /**
74 | * @param A[] $data
75 | * @param array $context
76 | * @return ValidationResult
77 | */
78 | public function validate($data, array $context = []): ValidationResult
79 | {
80 | /** @var callable(array-key, E[][], E[]): E[][] $errorFormatter */
81 | $errorFormatter = $this->errorFormatter;
82 |
83 | /** @var ValidationResult $result */
84 | $result = ValidationResult::valid($data);
85 |
86 | foreach ($data as $key => $element) {
87 | $result = $result->join(
88 | $this->elementValidation->validate($element, $context),
89 | /**
90 | * @psalm-param B[] $result
91 | * @psalm-param B $next
92 | * @return B[]
93 | */
94 | function (array $result, $next) {
95 | return $result;
96 | },
97 | /**
98 | * @param E[][] $resultMessages
99 | * @param E[] $validationMessages
100 | * @return E[][]
101 | */
102 | function (array $resultMessages, array $validationMessages) use ($key, $errorFormatter): array {
103 | return $errorFormatter($key, $resultMessages, $validationMessages);
104 | }
105 | );
106 | }
107 |
108 | return $result;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Combinator/Focus.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | final class Focus implements Validation
17 | {
18 | /** @var callable(A): B */
19 | private $focus;
20 |
21 | /** @var Validation */
22 | private $validation;
23 |
24 | /**
25 | * @param callable(A): B $focus
26 | * @param Validation $validation
27 | */
28 | private function __construct(callable $focus, Validation $validation)
29 | {
30 | $this->focus = $focus;
31 | $this->validation = $validation;
32 | }
33 |
34 | /**
35 | * @template C
36 | * @template D
37 | * @template F
38 | * @param callable(C): D $focus
39 | * @param Validation $validation
40 | * @return self
41 | */
42 | public static function on(callable $focus, Validation $validation): self
43 | {
44 | return new self($focus, $validation);
45 | }
46 |
47 | /**
48 | * @param A $data
49 | * @param array $context
50 | * @return ValidationResult
51 | */
52 | public function validate($data, array $context = []): ValidationResult
53 | {
54 | return $this->validation->validate(($this->focus)($data), $context)
55 | // would really need a Lens here to update the outer value applying the callable to the inner value
56 | ->map(
57 | /**
58 | * @return A
59 | */
60 | function () use ($data) {
61 | return $data;
62 | }
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Combinator/Map.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | final class Map implements Validation
18 | {
19 | /** @var Validation */
20 | private $validation;
21 |
22 | /** @var callable(B): C */
23 | private $function;
24 |
25 | /**
26 | * @param Validation $validation
27 | * @param callable(B): C $function
28 | */
29 | private function __construct(
30 | Validation $validation,
31 | callable $function
32 | ) {
33 | $this->validation = $validation;
34 | $this->function = $function;
35 | }
36 |
37 | /**
38 | * @template D
39 | * @template H
40 | * @template F
41 | * @template G
42 | * @param Validation $validation
43 | * @param callable(F): G $function
44 | * @return self
45 | */
46 | public static function to(
47 | Validation $validation,
48 | callable $function
49 | ): self {
50 | return new self($validation, $function);
51 | }
52 |
53 | /**
54 | * @param A $data
55 | * @param array $context
56 | * @return ValidationResult
57 | */
58 | public function validate($data, array $context = []): ValidationResult
59 | {
60 | return $this->validation->validate($data, $context)->map($this->function);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Combinator/MapErrors.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | final class MapErrors implements Validation
18 | {
19 | /** @var Validation */
20 | private $validation;
21 |
22 | /**
23 | * @var callable(E[]): F[]
24 | */
25 | private $function;
26 |
27 | /**
28 | * @param Validation $validation
29 | * @param callable(E[]): F[] $function
30 | */
31 | private function __construct(
32 | Validation $validation,
33 | callable $function
34 | ) {
35 | $this->validation = $validation;
36 | $this->function = $function;
37 | }
38 |
39 | /**
40 | * @template C
41 | * @template G
42 | * @template H
43 | * @template D
44 | * @param Validation $validation
45 | * @param callable(G[]): H[] $function
46 | * @return self
47 | */
48 | public static function to(
49 | Validation $validation,
50 | callable $function
51 | ): self {
52 | return new self($validation, $function);
53 | }
54 |
55 | /**
56 | * @param A $data
57 | * @param array $context
58 | * @return ValidationResult
59 | */
60 | public function validate($data, array $context = []): ValidationResult
61 | {
62 | return $this->validation->validate($data, $context)->mapErrors($this->function);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Combinator/Sequence.php:
--------------------------------------------------------------------------------
1 | validations = $validations;
29 | }
30 |
31 | /**
32 | * @param Validation[] $validations
33 | * @return self
34 | * @throws InvalidArgumentException
35 | */
36 | public static function validations(array $validations): self
37 | {
38 | return new self($validations);
39 | }
40 |
41 | public function validate($data, array $context = []): ValidationResult
42 | {
43 | return array_reduce(
44 | $this->validations,
45 | function (ValidationResult $carry, Validation $validation) use ($context): ValidationResult {
46 | return $carry->process(
47 | /** @psalm-suppress MissingClosureParamType */
48 | function ($validData) use ($validation, $context): ValidationResult {
49 | return $validation->validate($validData, $context);
50 | },
51 | function () use ($carry): ValidationResult {
52 | return $carry;
53 | }
54 | );
55 | },
56 | ValidationResult::valid($data)
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Combinator/TranslateErrors.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | final class TranslateErrors implements Validation
22 | {
23 | /** @var Validation */
24 | private $validation;
25 |
26 | /** @var Translator */
27 | private $translator;
28 |
29 | /**
30 | * @param Validation $validation
31 | */
32 | public function __construct(Validation $validation, Translator $translator)
33 | {
34 | $this->validation = $validation;
35 | $this->translator = $translator;
36 | }
37 |
38 | /**
39 | * @template C
40 | * @template F
41 | * @template D
42 | * @param Validation $validation
43 | * @return self
44 | */
45 | public static function validationWithTranslator(Validation $validation, Translator $translator): self
46 | {
47 | return new self($validation, $translator);
48 | }
49 |
50 | /**
51 | * @param A $data
52 | * @param array $context
53 | * @return ValidationResult
54 | */
55 | public function validate($data, array $context = []): ValidationResult
56 | {
57 | return MapErrors::to($this->validation, Closure::fromCallable([$this, 'translateNestedErrors']))->validate($data, $context);
58 | }
59 |
60 | /**
61 | * @param E[] $messages
62 | * @return E[]
63 | * @psalm-suppress InvalidReturnType
64 | */
65 | private function translateNestedErrors(array $messages): array
66 | {
67 | $translator = $this->translator;
68 |
69 | /** @psalm-suppress InvalidReturnStatement */
70 | return array_map(
71 | function ($message) use ($translator) {
72 | if (is_string($message)) {
73 | return $translator->translate($message);
74 | }
75 |
76 | if (is_array($message)) {
77 | return $this->translateNestedErrors($message);
78 | }
79 |
80 | return $message;
81 | },
82 | $messages
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Equality.php:
--------------------------------------------------------------------------------
1 | isValid = $isValid;
34 | $this->validContent = $validContent;
35 | $this->messages = $messages;
36 | }
37 |
38 | /**
39 | * @template B
40 | * @template F
41 | * @param B $validContent
42 | * @return self
43 | */
44 | public static function valid($validContent): self
45 | {
46 | return new self(true, $validContent, []);
47 | }
48 |
49 | /**
50 | * @template B
51 | * @template F
52 | * @param F[] $messages
53 | * @return self
54 | */
55 | public static function errors(array $messages): self
56 | {
57 | return new self(false, null, $messages);
58 | }
59 |
60 | /**
61 | * @template F
62 | * @template B
63 | * @template G
64 | * @template C
65 | * @param self $that
66 | * @param callable(A, B): C $joinValid
67 | * @param callable(E[], F[]): G[] $joinErrors
68 | * @return self
69 | */
70 | public function join(self $that, callable $joinValid, callable $joinErrors): self
71 | {
72 | if (! $this->isValid || ! $that->isValid) {
73 | return self::errors($joinErrors($this->messages, $that->messages));
74 | }
75 |
76 | /** @var A $thisContent */
77 | $thisContent = $this->validContent;
78 | /** @var B $thatContent */
79 | $thatContent = $that->validContent;
80 |
81 | return self::valid($joinValid($thisContent, $thatContent));
82 | }
83 |
84 | /**
85 | * @template F
86 | * @template B
87 | * @template G
88 | * @template C
89 | * @param self $that
90 | * @param callable(A, B): C $joinBothValid
91 | * @param callable(A): C $joinThisValid
92 | * @param callable(B): C $joinThatValid
93 | * @param callable(E[], F[]): G[] $joinErrors
94 | * @return self
95 | */
96 | public function meet(
97 | self $that,
98 | callable $joinBothValid,
99 | callable $joinThisValid,
100 | callable $joinThatValid,
101 | callable $joinErrors
102 | ): self {
103 | $thisContent = $this->validContent;
104 | $thatContent = $that->validContent;
105 |
106 | if ($this->isValid && $that->isValid) {
107 | /**
108 | * @var A $thisContent
109 | * @var B $thatContent
110 | */
111 |
112 | return self::valid($joinBothValid($thisContent, $thatContent));
113 | }
114 |
115 | if ($this->isValid) {
116 | /** @var A $thisContent */
117 |
118 | return self::valid($joinThisValid($thisContent));
119 | }
120 |
121 | if ($that->isValid) {
122 | /** @var B $thatContent */
123 |
124 | return self::valid($joinThatValid($thatContent));
125 | }
126 |
127 | return self::errors($joinErrors($this->messages, $that->messages));
128 | }
129 |
130 | /**
131 | * @template B
132 | * @param callable(A): B $processValid
133 | * @param callable(E[]): B $processErrors
134 | * @return B
135 | */
136 | public function process(
137 | callable $processValid,
138 | callable $processErrors
139 | ) {
140 | if (! $this->isValid) {
141 | return $processErrors($this->messages);
142 | }
143 |
144 | /** @psalm-suppress PossiblyNullArgument */
145 | return $processValid($this->validContent);
146 | }
147 |
148 | /**
149 | * @template B
150 | * @param callable(A): B $map
151 | * @return self
152 | */
153 | public function map(callable $map): self
154 | {
155 | return $this->process(
156 | /** @param A $validContent */
157 | function ($validContent) use ($map): self {
158 | return self::valid($map($validContent));
159 | },
160 | function (array $messages): self {
161 | return self::errors($messages);
162 | }
163 | );
164 | }
165 |
166 | /**
167 | * @template F
168 | * @param callable(E[]): F[] $map
169 | * @return self
170 | */
171 | public function mapErrors(callable $map): self
172 | {
173 | return $this->process(
174 | /** @param mixed $validContent */
175 | function ($validContent): self {
176 | return self::valid($validContent);
177 | },
178 | function (array $messages) use ($map): self {
179 | return self::errors($map($messages));
180 | }
181 | );
182 | }
183 |
184 | /**
185 | * @template B
186 | * @param ValidationResult $apply
187 | * @return self
188 | */
189 | public function apply(ValidationResult $apply): self
190 | {
191 | /** @psalm-suppress MixedArgumentTypeCoercion */
192 | return $apply->process(
193 | /**
194 | * @param callable(A): B $validApply
195 | * @return self
196 | */
197 | function (callable $validApply): self {
198 | return $this->map($validApply);
199 | },
200 | /** @return self */
201 | function (array $applyMessages) {
202 | return $this->process(
203 | /** @param A $validContent */
204 | function ($validContent) use ($applyMessages): self {
205 | return self::errors($applyMessages);
206 | },
207 | function (array $messages) use ($applyMessages): self {
208 | return self::errors(array_merge($applyMessages, $messages));
209 | }
210 | );
211 | }
212 | );
213 | }
214 |
215 | /**
216 | * @template B
217 | * @param callable(A): self $bind
218 | * @return self
219 | */
220 | public function bind(callable $bind): self
221 | {
222 | return $this->process(
223 | /** @param A $validContent */
224 | function ($validContent) use ($bind): self {
225 | return $bind($validContent);
226 | },
227 | function (array $messages): self {
228 | return self::errors($messages);
229 | }
230 | );
231 | }
232 |
233 | /**
234 | * @param self $that
235 | * @return bool
236 | */
237 | public function equals($that): bool
238 | {
239 | if (
240 | is_object($this->validContent) && is_object($that->validContent) &&
241 | get_class($this->validContent) === get_class($that->validContent) &&
242 | $this->validContent instanceof Equality
243 | ) {
244 | $contentEquality = $this->validContent->equals($that->validContent);
245 | } else {
246 | $contentEquality = $this->validContent === $that->validContent;
247 | }
248 |
249 | return ($this->isValid && $that->isValid && $contentEquality) ||
250 | (!$this->isValid && !$that->isValid && $this->messages === $that->messages);
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/Result/functions.php:
--------------------------------------------------------------------------------
1 | T
15 | * @return Closure with signature (ValidationResult T1, ValidationResult T2, ...) -> ValidationResult T
16 | *
17 | * @psalm-return Closure(): ValidationResult
18 | */
19 | function lift(callable $f): Closure
20 | {
21 | $innerLift =
22 | /**
23 | * @return Closure
24 | *
25 | * @psalm-return Closure(): ValidationResult
26 | */
27 | static function (
28 | callable $f,
29 | ?int $numberOfParameters = null,
30 | array $parameters = []
31 | ) use (&$innerLift): Closure {
32 | if (null === $numberOfParameters) {
33 | // retrieve number of parameters from reflection
34 | $fClosure = Closure::fromCallable($f);
35 | $fRef = new ReflectionFunction($fClosure);
36 | $numberOfParameters = $fRef->getNumberOfParameters();
37 | }
38 |
39 | /** @return ValidationResult|Closure */
40 | /** @psalm-suppress MissingClosureReturnType */
41 | return static function () use ($f, $numberOfParameters, $parameters, $innerLift) {
42 | // collect all necessary parameters
43 |
44 | /** @var array $newParameters */
45 | $newParameters = array_merge($parameters, func_get_args());
46 |
47 | if (count($newParameters) >= $numberOfParameters) {
48 | if ([] === $newParameters) {
49 | return ValidationResult::valid($f());
50 | }
51 |
52 | $firstParameter = $newParameters[0];
53 |
54 | $result = $firstParameter->map(curry($f));
55 |
56 | foreach (array_slice($newParameters, 1) as $validatedParameter) {
57 | $result = $validatedParameter->apply($result);
58 | }
59 |
60 | return $result;
61 | }
62 |
63 | return $innerLift($f, $numberOfParameters, $newParameters);
64 | };
65 | };
66 |
67 | return $innerLift($f);
68 | }
69 |
70 | /**
71 | * @param callable[] $fs every function takes as arguments the unwrapped results of the previous one and returns a
72 | * ValidationResult
73 | * @return ValidationResult
74 | */
75 | function sdo(callable ...$fs): ValidationResult
76 | {
77 | Assert::notEmpty($fs, 'do_ must receive at least one callable');
78 |
79 | $result = ValidationResult::valid(null);
80 |
81 | foreach ($fs as $f) {
82 | $result = $result->bind($f);
83 | }
84 |
85 | return $result;
86 | }
87 |
88 | final class DoPartialTempResult
89 | {
90 | /**
91 | * @var callable
92 | */
93 | private $f;
94 |
95 | /**
96 | * @var array
97 | */
98 | private $arguments;
99 |
100 | private function __construct(callable $f, array $arguments)
101 | {
102 | $this->f = $f;
103 | $this->arguments = $arguments;
104 | }
105 |
106 | public static function fromCallableAndArguments(callable $f, array $arguments): ValidationResult
107 | {
108 | return ValidationResult::valid(new self($f, $arguments));
109 | }
110 |
111 | public static function fromPreviousAndCallable(ValidationResult $previous, callable $f): ValidationResult
112 | {
113 | return $previous->bind(function (DoPartialTempResult $doPartialTempResult) use ($f): ValidationResult {
114 | $lastArgumentResult = $doPartialTempResult();
115 |
116 | /** @psalm-suppress MissingClosureParamType */
117 | return $lastArgumentResult->bind(function ($lastArgument) use ($doPartialTempResult, $f): ValidationResult {
118 | $fArguments = array_merge($doPartialTempResult->arguments, [$lastArgument]);
119 |
120 | return self::fromCallableAndArguments($f, $fArguments);
121 | });
122 | });
123 | }
124 |
125 | public function __invoke(): ValidationResult
126 | {
127 | return call_user_func_array($this->f, $this->arguments);
128 | }
129 | }
130 |
131 | /**
132 | * @param callable[] $fs every function takes as arguments the unwrapped results of the previous one and returns a
133 | * ValidationResult
134 | * @return ValidationResult
135 | */
136 | function mdo(callable ...$fs): ValidationResult
137 | {
138 | Assert::notEmpty($fs, 'do__ must receive at least one callable');
139 |
140 | $doPartialTempResult = DoPartialTempResult::fromCallableAndArguments($fs[0], []);
141 |
142 | foreach (array_slice($fs, 1) as $f) {
143 | $doPartialTempResult = DoPartialTempResult::fromPreviousAndCallable($doPartialTempResult, $f);
144 | }
145 |
146 | return $doPartialTempResult->bind(function (DoPartialTempResult $doPartialTempResult): ValidationResult {
147 | return $doPartialTempResult();
148 | });
149 | }
150 |
--------------------------------------------------------------------------------
/src/Translator/Combinator/Coalesce.php:
--------------------------------------------------------------------------------
1 | translators = $translators;
22 | }
23 |
24 | public static function withTranslators(?Translator ...$translators): self
25 | {
26 | return new self($translators);
27 | }
28 |
29 | public function translate(string $string): string
30 | {
31 | foreach ($this->translators as $translator) {
32 | if ($translator instanceof Translator) {
33 | return $translator->translate($string);
34 | }
35 | }
36 |
37 | return $string;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Translator/ConstantTranslator.php:
--------------------------------------------------------------------------------
1 | translation = $translation;
17 | }
18 |
19 | public static function withTranslation(string $translation): self
20 | {
21 | return new self($translation);
22 | }
23 |
24 | public function translate(string $string): string
25 | {
26 | return $this->translation;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Translator/IdentityTranslator.php:
--------------------------------------------------------------------------------
1 | key value dictionary of translations
13 | */
14 | private $dictionary;
15 |
16 | /**
17 | * @param array $dictionary
18 | */
19 | private function __construct(array $dictionary)
20 | {
21 | $this->dictionary = $dictionary;
22 | }
23 |
24 | /**
25 | * @param array $dictionary
26 | */
27 | public static function withDictionary(array $dictionary): self
28 | {
29 | return new self($dictionary);
30 | }
31 |
32 | public function translate(string $string): string
33 | {
34 | return array_key_exists($string, $this->dictionary) ?
35 | $this->dictionary[$string] :
36 | $string;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Translator/Translator.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | public function validate($data, array $context = []): ValidationResult;
22 | }
23 |
--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
1 | $something
14 | * @return Closure with signature $a1 -> ($a2 -> (... -> $something))
15 | *
16 | * @psalm-return Closure(): callable
17 | */
18 | function curry(callable $f): Closure
19 | {
20 | $innerCurry =
21 | /**
22 | * @return Closure
23 | *
24 | * @psalm-return Closure(): callable
25 | */
26 | static function (
27 | callable $f,
28 | ?int $numberOfParameters = null,
29 | array $parameters = []
30 | ) use (&$innerCurry): Closure {
31 | if (null === $numberOfParameters) {
32 | // retrieve number of parameters from reflection
33 | $fClosure = Closure::fromCallable($f);
34 | $fRef = new ReflectionFunction($fClosure);
35 | $numberOfParameters = $fRef->getNumberOfParameters();
36 | }
37 |
38 | /** @psalm-suppress MissingClosureReturnType */
39 | return static function () use ($f, $numberOfParameters, $parameters, $innerCurry) {
40 | /** @var array $newParameters */
41 | $newParameters = array_merge($parameters, func_get_args());
42 |
43 | if (count($newParameters) >= $numberOfParameters) {
44 | return call_user_func_array($f, $newParameters);
45 | }
46 |
47 | return $innerCurry($f, $numberOfParameters, $newParameters);
48 | };
49 | };
50 |
51 | return $innerCurry($f);
52 | }
53 |
54 | /**
55 | * @param callable $f with signature $a1 -> ($a2 -> (... -> $something))
56 | * @return Closure with signature ($a1, $a2, ...) -> $something
57 | *
58 | * @psalm-return Closure(... array): mixed
59 | */
60 | function uncurry(callable $f): Closure
61 | {
62 | /**
63 | * @psalm-suppress MissingClosureParamType
64 | * @psalm-suppress MissingClosureReturnType
65 | */
66 | return static function (...$params) use ($f) {
67 | if ([] === $params) {
68 | return $f();
69 | }
70 |
71 | $firstParam = $params[0];
72 |
73 | $firstApplication = $f($firstParam);
74 |
75 | if (! is_callable($firstApplication)) {
76 | return $firstApplication;
77 | }
78 |
79 | return uncurry($firstApplication)(...array_slice($params, 1));
80 | };
81 | }
82 |
--------------------------------------------------------------------------------