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 848724e
Show file tree
Hide file tree
Showing 15 changed files with 260 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);
}
}
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ parameters:
path: tests/feature/Transformers/AliasesTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::(min|max)Age\(\)./'
path: tests/feature/Transformers/DeprecatedAgeTest.php
- message: '/Static method Respect\\Validation\\Mixins\\Builder::.+Of\(\) invoked with \d parameters?, at least 2 required/'
path: tests/feature/Transformers/DeprecatedCompositeTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::attribute\(\)./'
path: tests/feature/Transformers/DeprecatedAttributeTest.php
- message: '/Static method Respect\\Validation\\Mixins\\Builder::key\(\) invoked with \d parameters?, 2 required/'
Expand Down
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'),
'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'),
'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'),
'"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'),
'"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'),
'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'),
'"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'),
'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'),
'"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'),
'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'),
'"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 848724e

Please sign in to comment.