From 26e9fb9d5917cb563800e2deff5ecbf9d822df05 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Wed, 14 Feb 2024 18:06:32 +0100 Subject: [PATCH] Allow templates to have raw and translatable params In some cases, a user would like to show the parameter just as it is, and in other cases, they need to translate a specific parameter. This change creates that capability by adding a template-style modifier to a parameter in the template. Signed-off-by: Henrique Moody --- library/Exceptions/ValidationException.php | 6 +- library/Factory.php | 4 +- library/Message/Formatter.php | 49 ----- library/Message/TemplateRenderer.php | 74 +++++++ library/Rules/Base.php | 4 +- library/Rules/Charset.php | 4 +- library/Rules/CreditCard.php | 8 +- library/Rules/Factor.php | 4 +- library/Rules/Instance.php | 4 +- library/Rules/Ip.php | 4 +- library/Rules/LanguageCode.php | 4 +- library/Rules/MaxAge.php | 4 +- library/Rules/MinAge.php | 4 +- library/Rules/Phone.php | 4 +- library/Rules/Printable.php | 4 +- library/Rules/Regex.php | 4 +- library/Rules/SubdivisionCode.php | 4 +- library/Rules/Uuid.php | 4 +- library/Rules/VideoUrl.php | 4 +- tests/integration/rules/creditCard.phpt | 8 +- tests/integration/rules/ip.phpt | 8 +- tests/integration/rules/languageCode.phpt | 8 +- tests/integration/rules/regex.phpt | 8 +- tests/integration/rules/videoUrl.phpt | 8 +- tests/integration/translator-assert.phpt | 6 +- .../Message/TestingParameterStringifier.php | 23 +++ tests/library/Stubs/CompositeSub.php | 4 +- .../NestedValidationExceptionTest.php | 6 +- .../Exceptions/ValidationExceptionTest.php | 6 +- tests/unit/Message/TemplateRendererTest.php | 182 ++++++++++++++++++ tests/unit/Rules/PhoneTest.php | 2 +- tests/unit/Rules/SubdivisionCodeTest.php | 2 +- 32 files changed, 351 insertions(+), 117 deletions(-) delete mode 100644 library/Message/Formatter.php create mode 100644 library/Message/TemplateRenderer.php create mode 100644 tests/library/Message/TestingParameterStringifier.php create mode 100644 tests/unit/Message/TemplateRendererTest.php diff --git a/library/Exceptions/ValidationException.php b/library/Exceptions/ValidationException.php index 531f3c52e..bf50a5870 100644 --- a/library/Exceptions/ValidationException.php +++ b/library/Exceptions/ValidationException.php @@ -10,8 +10,8 @@ namespace Respect\Validation\Exceptions; use InvalidArgumentException; -use Respect\Validation\Message\Formatter; use Respect\Validation\Message\Template; +use Respect\Validation\Message\TemplateRenderer; use function count; @@ -37,7 +37,7 @@ public function __construct( private array $params, private string $template, array $templates, - private readonly Formatter $formatter + private readonly TemplateRenderer $formatter ) { if (count($templates) === 0) { $templates = [new Template('{{name}} must be valid', '{{name}} must not be valid')]; @@ -104,7 +104,7 @@ private function getTemplateString(): string private function createMessage(): string { - return $this->formatter->format($this->getTemplateString(), $this->input, $this->params); + return $this->formatter->render($this->getTemplateString(), $this->input, $this->params); } public function __toString(): string diff --git a/library/Factory.php b/library/Factory.php index 9fa29d87d..cec9b7b40 100644 --- a/library/Factory.php +++ b/library/Factory.php @@ -16,10 +16,10 @@ use Respect\Validation\Exceptions\ComponentException; use Respect\Validation\Exceptions\InvalidClassException; use Respect\Validation\Exceptions\ValidationException; -use Respect\Validation\Message\Formatter; use Respect\Validation\Message\ParameterStringifier; use Respect\Validation\Message\Stringifier\KeepOriginalStringName; use Respect\Validation\Message\TemplateCollector; +use Respect\Validation\Message\TemplateRenderer; use function count; use function lcfirst; @@ -121,7 +121,7 @@ public function exception(Validatable $validatable, mixed $input, array $extraPa } $template = $validatable->getTemplate($input); $templates = $this->templateCollector->extract($validatable); - $formatter = new Formatter($this->translator, $this->parameterStringifier); + $formatter = new TemplateRenderer($this->translator, $this->parameterStringifier); $attributes = $reflection->getAttributes(ExceptionClass::class); if (count($attributes) === 0) { diff --git a/library/Message/Formatter.php b/library/Message/Formatter.php deleted file mode 100644 index e1d94f701..000000000 --- a/library/Message/Formatter.php +++ /dev/null @@ -1,49 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Message; - -use function call_user_func; -use function preg_replace_callback; -use function Respect\Stringifier\stringify; - -final class Formatter -{ - /** - * @var callable - */ - private $translator; - - public function __construct( - callable $translator, - private readonly ParameterStringifier $parameterStringifier - ) { - $this->translator = $translator; - } - - /** - * @param mixed[] $parameters - */ - public function format(string $template, mixed $input, array $parameters): string - { - $parameters['name'] = $parameters['name'] ?? stringify($input); - - return preg_replace_callback( - '/{{(\w+)}}/', - function ($match) use ($parameters) { - if (!isset($parameters[$match[1]])) { - return $match[0]; - } - - return $this->parameterStringifier->stringify($match[1], $parameters[$match[1]]); - }, - call_user_func($this->translator, $template) - ); - } -} diff --git a/library/Message/TemplateRenderer.php b/library/Message/TemplateRenderer.php new file mode 100644 index 000000000..9b7051880 --- /dev/null +++ b/library/Message/TemplateRenderer.php @@ -0,0 +1,74 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Message; + +use Respect\Validation\Exceptions\ComponentException; +use Throwable; + +use function call_user_func; +use function is_scalar; +use function preg_replace_callback; +use function Respect\Stringifier\stringify; +use function sprintf; + +final class TemplateRenderer +{ + /** @var callable */ + private $translator; + + public function __construct( + callable $translator, + private readonly ParameterStringifier $parameterStringifier + ) { + $this->translator = $translator; + } + + /** + * @param mixed[] $parameters + */ + public function render(string $template, mixed $input, array $parameters): string + { + $parameters['name'] ??= $this->parameterStringifier->stringify('input', $input); + + return (string) preg_replace_callback( + '/{{(\w+)(\|(trans|raw))?}}/', + function (array $matches) use ($parameters): string { + if (!isset($parameters[$matches[1]])) { + return $matches[0]; + } + + $modifier = $matches[3] ?? null; + if ($modifier === 'raw' && is_scalar($parameters[$matches[1]])) { + return (string) $parameters[$matches[1]]; + } + + if ($modifier === 'trans') { + return $this->translate($parameters[$matches[1]]); + } + + return $this->parameterStringifier->stringify($matches[1], $parameters[$matches[1]]); + }, + $this->translate($template) + ); + } + + private function translate(mixed $message): string + { + if (!is_scalar($message)) { + throw new ComponentException(sprintf('Cannot translate scalar value "%s"', stringify($message))); + } + + try { + return call_user_func($this->translator, (string) $message); + } catch (Throwable $throwable) { + throw new ComponentException(sprintf('Failed to translate "%s"', $message), 0, $throwable); + } + } +} diff --git a/library/Rules/Base.php b/library/Rules/Base.php index 63f106ac4..3e46708eb 100644 --- a/library/Rules/Base.php +++ b/library/Rules/Base.php @@ -18,8 +18,8 @@ use function sprintf; #[Template( - '{{name}} must be a number in the base {{base}}', - '{{name}} must not be a number in the base {{base}}', + '{{name}} must be a number in the base {{base|raw}}', + '{{name}} must not be a number in the base {{base|raw}}', )] final class Base extends AbstractRule { diff --git a/library/Rules/Charset.php b/library/Rules/Charset.php index b0850b259..7bc12c478 100644 --- a/library/Rules/Charset.php +++ b/library/Rules/Charset.php @@ -18,8 +18,8 @@ use function mb_list_encodings; #[Template( - '{{name}} must be in the {{charset}} charset', - '{{name}} must not be in the {{charset}} charset', + '{{name}} must be in the {{charset|raw}} charset', + '{{name}} must not be in the {{charset|raw}} charset', )] final class Charset extends AbstractRule { diff --git a/library/Rules/CreditCard.php b/library/Rules/CreditCard.php index 9e3ba1711..62fccec6b 100644 --- a/library/Rules/CreditCard.php +++ b/library/Rules/CreditCard.php @@ -20,13 +20,13 @@ use function sprintf; #[Template( - '{{name}} must be a valid Credit Card number', - '{{name}} must not be a valid Credit Card number', + '{{name}} must be a valid credit card number', + '{{name}} must not be a valid credit card number', self::TEMPLATE_STANDARD, )] #[Template( - '{{name}} must be a valid {{brand}} Credit Card number', - '{{name}} must not be a valid {{brand}} Credit Card number', + '{{name}} must be a valid {{brand|raw}} credit card number', + '{{name}} must not be a valid {{brand|raw}} credit card number', self::TEMPLATE_BRANDED, )] final class CreditCard extends AbstractRule diff --git a/library/Rules/Factor.php b/library/Rules/Factor.php index 05ad70050..5bbccddf8 100644 --- a/library/Rules/Factor.php +++ b/library/Rules/Factor.php @@ -16,8 +16,8 @@ use function is_numeric; #[Template( - '{{name}} must be a factor of {{dividend}}', - '{{name}} must not be a factor of {{dividend}}', + '{{name}} must be a factor of {{dividend|raw}}', + '{{name}} must not be a factor of {{dividend|raw}}', )] final class Factor extends AbstractRule { diff --git a/library/Rules/Instance.php b/library/Rules/Instance.php index 35f837354..2407ab07d 100644 --- a/library/Rules/Instance.php +++ b/library/Rules/Instance.php @@ -12,8 +12,8 @@ use Respect\Validation\Message\Template; #[Template( - '{{name}} must be an instance of {{instanceName}}', - '{{name}} must not be an instance of {{instanceName}}', + '{{name}} must be an instance of `{{instanceName|raw}}`', + '{{name}} must not be an instance of `{{instanceName|raw}}`', )] final class Instance extends AbstractRule { diff --git a/library/Rules/Ip.php b/library/Rules/Ip.php index bf8da26de..5c98c6c69 100644 --- a/library/Rules/Ip.php +++ b/library/Rules/Ip.php @@ -33,8 +33,8 @@ self::TEMPLATE_STANDARD, )] #[Template( - '{{name}} must be an IP address in the {{range}} range', - '{{name}} must not be an IP address in the {{range}} range', + '{{name}} must be an IP address in the {{range|raw}} range', + '{{name}} must not be an IP address in the {{range|raw}} range', self::TEMPLATE_NETWORK_RANGE, )] final class Ip extends AbstractRule diff --git a/library/Rules/LanguageCode.php b/library/Rules/LanguageCode.php index d5c45fd3d..719b2a0dd 100644 --- a/library/Rules/LanguageCode.php +++ b/library/Rules/LanguageCode.php @@ -18,8 +18,8 @@ use function sprintf; #[Template( - '{{name}} must be a valid ISO 639 {{set}} language code', - '{{name}} must not be a valid ISO 639 {{set}} language code', + '{{name}} must be a valid ISO 639 {{set|raw}} language code', + '{{name}} must not be a valid ISO 639 {{set|raw}} language code', )] final class LanguageCode extends AbstractEnvelope { diff --git a/library/Rules/MaxAge.php b/library/Rules/MaxAge.php index 8a15f9a17..ab6fbe585 100644 --- a/library/Rules/MaxAge.php +++ b/library/Rules/MaxAge.php @@ -12,8 +12,8 @@ use Respect\Validation\Message\Template; #[Template( - '{{name}} must be {{age}} years or less', - '{{name}} must not be {{age}} years or less', + '{{name}} must be {{age|raw}} years or less', + '{{name}} must not be {{age|raw}} years or less', )] final class MaxAge extends AbstractAge { diff --git a/library/Rules/MinAge.php b/library/Rules/MinAge.php index e25ec5b16..94ef9dde6 100644 --- a/library/Rules/MinAge.php +++ b/library/Rules/MinAge.php @@ -12,8 +12,8 @@ use Respect\Validation\Message\Template; #[Template( - '{{name}} must be {{age}} years or more', - '{{name}} must not be {{age}} years or more', + '{{name}} must be {{age|raw}} years or more', + '{{name}} must not be {{age|raw}} years or more', )] final class MinAge extends AbstractAge { diff --git a/library/Rules/Phone.php b/library/Rules/Phone.php index cc9fc3fa4..4ef2c072a 100644 --- a/library/Rules/Phone.php +++ b/library/Rules/Phone.php @@ -26,8 +26,8 @@ self::TEMPLATE_INTERNATIONAL, )] #[Template( - '{{name}} must be a valid telephone number for country {{countryName}}', - '{{name}} must not be a valid telephone number for country {{countryName}}', + '{{name}} must be a valid telephone number for country {{countryName|trans}}', + '{{name}} must not be a valid telephone number for country {{countryName|trans}}', self::TEMPLATE_FOR_COUNTRY, )] final class Phone extends AbstractRule diff --git a/library/Rules/Printable.php b/library/Rules/Printable.php index f626242b3..8eea86df5 100644 --- a/library/Rules/Printable.php +++ b/library/Rules/Printable.php @@ -19,8 +19,8 @@ self::TEMPLATE_STANDARD, )] #[Template( - '{{name}} must contain only printable characters and "{{additionalChars}}"', - '{{name}} must not contain printable characters or "{{additionalChars}}"', + '{{name}} must contain only printable characters and {{additionalChars}}', + '{{name}} must not contain printable characters or {{additionalChars}}', self::TEMPLATE_EXTRA, )] final class Printable extends AbstractFilterRule diff --git a/library/Rules/Regex.php b/library/Rules/Regex.php index 0c2eb1c7f..cb5956d1c 100644 --- a/library/Rules/Regex.php +++ b/library/Rules/Regex.php @@ -15,8 +15,8 @@ use function preg_match; #[Template( - '{{name}} must validate against {{regex}}', - '{{name}} must not validate against {{regex}}', + '{{name}} must validate against `{{regex|raw}}`', + '{{name}} must not validate against `{{regex|raw}}`', )] final class Regex extends AbstractRule { diff --git a/library/Rules/SubdivisionCode.php b/library/Rules/SubdivisionCode.php index 0bfa0a030..06525947d 100644 --- a/library/Rules/SubdivisionCode.php +++ b/library/Rules/SubdivisionCode.php @@ -20,8 +20,8 @@ use function str_replace; #[Template( - '{{name}} must be a subdivision code of {{countryName}}', - '{{name}} must not be a subdivision code of {{countryName}}', + '{{name}} must be a subdivision code of {{countryName|trans}}', + '{{name}} must not be a subdivision code of {{countryName|trans}}', )] final class SubdivisionCode extends AbstractSearcher { diff --git a/library/Rules/Uuid.php b/library/Rules/Uuid.php index 16a3a0bb3..67116a2f4 100644 --- a/library/Rules/Uuid.php +++ b/library/Rules/Uuid.php @@ -22,8 +22,8 @@ self::TEMPLATE_STANDARD, )] #[Template( - '{{name}} must be a valid UUID version {{version}}', - '{{name}} must not be a valid UUID version {{version}}', + '{{name}} must be a valid UUID version {{version|raw}}', + '{{name}} must not be a valid UUID version {{version|raw}}', self::TEMPLATE_VERSION, )] final class Uuid extends AbstractRule diff --git a/library/Rules/VideoUrl.php b/library/Rules/VideoUrl.php index 562be963f..5b88380b1 100644 --- a/library/Rules/VideoUrl.php +++ b/library/Rules/VideoUrl.php @@ -24,8 +24,8 @@ self::TEMPLATE_STANDARD, )] #[Template( - '{{name}} must be a valid {{service}} video URL', - '{{name}} must not be a valid {{service}} video URL', + '{{name}} must be a valid {{service|raw}} video URL', + '{{name}} must not be a valid {{service|raw}} video URL', self::TEMPLATE_SERVICE, )] final class VideoUrl extends AbstractRule diff --git a/tests/integration/rules/creditCard.phpt b/tests/integration/rules/creditCard.phpt index 841ef1597..ce81ed46f 100644 --- a/tests/integration/rules/creditCard.phpt +++ b/tests/integration/rules/creditCard.phpt @@ -13,7 +13,7 @@ exceptionFullMessage(static fn() => v::creditCard('MasterCard')->assert(35660020 exceptionFullMessage(static fn() => v::not(v::creditCard())->assert(5555444433331111)); ?> --EXPECT-- -3566002020360505 must be a valid "Discover" Credit Card number -4024007153361885 must not be a valid "Visa" Credit Card number -- 3566002020360505 must be a valid "MasterCard" Credit Card number -- 5555444433331111 must not be a valid Credit Card number +3566002020360505 must be a valid Discover credit card number +4024007153361885 must not be a valid Visa credit card number +- 3566002020360505 must be a valid MasterCard credit card number +- 5555444433331111 must not be a valid credit card number diff --git a/tests/integration/rules/ip.phpt b/tests/integration/rules/ip.phpt index f7546c721..0bd6eefdf 100644 --- a/tests/integration/rules/ip.phpt +++ b/tests/integration/rules/ip.phpt @@ -24,9 +24,9 @@ if (!extension_loaded('bcmath')) { --EXPECT-- "257.0.0.1" must be an IP address "127.0.0.1" must not be an IP address -"127.0.0.1" must be an IP address in the "127.0.1.0-127.0.1.255" range -"127.0.1.1" must not be an IP address in the "127.0.1.0-127.0.1.255" range +"127.0.0.1" must be an IP address in the 127.0.1.0-127.0.1.255 range +"127.0.1.1" must not be an IP address in the 127.0.1.0-127.0.1.255 range - "257.0.0.1" must be an IP address - "127.0.0.1" must not be an IP address -- "127.0.0.1" must be an IP address in the "127.0.1.0-127.0.1.255" range -- "127.0.1.1" must not be an IP address in the "127.0.1.0-127.0.1.255" range +- "127.0.0.1" must be an IP address in the 127.0.1.0-127.0.1.255 range +- "127.0.1.1" must not be an IP address in the 127.0.1.0-127.0.1.255 range diff --git a/tests/integration/rules/languageCode.phpt b/tests/integration/rules/languageCode.phpt index 4154a54b7..05471ff04 100644 --- a/tests/integration/rules/languageCode.phpt +++ b/tests/integration/rules/languageCode.phpt @@ -13,7 +13,7 @@ exceptionFullMessage(static fn() => v::languageCode()->assert('por')); exceptionFullMessage(static fn() => v::not(v::languageCode())->assert('en')); ?> --EXPECT-- -`null` must be a valid ISO 639 "alpha-2" language code -"pt" must not be a valid ISO 639 "alpha-2" language code -- "por" must be a valid ISO 639 "alpha-2" language code -- "en" must not be a valid ISO 639 "alpha-2" language code +`null` must be a valid ISO 639 alpha-2 language code +"pt" must not be a valid ISO 639 alpha-2 language code +- "por" must be a valid ISO 639 alpha-2 language code +- "en" must not be a valid ISO 639 alpha-2 language code diff --git a/tests/integration/rules/regex.phpt b/tests/integration/rules/regex.phpt index ff9471635..5e6fe9523 100644 --- a/tests/integration/rules/regex.phpt +++ b/tests/integration/rules/regex.phpt @@ -13,7 +13,7 @@ exceptionFullMessage(static fn() => v::regex('/^w+$/')->assert(new stdClass())); exceptionFullMessage(static fn() => v::not(v::regex('/^[a-z]+$/i'))->assert('wPoiur')); ?> --EXPECT-- -"w poiur" must validate against "/^w+$/" -"wpoiur" must not validate against "/^[a-z]+$/" -- `stdClass {}` must validate against "/^w+$/" -- "wPoiur" must not validate against "/^[a-z]+$/i" +"w poiur" must validate against `/^w+$/` +"wpoiur" must not validate against `/^[a-z]+$/` +- `stdClass {}` must validate against `/^w+$/` +- "wPoiur" must not validate against `/^[a-z]+$/i` diff --git a/tests/integration/rules/videoUrl.phpt b/tests/integration/rules/videoUrl.phpt index 89af5e106..a0e34f646 100644 --- a/tests/integration/rules/videoUrl.phpt +++ b/tests/integration/rules/videoUrl.phpt @@ -18,10 +18,10 @@ exceptionFullMessage(static fn() => v::not(v::videoUrl('Vimeo'))->assert('https: ?> --EXPECT-- "example.com" must be a valid video URL -"example.com" must be a valid "YouTube" video URL +"example.com" must be a valid YouTube video URL "https://player.vimeo.com/video/7178746722" must not be a valid video URL -"https://www.youtube.com/embed/netHLn9TScY" must not be a valid "YouTube" video URL +"https://www.youtube.com/embed/netHLn9TScY" must not be a valid YouTube video URL - "example.com" must be a valid video URL -- "example.com" must be a valid "Vimeo" video URL +- "example.com" must be a valid Vimeo video URL - "https://youtu.be/netHLn9TScY" must not be a valid video URL -- "https://vimeo.com/71787467" must not be a valid "Vimeo" video URL +- "https://vimeo.com/71787467" must not be a valid Vimeo video URL diff --git a/tests/integration/translator-assert.phpt b/tests/integration/translator-assert.phpt index 9f650c770..f2564668a 100644 --- a/tests/integration/translator-assert.phpt +++ b/tests/integration/translator-assert.phpt @@ -18,13 +18,17 @@ Factory::setDefaultInstance( => '{{name}} deve ser do tipo string', '{{name}} must have a length between {{minValue}} and {{maxValue}}' => '{{name}} deve possuir de {{minValue}} a {{maxValue}} caracteres', + '{{name}} must be a valid telephone number for country {{countryName|trans}}' + => '{{name}} deve ser um número de telefone válido para o país {{countryName|trans}}', + 'United States' => 'Estados Unidos', ][$message] ?? $message; }) ); -exceptionFullMessage(static fn() => Validator::stringType()->length(2, 15)->assert(0)); +exceptionFullMessage(static fn() => Validator::stringType()->length(2, 15)->phone('US')->assert(0)); ?> --EXPECT-- - Todas as regras requeridas devem passar para 0 - 0 deve ser do tipo string - 0 deve possuir de 2 a 15 caracteres + - 0 deve ser um número de telefone válido para o país Estados Unidos diff --git a/tests/library/Message/TestingParameterStringifier.php b/tests/library/Message/TestingParameterStringifier.php new file mode 100644 index 000000000..78ae4134f --- /dev/null +++ b/tests/library/Message/TestingParameterStringifier.php @@ -0,0 +1,23 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Test\Message; + +use Respect\Validation\Message\ParameterStringifier; + +use function json_encode; +use function sprintf; + +final class TestingParameterStringifier implements ParameterStringifier +{ + public function stringify(string $name, mixed $value): string + { + return sprintf('<%s:%s>', $name, json_encode($value)); + } +} diff --git a/tests/library/Stubs/CompositeSub.php b/tests/library/Stubs/CompositeSub.php index df18fbef6..92e2b3d2b 100644 --- a/tests/library/Stubs/CompositeSub.php +++ b/tests/library/Stubs/CompositeSub.php @@ -9,8 +9,8 @@ namespace Respect\Validation\Test\Stubs; -use Respect\Validation\Message\Formatter; use Respect\Validation\Message\Stringifier\KeepOriginalStringName; +use Respect\Validation\Message\TemplateRenderer; use Respect\Validation\Rules\AbstractComposite; use Respect\Validation\Test\Exceptions\CompositeStubException; use Respect\Validation\Validatable; @@ -33,7 +33,7 @@ public function reportError(mixed $input, array $extraParameters = []): Composit params: $extraParameters, template: Validatable::TEMPLATE_STANDARD, templates: [], - formatter: new Formatter(static fn ($value) => $value, new KeepOriginalStringName()) + formatter: new TemplateRenderer(static fn ($value) => $value, new KeepOriginalStringName()) ); } } diff --git a/tests/unit/Exceptions/NestedValidationExceptionTest.php b/tests/unit/Exceptions/NestedValidationExceptionTest.php index ffe6915e8..9109654f2 100644 --- a/tests/unit/Exceptions/NestedValidationExceptionTest.php +++ b/tests/unit/Exceptions/NestedValidationExceptionTest.php @@ -12,8 +12,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; -use Respect\Validation\Message\Formatter; use Respect\Validation\Message\Stringifier\KeepOriginalStringName; +use Respect\Validation\Message\TemplateRenderer; use Respect\Validation\Test\TestCase; use Respect\Validation\Validatable; @@ -51,7 +51,7 @@ public function createNestedValidationException(): NestedValidationException params: [], template: Validatable::TEMPLATE_STANDARD, templates: [], - formatter: new Formatter('strval', new KeepOriginalStringName()) + formatter: new TemplateRenderer('strval', new KeepOriginalStringName()) ); } @@ -63,7 +63,7 @@ public function createValidationException(): ValidationException params: [], template: Validatable::TEMPLATE_STANDARD, templates: [], - formatter: new Formatter('strval', new KeepOriginalStringName()) + formatter: new TemplateRenderer('strval', new KeepOriginalStringName()) ); } } diff --git a/tests/unit/Exceptions/ValidationExceptionTest.php b/tests/unit/Exceptions/ValidationExceptionTest.php index 3c0ec6071..b917b7acd 100644 --- a/tests/unit/Exceptions/ValidationExceptionTest.php +++ b/tests/unit/Exceptions/ValidationExceptionTest.php @@ -12,9 +12,9 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; -use Respect\Validation\Message\Formatter; use Respect\Validation\Message\Stringifier\KeepOriginalStringName; use Respect\Validation\Message\Template; +use Respect\Validation\Message\TemplateRenderer; use Respect\Validation\Test\TestCase; use Respect\Validation\Validatable; @@ -108,7 +108,7 @@ public function itShouldUseFormatter(): void $template = ' This is my new template '; $expected = trim($template); - $sut = $this->createValidationException(formatter: new Formatter('trim', new KeepOriginalStringName())); + $sut = $this->createValidationException(formatter: new TemplateRenderer('trim', new KeepOriginalStringName())); $sut->updateTemplate($template); self::assertEquals($expected, $sut->getMessage()); @@ -166,7 +166,7 @@ private function createValidationException( array $params = [], string $template = Validatable::TEMPLATE_STANDARD, array $templates = [], - Formatter $formatter = new Formatter('strval', new KeepOriginalStringName()) + TemplateRenderer $formatter = new TemplateRenderer('strval', new KeepOriginalStringName()) ): ValidationException { return new ValidationException($input, $id, $params, $template, $templates, $formatter); } diff --git a/tests/unit/Message/TemplateRendererTest.php b/tests/unit/Message/TemplateRendererTest.php new file mode 100644 index 000000000..52a84fe58 --- /dev/null +++ b/tests/unit/Message/TemplateRendererTest.php @@ -0,0 +1,182 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Message; + +use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use Respect\Validation\Exceptions\ComponentException; +use Respect\Validation\Test\Message\TestingParameterStringifier; +use Respect\Validation\Test\TestCase; +use stdClass; + +use function sprintf; + +#[CoversClass(TemplateRenderer::class)] +final class TemplateRendererTest extends TestCase +{ + #[Test] + public function itShouldRenderMessageWithItsTemplate(): void + { + $renderer = new TemplateRenderer(static fn(string $value) => $value, new TestingParameterStringifier()); + + $template = 'This is my template'; + + self::assertSame($template, $renderer->render($template, 'input', [])); + } + + #[Test] + public function itShouldReplaceParameters(): void + { + $parameterStringifier = new TestingParameterStringifier(); + + $renderer = new TemplateRenderer(static fn(string $value) => $value, $parameterStringifier); + + $key = 'foo'; + $value = 42; + + $expected = 'Will replace ' . $parameterStringifier->stringify($key, $value); + $actual = $renderer->render('Will replace {{foo}}', 'input', [$key => $value]); + + self::assertSame($expected, $actual); + } + + #[Test] + public function itShouldReplaceNameWithStringifiedInputWhenThereIsNoName(): void + { + $parameterStringifier = new TestingParameterStringifier(); + + $renderer = new TemplateRenderer(static fn(string $value) => $value, $parameterStringifier); + + $message = 'Will replace {{name}}'; + $input = 'input'; + + $expected = 'Will replace ' . $parameterStringifier->stringify( + 'name', + $parameterStringifier->stringify('input', $input), + ); + $actual = $renderer->render($message, $input, []); + + self::assertSame($expected, $actual); + } + + #[Test] + public function itShouldKeepNameWhenDefined(): void + { + $parameterStringifier = new TestingParameterStringifier(); + + $renderer = new TemplateRenderer(static fn(string $value) => $value, $parameterStringifier); + + $name = 'real name'; + + $expected = 'Will replace ' . $parameterStringifier->stringify('name', $name); + $actual = $renderer->render('Will replace {{name}}', 'input', ['name' => $name]); + + self::assertSame($expected, $actual); + } + + #[Test] + public function itShouldKeepUnknownParameters(): void + { + $renderer = new TemplateRenderer(static fn(string $value) => $value, new TestingParameterStringifier()); + + $expected = 'Will not replace {{unknown}}'; + $actual = $renderer->render($expected, 'input', []); + + self::assertSame($expected, $actual); + } + + #[Test] + public function itShouldRenderTranslateTemplate(): void + { + $template = 'This is my template with {{foo}}'; + $translations = [$template => 'This is my translated template with {{foo}}']; + + $renderer = new TemplateRenderer( + static fn(string $value) => $translations[$value], + new TestingParameterStringifier() + ); + + $expected = $translations[$template]; + $actual = $renderer->render($template, 'input', []); + + self::assertSame($expected, $actual); + } + + #[Test] + public function itShouldThrowAnExceptionWhenTranslateThrowsAnException(): void + { + $template = 'This is my template'; + + $this->expectException(ComponentException::class); + $this->expectExceptionMessage(sprintf('Failed to translate "%s"', $template)); + + $renderer = new TemplateRenderer( + static fn(string $value) => throw new Exception(), + new TestingParameterStringifier() + ); + $renderer->render($template, 'input', []); + } + + #[Test] + public function itShouldRenderTranslateParameter(): void + { + $parameterOriginal = 'original'; + $parameterTranslated = 'translated'; + + $template = 'This is my template with {{foo|trans}}'; + + $translations = [ + $parameterOriginal => $parameterTranslated, + $template => 'This is my translated template with {{foo|trans}}', + ]; + + $renderer = new TemplateRenderer( + static fn(string $value) => $translations[$value], + new TestingParameterStringifier() + ); + + $parameters = ['foo' => $parameterOriginal]; + + $expected = 'This is my translated template with translated'; + $actual = $renderer->render($template, 'input', $parameters); + + self::assertSame($expected, $actual); + } + + #[Test] + public function itShouldThrowAnExceptionWhenTranslateParameterIsNotScalar(): void + { + $parameterValue = new stdClass(); + + $template = 'This is my template with {{foo|trans}}'; + + $renderer = new TemplateRenderer(static fn(string $value) => $value, new TestingParameterStringifier()); + + $this->expectException(ComponentException::class); + + $renderer->render($template, 'input', ['foo' => $parameterValue]); + } + + #[Test] + public function itShouldRenderRawParameter(): void + { + $raw = 'raw'; + + $template = 'This is my template with {{foo|raw}}'; + + $renderer = new TemplateRenderer(static fn(string $value) => $value, new TestingParameterStringifier()); + + $expected = 'This is my template with raw'; + $actual = $renderer->render($template, 'input', ['foo' => $raw]); + + self::assertSame($expected, $actual); + } +} diff --git a/tests/unit/Rules/PhoneTest.php b/tests/unit/Rules/PhoneTest.php index 8aa5dd8c6..78555f4c9 100644 --- a/tests/unit/Rules/PhoneTest.php +++ b/tests/unit/Rules/PhoneTest.php @@ -23,7 +23,7 @@ public function testThrowsExceptionWithCountryName(): void $phoneValidator = new Phone('BR'); $this->expectException(ValidationException::class); - $this->expectExceptionMessage('"abc" must be a valid telephone number for country "Brazil"'); + $this->expectExceptionMessage('"abc" must be a valid telephone number for country Brazil'); $phoneValidator->assert('abc'); } diff --git a/tests/unit/Rules/SubdivisionCodeTest.php b/tests/unit/Rules/SubdivisionCodeTest.php index 74077dd37..75426861e 100644 --- a/tests/unit/Rules/SubdivisionCodeTest.php +++ b/tests/unit/Rules/SubdivisionCodeTest.php @@ -63,7 +63,7 @@ public function shouldThrowsValidationException(): void $countrySubdivision = new SubdivisionCode('BR'); $this->expectException(ValidationException::class); - $this->expectExceptionMessage('"CA" must be a subdivision code of "Brazil"'); + $this->expectExceptionMessage('"CA" must be a subdivision code of Brazil'); $countrySubdivision->assert('CA'); }