diff --git a/library/Message/StandardRenderer.php b/library/Message/StandardRenderer.php index 5bee117b0..195410d77 100644 --- a/library/Message/StandardRenderer.php +++ b/library/Message/StandardRenderer.php @@ -11,7 +11,6 @@ use ReflectionClass; use Respect\Stringifier\Stringifier; -use Respect\Stringifier\Stringifiers\CompositeStringifier; use Respect\Validation\Mode; use Respect\Validation\Result; use Respect\Validation\Rule; @@ -27,11 +26,9 @@ final class StandardRenderer implements Renderer /** @var array> */ private array $templates = []; - private readonly Stringifier $stringifier; - - public function __construct(?Stringifier $stringifier = null) - { - $this->stringifier = $stringifier ?? CompositeStringifier::createDefault(); + public function __construct( + private readonly Stringifier $stringifier = new StandardStringifier(), + ) { } public function render(Result $result, Translator $translator, ?string $template = null): string diff --git a/library/Message/StandardStringifier.php b/library/Message/StandardStringifier.php new file mode 100644 index 000000000..ce634a35d --- /dev/null +++ b/library/Message/StandardStringifier.php @@ -0,0 +1,92 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Message; + +use DateTimeInterface; +use Respect\Stringifier\Quoter; +use Respect\Stringifier\Quoters\StandardQuoter; +use Respect\Stringifier\Stringifier; +use Respect\Stringifier\Stringifiers\ArrayObjectStringifier; +use Respect\Stringifier\Stringifiers\ArrayStringifier; +use Respect\Stringifier\Stringifiers\BoolStringifier; +use Respect\Stringifier\Stringifiers\CompositeStringifier; +use Respect\Stringifier\Stringifiers\DateTimeStringifier; +use Respect\Stringifier\Stringifiers\DeclaredStringifier; +use Respect\Stringifier\Stringifiers\EnumerationStringifier; +use Respect\Stringifier\Stringifiers\InfiniteNumberStringifier; +use Respect\Stringifier\Stringifiers\IteratorObjectStringifier; +use Respect\Stringifier\Stringifiers\JsonEncodableStringifier; +use Respect\Stringifier\Stringifiers\JsonSerializableObjectStringifier; +use Respect\Stringifier\Stringifiers\NotANumberStringifier; +use Respect\Stringifier\Stringifiers\NullStringifier; +use Respect\Stringifier\Stringifiers\ObjectStringifier; +use Respect\Stringifier\Stringifiers\ObjectWithDebugInfoStringifier; +use Respect\Stringifier\Stringifiers\ResourceStringifier; +use Respect\Stringifier\Stringifiers\StringableObjectStringifier; +use Respect\Stringifier\Stringifiers\ThrowableObjectStringifier; + +final class StandardStringifier implements Stringifier +{ + private const MAXIMUM_DEPTH = 3; + private const MAXIMUM_NUMBER_OF_ITEMS = 5; + private const MAXIMUM_NUMBER_OF_PROPERTIES = self::MAXIMUM_NUMBER_OF_ITEMS; + private const MAXIMUM_LENGTH = 120; + + private readonly Stringifier $stringifier; + + public function __construct( + private readonly Quoter $quoter = new StandardQuoter(self::MAXIMUM_LENGTH) + ) { + $this->stringifier = $this->createStringifier($quoter); + } + + public function stringify(mixed $raw, int $depth): string + { + return $this->stringifier->stringify($raw, $depth) ?? $this->quoter->quote('unknown', $depth); + } + + private function createStringifier(Quoter $quoter): Stringifier + { + $jsonEncodableStringifier = new JsonEncodableStringifier(); + + $stringifier = new CompositeStringifier( + new InfiniteNumberStringifier($quoter), + new NotANumberStringifier($quoter), + new ResourceStringifier($quoter), + new BoolStringifier($quoter), + new NullStringifier($quoter), + new DeclaredStringifier($quoter), + $jsonEncodableStringifier, + ); + $arrayStringifier = new ArrayStringifier( + $stringifier, + $quoter, + self::MAXIMUM_DEPTH, + self::MAXIMUM_NUMBER_OF_ITEMS, + ); + $stringifier->prependStringifier($arrayStringifier); + $stringifier->prependStringifier(new ObjectStringifier( + $stringifier, + $quoter, + self::MAXIMUM_DEPTH, + self::MAXIMUM_NUMBER_OF_PROPERTIES + )); + $stringifier->prependStringifier(new EnumerationStringifier($quoter)); + $stringifier->prependStringifier(new ObjectWithDebugInfoStringifier($arrayStringifier, $quoter)); + $stringifier->prependStringifier(new ArrayObjectStringifier($arrayStringifier, $quoter)); + $stringifier->prependStringifier(new JsonSerializableObjectStringifier($jsonEncodableStringifier, $quoter)); + $stringifier->prependStringifier(new StringableObjectStringifier($jsonEncodableStringifier, $quoter)); + $stringifier->prependStringifier(new ThrowableObjectStringifier($jsonEncodableStringifier, $quoter)); + $stringifier->prependStringifier(new DateTimeStringifier($quoter, DateTimeInterface::ATOM)); + $stringifier->prependStringifier(new IteratorObjectStringifier($stringifier, $quoter)); + + return $stringifier; + } +} diff --git a/tests/feature/Message/StandardStringifierTest.php b/tests/feature/Message/StandardStringifierTest.php new file mode 100644 index 000000000..d1c66d291 --- /dev/null +++ b/tests/feature/Message/StandardStringifierTest.php @@ -0,0 +1,19 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +use Respect\Validation\Message\StandardStringifier; + +test('Should return `unknown` when cannot stringify value', function () { + $resource = tmpfile(); + fclose($resource); + + $stringifier = new StandardStringifier(); + + expect($stringifier->stringify($resource, 0))->toBe('`unknown`'); +}); diff --git a/tests/feature/Rules/CallTest.php b/tests/feature/Rules/CallTest.php index 6e394b5ff..4256e9a61 100644 --- a/tests/feature/Rules/CallTest.php +++ b/tests/feature/Rules/CallTest.php @@ -19,7 +19,7 @@ test('Scenario #3', expectMessage( fn() => v::call('stripslashes', v::alwaysValid())->assert([]), - '`[]` must be a suitable argument for `stripslashes(string $string): string`', + '`[]` must be a suitable argument for "stripslashes"', )); test('Scenario #4', expectFullMessage( @@ -34,5 +34,5 @@ test('Scenario #6', expectFullMessage( fn() => v::call('array_shift', v::alwaysValid())->assert(INF), - '- `INF` must be a suitable argument for `array_shift(array &$array): ?mixed`', + '- `INF` must be a suitable argument for "array_shift"', )); diff --git a/tests/feature/Rules/CallableTypeTest.php b/tests/feature/Rules/CallableTypeTest.php index aab63828d..cfc1c9b61 100644 --- a/tests/feature/Rules/CallableTypeTest.php +++ b/tests/feature/Rules/CallableTypeTest.php @@ -14,7 +14,7 @@ test('Scenario #2', expectMessage( fn() => v::not(v::callableType())->assert('trim'), - '`trim(string $string, string $characters = " \\n\\r\\t\\u000b\\u0000"): string` must not be a callable', + '"trim" must not be a callable', )); test('Scenario #3', expectFullMessage( @@ -26,5 +26,5 @@ fn() => v::not(v::callableType())->assert(function (): void { // Do nothing }), - '- `function (): void` must not be a callable', + '- `Closure {}` must not be a callable', )); diff --git a/tests/feature/Rules/CnpjTest.php b/tests/feature/Rules/CnpjTest.php index 2a793bb27..c81a960f3 100644 --- a/tests/feature/Rules/CnpjTest.php +++ b/tests/feature/Rules/CnpjTest.php @@ -21,7 +21,7 @@ test('Scenario #3', expectFullMessage( fn() => v::cnpj()->assert('test'), - '- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be a valid CNPJ number', + '- "test" must be a valid CNPJ number', )); test('Scenario #4', expectFullMessage( diff --git a/tests/feature/Rules/ObjectTypeTest.php b/tests/feature/Rules/ObjectTypeTest.php index 7dd1ea497..2ba586044 100644 --- a/tests/feature/Rules/ObjectTypeTest.php +++ b/tests/feature/Rules/ObjectTypeTest.php @@ -19,7 +19,7 @@ test('Scenario #3', expectFullMessage( fn() => v::objectType()->assert('test'), - '- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be an object', + '- "test" must be an object', )); test('Scenario #4', expectFullMessage( diff --git a/tests/feature/Rules/ResourceTypeTest.php b/tests/feature/Rules/ResourceTypeTest.php index 5057a24d9..136cc5f5f 100644 --- a/tests/feature/Rules/ResourceTypeTest.php +++ b/tests/feature/Rules/ResourceTypeTest.php @@ -9,7 +9,7 @@ test('Scenario #1', expectMessage( fn() => v::resourceType()->assert('test'), - '`test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be a resource', + '"test" must be a resource', )); test('Scenario #2', expectMessage( diff --git a/tests/feature/Rules/UniqueTest.php b/tests/feature/Rules/UniqueTest.php index 6b43ea1c1..070fd51cb 100644 --- a/tests/feature/Rules/UniqueTest.php +++ b/tests/feature/Rules/UniqueTest.php @@ -19,7 +19,7 @@ test('Scenario #3', expectFullMessage( fn() => v::unique()->assert('test'), - '- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must not contain duplicates', + '- "test" must not contain duplicates', )); test('Scenario #4', expectFullMessage(