Skip to content

Commit

Permalink
Create transformer for deprecated composite rules
Browse files Browse the repository at this point in the history
This transformers will help transition from the current major version to
the next. In the current version, it's possible to call `allOf`,
`anyOf`, `noneOf`, and `oneOf` without any arguments or with only one,
and that doesn't make much sense.
  • Loading branch information
henriquemoody committed Jan 7, 2025
1 parent 1d6d005 commit b6fdd50
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 102 deletions.
15 changes: 12 additions & 3 deletions library/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Respect\Validation\Transformers\Aliases;
use Respect\Validation\Transformers\DeprecatedAge;
use Respect\Validation\Transformers\DeprecatedAttribute;
use Respect\Validation\Transformers\DeprecatedComposite;
use Respect\Validation\Transformers\DeprecatedKey;
use Respect\Validation\Transformers\DeprecatedKeyNested;
use Respect\Validation\Transformers\DeprecatedKeyValue;
Expand Down Expand Up @@ -45,9 +46,17 @@ public function __construct(
new DeprecatedKeyValue(
new DeprecatedMinAndMax(
new DeprecatedAge(
new DeprecatedKeyNested(new DeprecatedLength(new DeprecatedType(new DeprecatedSize(
new Aliases(new Prefix())
))))
new DeprecatedKeyNested(
new DeprecatedLength(
new DeprecatedType(
new DeprecatedSize(
new DeprecatedComposite(
new Aliases(new Prefix())
)
)
)
)
)
)
)
)
Expand Down
72 changes: 72 additions & 0 deletions library/Transformers/DeprecatedComposite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Transformers;

use Respect\Validation\Rules\AlwaysInvalid;
use Respect\Validation\Rules\AlwaysValid;

use function count;
use function in_array;
use function sprintf;
use function trigger_error;

use const E_USER_DEPRECATED;

final class DeprecatedComposite implements Transformer
{
public function __construct(

Check warning on line 24 in library/Transformers/DeprecatedComposite.php

View check run for this annotation

Codecov / codecov/patch

library/Transformers/DeprecatedComposite.php#L24

Added line #L24 was not covered by tests
private readonly Transformer $next
) {
}

Check warning on line 27 in library/Transformers/DeprecatedComposite.php

View check run for this annotation

Codecov / codecov/patch

library/Transformers/DeprecatedComposite.php#L27

Added line #L27 was not covered by tests

public function transform(RuleSpec $ruleSpec): RuleSpec
{
if (!in_array($ruleSpec->name, ['allOf', 'anyOf', 'noneOf', 'oneOf'])) {
return $this->next->transform($ruleSpec);
}

$arguments = $ruleSpec->arguments;
if (count($arguments) >= 2) {
return $this->next->transform($ruleSpec);
}

if (count($arguments) === 0) {
trigger_error(
sprintf(
'Calling %s() without any arguments has been deprecated, ' .
'and will be not be allowed in the next major version. Use it with at least 2 arguments.',
$ruleSpec->name
),
E_USER_DEPRECATED
);

return match ($ruleSpec->name) {
'allOf', 'noneOf' => new RuleSpec('alwaysValid'),
'anyOf', 'oneOf' => new RuleSpec('alwaysInvalid'),
};
}

trigger_error(
sprintf(
'Calling %s() with a single argument has been deprecated, ' .
'and will be not be allowed in the next major version. Use it with at least 2 arguments.',
$ruleSpec->name
),
E_USER_DEPRECATED
);

$arguments[] = match ($ruleSpec->name) {
'allOf' => new AlwaysValid(),
'anyOf', 'noneOf', 'oneOf' => new AlwaysInvalid(),
};

return new RuleSpec($ruleSpec->name, $arguments);
}
}
15 changes: 7 additions & 8 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,20 @@ function expectDeprecation(Closure $callback, string $error): Closure
return true;
});

try {
$callback->call($this);
} catch (Throwable $e) {
restore_error_handler();
expect($lastError)->toBe($error);
throw $e;
}
$callback->call($this);
restore_error_handler();
expect($lastError)->toBe($error);
};
}

function expectMessageAndError(Closure $callback, string $message, string $error): Closure
function expectMessageAndDeprecation(Closure $callback, string $message, string $error): Closure
{
return function () use ($callback, $message, $error): void {
$lastError = null;
set_error_handler(static function (int $errno, string $errstr) use (&$lastError): bool {
if ($errno !== E_USER_DEPRECATED) {
return false;
}
$lastError = $errstr;

return true;
Expand Down
16 changes: 8 additions & 8 deletions tests/feature/Transformers/DeprecatedAgeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,49 @@

date_default_timezone_set('UTC');

test('Scenario #1', expectMessageAndError(
test('Scenario #1', expectMessageAndDeprecation(
fn() => v::minAge(18)->assert('17 years ago'),
'The number of years between now and "17 years ago" must be greater than or equal to 18',
'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #2', expectMessageAndError(
test('Scenario #2', expectMessageAndDeprecation(
fn() => v::not(v::minAge(18))->assert('-30 years'),
'The number of years between now and "-30 years" must be less than 18',
'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #3', expectMessageAndError(
test('Scenario #3', expectMessageAndDeprecation(
fn() => v::minAge(18)->assert('yesterday'),
'The number of years between now and "yesterday" must be greater than or equal to 18',
'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #4', expectMessageAndError(
test('Scenario #4', expectMessageAndDeprecation(
fn() => v::minAge(18, 'd/m/Y')->assert('12/10/2010'),
'The number of years between now and "12/10/2010" must be greater than or equal to 18',
'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #5', expectMessageAndError(
test('Scenario #5', expectMessageAndDeprecation(
fn() => v::maxAge(12)->assert('50 years ago'),
'The number of years between now and "50 years ago" must be less than or equal to 12',
'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #6', expectMessageAndError(
test('Scenario #6', expectMessageAndDeprecation(
fn() => v::not(v::maxAge(12))->assert('11 years ago'),
'The number of years between now and "11 years ago" must be greater than 12',
'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #7', expectMessageAndError(
test('Scenario #7', expectMessageAndDeprecation(
fn() => v::maxAge(12, 'Y-m-d')->assert('1988-09-09'),
'The number of years between now and "1988-09-09" must be less than or equal to 12',
'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
));

test('Scenario #8', expectMessageAndError(
test('Scenario #8', expectMessageAndDeprecation(
fn() => v::not(v::maxAge(12, 'Y-m-d'))->assert('2018-01-01'),
'The number of years between now and "2018-01-01" must be greater than 12',
'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
Expand Down
16 changes: 8 additions & 8 deletions tests/feature/Transformers/DeprecatedAttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,49 @@
$object->foo = true;
$object->bar = 42;

test('Scenario #1', expectMessageAndError(
test('Scenario #1', expectMessageAndDeprecation(
fn() => v::attribute('baz')->assert($object),
'`.baz` must be present',
'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyExists() instead.',
));

test('Scenario #2', expectMessageAndError(
test('Scenario #2', expectMessageAndDeprecation(
fn() => v::not(v::attribute('foo'))->assert($object),
'`.foo` must not be present',
'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyExists() instead.',
));

test('Scenario #3', expectMessageAndError(
test('Scenario #3', expectMessageAndDeprecation(
fn() => v::attribute('foo', v::falseVal())->assert($object),
'`.foo` must evaluate to `false`',
'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.',
));

test('Scenario #4', expectMessageAndError(
test('Scenario #4', expectMessageAndDeprecation(
fn() => v::not(v::attribute('foo', v::trueVal()))->assert($object),
'`.foo` must not evaluate to `true`',
'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.',
));

test('Scenario #5', expectMessageAndError(
test('Scenario #5', expectMessageAndDeprecation(
fn() => v::attribute('foo', v::falseVal(), true)->assert($object),
'`.foo` must evaluate to `false`',
'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.',
));

test('Scenario #6', expectMessageAndError(
test('Scenario #6', expectMessageAndDeprecation(
fn() => v::not(v::attribute('foo', v::trueVal(), true))->assert($object),
'`.foo` must not evaluate to `true`',
'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.',
));

test('Scenario #7', expectMessageAndError(
test('Scenario #7', expectMessageAndDeprecation(
fn() => v::attribute('foo', v::falseVal(), false)->assert($object),
'`.foo` must evaluate to `false`',
'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyOptional() instead.',
));

test('Scenario #8', expectMessageAndError(
test('Scenario #8', expectMessageAndDeprecation(
fn() => v::not(v::attribute('foo', v::trueVal(), false))->assert($object),
'`.foo` must not evaluate to `true`',
'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyOptional() instead.',
Expand Down
76 changes: 76 additions & 0 deletions tests/feature/Transformers/DeprecatedCompositeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

date_default_timezone_set('UTC');

test('Calling allOf without any arguments', expectDeprecation(
fn() => v::allOf()->assert('input'),

Check failure on line 13 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::allOf() invoked with 0 parameters, at least 2 required.
'Calling allOf() without any arguments has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling allOf with a single passing rule as argument', expectDeprecation(
fn() => v::allOf(v::stringType())->assert('input'),

Check failure on line 18 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::allOf() invoked with 1 parameter, at least 2 required.
'Calling allOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling allOf with a single failing rule as argument', expectMessageAndDeprecation(
fn() => v::allOf(v::intType())->assert('input'),

Check failure on line 23 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::allOf() invoked with 1 parameter, at least 2 required.
'"input" must be an integer',
'Calling allOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling anyOf without any arguments', expectMessageAndDeprecation(
fn() => v::anyOf()->assert('input'),

Check failure on line 29 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::anyOf() invoked with 0 parameters, at least 2 required.
'"input" must be valid',
'Calling anyOf() without any arguments has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling anyOf with a single passing rule as argument', expectDeprecation(
fn() => v::anyOf(v::stringType())->assert('input'),

Check failure on line 35 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::anyOf() invoked with 1 parameter, at least 2 required.
'Calling anyOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling anyOf with a single failing rule as argument', expectMessageAndDeprecation(
fn() => v::anyOf(v::intType())->assert('input'),

Check failure on line 40 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::anyOf() invoked with 1 parameter, at least 2 required.
'"input" must be an integer',
'Calling anyOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling noneOf without any arguments', expectDeprecation(
fn() => v::noneOf()->assert('input'),

Check failure on line 46 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::noneOf() invoked with 0 parameters, at least 2 required.
'Calling noneOf() without any arguments has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling noneOf with a single passing rule as argument', expectMessageAndDeprecation(
fn() => v::noneOf(v::stringType())->assert('input'),

Check failure on line 51 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::noneOf() invoked with 1 parameter, at least 2 required.
'"input" must not be a string',
'Calling noneOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling noneOf with a single failing rule as argument', expectDeprecation(
fn() => v::noneOf(v::intType())->assert('input'),

Check failure on line 57 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::noneOf() invoked with 1 parameter, at least 2 required.
'Calling noneOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling oneOf without any arguments', expectMessageAndDeprecation(
fn() => v::oneOf()->assert('input'),

Check failure on line 62 in tests/feature/Transformers/DeprecatedCompositeTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Static method Respect\Validation\Mixins\Builder::oneOf() invoked with 0 parameters, at least 2 required.
'"input" must be valid',
'Calling oneOf() without any arguments has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling oneOf with a single passing rule as argument', expectDeprecation(
fn() => v::oneOf(v::stringType())->assert('input'),
'Calling oneOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));

test('Calling oneOf with a single failing rule as argument', expectMessageAndDeprecation(
fn() => v::oneOf(v::intType())->assert('input'),
'"input" must be an integer',
'Calling oneOf() with a single argument has been deprecated, and will be not be allowed in the next major version. Use it with at least 2 arguments.',
));
14 changes: 7 additions & 7 deletions tests/feature/Transformers/DeprecatedKeyNestedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,43 @@
],
];

test('Scenario #1', expectMessageAndError(
test('Scenario #1', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar.baz')->assert(['foo.bar.baz' => false]),
'`.foo` must be present',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
));

test('Scenario #A', expectMessageAndError(
test('Scenario #A', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar.baz')->assert(['foo' => []]),
'`.foo.bar` must be present',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
));

test('Scenario #B', expectMessageAndError(
test('Scenario #B', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar.baz')->assert(['foo' => []]),
'`.foo.bar` must be present',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
));

test('Scenario #C', expectMessageAndError(
test('Scenario #C', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar.baz')->assert(['foo' => ['bar' => []]]),
'`.foo.bar.baz` must be present',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
));

test('Scenario #2', expectMessageAndError(
test('Scenario #2', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar', v::negative())->assert($input),
'`.foo.bar` must be a negative number',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
));

test('Scenario #3', expectMessageAndError(
test('Scenario #3', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar', v::stringType())->assert(new ArrayObject($input)),
'`.foo.bar` must be a string',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
));

test('Scenario #4', expectMessageAndError(
test('Scenario #4', expectMessageAndDeprecation(
fn() => v::keyNested('foo.bar', v::floatType(), false)->assert($input),
'`.foo.bar` must be float',
'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.',
Expand Down
Loading

0 comments on commit b6fdd50

Please sign in to comment.