From d3b1dcb64ec561cdedffe5ca779341fc9452a858 Mon Sep 17 00:00:00 2001 From: Romain Canon Date: Sun, 10 Jul 2022 18:17:16 +0200 Subject: [PATCH] feat!: refactor tree node API The class `\CuyZ\Valinor\Mapper\Tree\Node` has been refactored to remove access to unwanted methods that were not supposed to be part of the public API. Below are a list of all changes: - New methods `$node->sourceFilled()` and `$node->sourceValue()` allow accessing the source value. - The method `$node->value()` has been renamed to `$node->mappedValue()` and will throw an exception if the node is not value. - The method `$node->type()` now returns a string. - The methods `$message->name()`, `$message->path()`, `$message->type()` and `$message->value()` have been deprecated in favor of the new method `$message->node()`. - The message parameter `{original_value}` has been deprecated in favor of `{source_value}`. --- docs/pages/message-customization.md | 8 +- src/Mapper/Tree/Builder/ArrayNodeBuilder.php | 13 +- src/Mapper/Tree/Builder/CasterNodeBuilder.php | 3 +- .../Tree/Builder/CasterProxyNodeBuilder.php | 5 +- src/Mapper/Tree/Builder/ClassNodeBuilder.php | 9 +- src/Mapper/Tree/Builder/EnumNodeBuilder.php | 5 +- .../Tree/Builder/ErrorCatcherNodeBuilder.php | 5 +- .../Tree/Builder/InterfaceNodeBuilder.php | 7 +- .../Tree/Builder/IterableNodeBuilder.php | 3 +- src/Mapper/Tree/Builder/ListNodeBuilder.php | 13 +- src/Mapper/Tree/Builder/NodeBuilder.php | 3 +- src/Mapper/Tree/Builder/RootNodeBuilder.php | 3 +- src/Mapper/Tree/Builder/ScalarNodeBuilder.php | 5 +- .../Tree/Builder/ShapedArrayNodeBuilder.php | 11 +- .../Tree/Builder/ShellVisitorNodeBuilder.php | 3 +- src/Mapper/Tree/Builder/StrictNodeBuilder.php | 5 +- src/Mapper/Tree/Builder/TreeNode.php | 169 +++++++++++++ src/Mapper/Tree/Builder/UnionNodeBuilder.php | 3 +- .../Tree/Builder/ValueAlteringNodeBuilder.php | 3 +- .../Exception/CannotGetInvalidNodeValue.php | 20 -- .../Exception/InvalidNodeHasNoMappedValue.php | 19 ++ .../Exception/SourceValueWasNotFilled.php | 19 ++ .../Message/Formatter/MessageMapFormatter.php | 2 +- .../Formatter/PlaceHolderMessageFormatter.php | 7 +- src/Mapper/Tree/Message/NodeMessage.php | 65 ++--- src/Mapper/Tree/Node.php | 194 +++++++-------- src/Mapper/TreeMapperContainer.php | 6 +- .../Mapper/Tree/Builder/FakeNodeBuilder.php | 10 +- .../Builder/FakeTreeNode.php} | 23 +- tests/Fake/Mapper/Tree/FakeNode.php | 48 ++++ .../Mapper/Tree/Message/FakeNodeMessage.php | 16 +- tests/Unit/Mapper/MappingErrorTest.php | 2 +- .../Unit/Mapper/Tree/Builder/TreeNodeTest.php | 182 ++++++++++++++ .../PlaceHolderMessageFormatterTest.php | 16 +- .../TranslationMessageFormatterTest.php | 10 +- .../Tree/Message/MessagesFlattenerTest.php | 21 +- .../Mapper/Tree/Message/NodeMessageTest.php | 53 ++--- tests/Unit/Mapper/Tree/NodeTest.php | 222 ++++++------------ tests/Unit/Mapper/Tree/NodeTraverserTest.php | 16 +- 39 files changed, 767 insertions(+), 460 deletions(-) create mode 100644 src/Mapper/Tree/Builder/TreeNode.php delete mode 100644 src/Mapper/Tree/Exception/CannotGetInvalidNodeValue.php create mode 100644 src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php create mode 100644 src/Mapper/Tree/Exception/SourceValueWasNotFilled.php rename tests/Fake/Mapper/{FakeNode.php => Tree/Builder/FakeTreeNode.php} (70%) create mode 100644 tests/Fake/Mapper/Tree/FakeNode.php create mode 100644 tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php diff --git a/docs/pages/message-customization.md b/docs/pages/message-customization.md index 06182eb..6898a36 100644 --- a/docs/pages/message-customization.md +++ b/docs/pages/message-customization.md @@ -12,7 +12,7 @@ on the original message. | `{node_name}` | name of the node to which the message is bound | | `{node_path}` | path of the node to which the message is bound | | `{node_type}` | type of the node to which the message is bound | -| `{original_value}` | the source value that was given to the node | +| `{source_value}` | the source value that was given to the node | | `{original_message}` | the original message before being customized | Usage: @@ -46,9 +46,9 @@ try { } catch (\CuyZ\Valinor\Mapper\MappingError $error) { $message = $error->node()->messages()[0]; - if (is_numeric($message->value())) { + if (is_numeric($message->node()->mappedValue())) { $message = $message->withBody( - 'Invalid amount {original_value, number, currency}' + 'Invalid amount {source_value, number, currency}' ); } @@ -133,7 +133,7 @@ In any case, the content can contain placeholders as described 'Some message content' => 'New content / previous: {original_message}', // Will match if the given message is an instance of `SomeError` - SomeError::class => 'New content / value: {original_value}', + SomeError::class => 'New content / value: {source_value}', // A callback can be used to get access to the message instance OtherError::class => function (NodeMessage $message): string { diff --git a/src/Mapper/Tree/Builder/ArrayNodeBuilder.php b/src/Mapper/Tree/Builder/ArrayNodeBuilder.php index 30e9bca..2e3de61 100644 --- a/src/Mapper/Tree/Builder/ArrayNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ArrayNodeBuilder.php @@ -6,12 +6,9 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidTraversableKey; use CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\CompositeTraversableType; - use CuyZ\Valinor\Type\Types\ArrayType; - use CuyZ\Valinor\Type\Types\IterableType; use CuyZ\Valinor\Type\Types\NonEmptyArrayType; @@ -28,7 +25,7 @@ final class ArrayNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); $value = $shell->hasValue() ? $shell->value() : null; @@ -36,7 +33,7 @@ final class ArrayNodeBuilder implements NodeBuilder assert($type instanceof ArrayType || $type instanceof NonEmptyArrayType || $type instanceof IterableType); if (null === $value && $this->flexible) { - return Node::branch($shell, [], []); + return TreeNode::branch($shell, [], []); } if (! is_array($value)) { @@ -46,11 +43,11 @@ final class ArrayNodeBuilder implements NodeBuilder $children = $this->children($type, $shell, $rootBuilder); $array = $this->buildArray($children); - return Node::branch($shell, $array, $children); + return TreeNode::branch($shell, $array, $children); } /** - * @return array + * @return array */ private function children(CompositeTraversableType $type, Shell $shell, RootNodeBuilder $rootBuilder): array { @@ -74,7 +71,7 @@ final class ArrayNodeBuilder implements NodeBuilder } /** - * @param array $children + * @param array $children * @return mixed[]|null */ private function buildArray(array $children): ?array diff --git a/src/Mapper/Tree/Builder/CasterNodeBuilder.php b/src/Mapper/Tree/Builder/CasterNodeBuilder.php index 05098d9..20a6eb7 100644 --- a/src/Mapper/Tree/Builder/CasterNodeBuilder.php +++ b/src/Mapper/Tree/Builder/CasterNodeBuilder.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Exception\NoCasterForType; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; /** @internal */ @@ -22,7 +21,7 @@ final class CasterNodeBuilder implements NodeBuilder $this->builders = $builders; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); diff --git a/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php b/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php index 52b11f2..ba757bf 100644 --- a/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php +++ b/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; /** @internal */ @@ -17,13 +16,13 @@ final class CasterProxyNodeBuilder implements NodeBuilder $this->delegate = $delegate; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { if ($shell->hasValue()) { $value = $shell->value(); if ($shell->type()->accepts($value)) { - return Node::leaf($shell, $value); + return TreeNode::leaf($shell, $value); } } diff --git a/src/Mapper/Tree/Builder/ClassNodeBuilder.php b/src/Mapper/Tree/Builder/ClassNodeBuilder.php index 3ea0be6..d8385be 100644 --- a/src/Mapper/Tree/Builder/ClassNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ClassNodeBuilder.php @@ -10,7 +10,6 @@ use CuyZ\Valinor\Mapper\Object\FilledArguments; use CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder; use CuyZ\Valinor\Mapper\Object\ObjectBuilder; use CuyZ\Valinor\Mapper\Tree\Exception\UnexpectedArrayKeysForClass; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ClassType; @@ -41,7 +40,7 @@ final class ClassNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $classTypes = $this->classTypes($shell->type()); @@ -70,7 +69,7 @@ final class ClassNodeBuilder implements NodeBuilder $object = $this->buildObject($builder, $children); - $node = Node::branch($shell, $object, $children); + $node = TreeNode::branch($shell, $object, $children); if (! $this->flexible) { $node = $this->checkForUnexpectedKeys($arguments, $node); @@ -109,7 +108,7 @@ final class ClassNodeBuilder implements NodeBuilder } /** - * @param Node[] $children + * @param TreeNode[] $children */ private function buildObject(ObjectBuilder $builder, array $children): ?object { @@ -126,7 +125,7 @@ final class ClassNodeBuilder implements NodeBuilder return $builder->build($arguments); } - private function checkForUnexpectedKeys(FilledArguments $arguments, Node $node): Node + private function checkForUnexpectedKeys(FilledArguments $arguments, TreeNode $node): TreeNode { $superfluousKeys = $arguments->superfluousKeys(); diff --git a/src/Mapper/Tree/Builder/EnumNodeBuilder.php b/src/Mapper/Tree/Builder/EnumNodeBuilder.php index b391a27..f6c96ed 100644 --- a/src/Mapper/Tree/Builder/EnumNodeBuilder.php +++ b/src/Mapper/Tree/Builder/EnumNodeBuilder.php @@ -6,7 +6,6 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; use BackedEnum; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidEnumValue; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\Types\EnumType; use Stringable; @@ -28,7 +27,7 @@ final class EnumNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); $value = $shell->value(); @@ -37,7 +36,7 @@ final class EnumNodeBuilder implements NodeBuilder foreach ($type->className()::cases() as $case) { if ($this->valueMatchesEnumCase($value, $case)) { - return Node::leaf($shell, $case); + return TreeNode::leaf($shell, $case); } } diff --git a/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php b/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php index bec7da9..1ff592b 100644 --- a/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php @@ -7,7 +7,6 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage; use CuyZ\Valinor\Mapper\Tree\Message\Message; use CuyZ\Valinor\Mapper\Tree\Message\UserlandError; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use Throwable; @@ -28,7 +27,7 @@ final class ErrorCatcherNodeBuilder implements NodeBuilder $this->exceptionFilter = $exceptionFilter; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { try { return $this->delegate->build($shell, $rootBuilder); @@ -37,7 +36,7 @@ final class ErrorCatcherNodeBuilder implements NodeBuilder $exception = ($this->exceptionFilter)($exception->previous()); } - return Node::error($shell, $exception); + return TreeNode::error($shell, $exception); } } } diff --git a/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php b/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php index 1b746e5..11d2df5 100644 --- a/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php +++ b/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php @@ -10,7 +10,6 @@ use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory; use CuyZ\Valinor\Mapper\Object\FilledArguments; use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError; use CuyZ\Valinor\Mapper\Tree\Message\UserlandError; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\Types\ClassType; use CuyZ\Valinor\Type\Types\InterfaceType; @@ -44,7 +43,7 @@ final class InterfaceNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); @@ -63,7 +62,7 @@ final class InterfaceNodeBuilder implements NodeBuilder foreach ($children as $child) { if (! $child->isValid()) { - return Node::branch($shell, null, $children); + return TreeNode::branch($shell, null, $children); } $values[] = $child->value(); @@ -82,7 +81,7 @@ final class InterfaceNodeBuilder implements NodeBuilder } /** - * @return Node[] + * @return TreeNode[] */ private function children(Shell $shell, FilledArguments $arguments, RootNodeBuilder $rootBuilder): array { diff --git a/src/Mapper/Tree/Builder/IterableNodeBuilder.php b/src/Mapper/Tree/Builder/IterableNodeBuilder.php index add2743..08397d8 100644 --- a/src/Mapper/Tree/Builder/IterableNodeBuilder.php +++ b/src/Mapper/Tree/Builder/IterableNodeBuilder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use function is_array; @@ -21,7 +20,7 @@ final class IterableNodeBuilder implements NodeBuilder $this->delegate = $delegate; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { if ($shell->hasValue()) { $value = $shell->value(); diff --git a/src/Mapper/Tree/Builder/ListNodeBuilder.php b/src/Mapper/Tree/Builder/ListNodeBuilder.php index b9b229e..97631b6 100644 --- a/src/Mapper/Tree/Builder/ListNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ListNodeBuilder.php @@ -6,7 +6,6 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidListKey; use CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\CompositeTraversableType; use CuyZ\Valinor\Type\Types\ListType; @@ -25,7 +24,7 @@ final class ListNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); $value = $shell->hasValue() ? $shell->value() : null; @@ -33,7 +32,7 @@ final class ListNodeBuilder implements NodeBuilder assert($type instanceof ListType || $type instanceof NonEmptyListType); if (null === $value && $this->flexible) { - return Node::branch($shell, [], []); + return TreeNode::branch($shell, [], []); } if (! is_array($value)) { @@ -43,11 +42,11 @@ final class ListNodeBuilder implements NodeBuilder $children = $this->children($type, $shell, $rootBuilder); $array = $this->buildArray($children); - return Node::branch($shell, $array, $children); + return TreeNode::branch($shell, $array, $children); } /** - * @return array + * @return array */ private function children(CompositeTraversableType $type, Shell $shell, RootNodeBuilder $rootBuilder): array { @@ -64,7 +63,7 @@ final class ListNodeBuilder implements NodeBuilder $children[$expected] = $rootBuilder->build($child->withValue($value)); } else { $child = $shell->child((string)$key, $subType); - $children[$key] = Node::error($child, new InvalidListKey($key, $expected)); + $children[$key] = TreeNode::error($child, new InvalidListKey($key, $expected)); } $expected++; @@ -74,7 +73,7 @@ final class ListNodeBuilder implements NodeBuilder } /** - * @param array $children + * @param array $children * @return mixed[]|null */ private function buildArray(array $children): ?array diff --git a/src/Mapper/Tree/Builder/NodeBuilder.php b/src/Mapper/Tree/Builder/NodeBuilder.php index b98197a..52de275 100644 --- a/src/Mapper/Tree/Builder/NodeBuilder.php +++ b/src/Mapper/Tree/Builder/NodeBuilder.php @@ -2,11 +2,10 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; /** @internal */ interface NodeBuilder { - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node; + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode; } diff --git a/src/Mapper/Tree/Builder/RootNodeBuilder.php b/src/Mapper/Tree/Builder/RootNodeBuilder.php index 212aea0..c466ca0 100644 --- a/src/Mapper/Tree/Builder/RootNodeBuilder.php +++ b/src/Mapper/Tree/Builder/RootNodeBuilder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; /** @internal */ @@ -17,7 +16,7 @@ final class RootNodeBuilder $this->root = $root; } - public function build(Shell $shell): Node + public function build(Shell $shell): TreeNode { return $this->root->build($shell, $this); } diff --git a/src/Mapper/Tree/Builder/ScalarNodeBuilder.php b/src/Mapper/Tree/Builder/ScalarNodeBuilder.php index b49dfed..f9cff78 100644 --- a/src/Mapper/Tree/Builder/ScalarNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ScalarNodeBuilder.php @@ -6,7 +6,6 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Exception\CannotCastToScalarValue; use CuyZ\Valinor\Mapper\Tree\Exception\ValueNotAcceptedByScalarType; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\ScalarType; @@ -22,7 +21,7 @@ final class ScalarNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); $value = $shell->hasValue() ? $shell->value() : null; @@ -37,6 +36,6 @@ final class ScalarNodeBuilder implements NodeBuilder throw new CannotCastToScalarValue($value, $type); } - return Node::leaf($shell, $type->cast($value)); + return TreeNode::leaf($shell, $type->cast($value)); } } diff --git a/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php b/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php index 8f9f6fc..796b157 100644 --- a/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php @@ -7,7 +7,6 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Exception\ShapedArrayElementMissing; use CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable; use CuyZ\Valinor\Mapper\Tree\Exception\UnexpectedShapedArrayKeys; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\Types\ShapedArrayType; @@ -27,7 +26,7 @@ final class ShapedArrayNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); $value = $shell->hasValue() ? $shell->value() : null; @@ -41,11 +40,11 @@ final class ShapedArrayNodeBuilder implements NodeBuilder $children = $this->children($type, $shell, $rootBuilder); $array = $this->buildArray($children); - return Node::branch($shell, $array, $children); + return TreeNode::branch($shell, $array, $children); } /** - * @return array + * @return array */ private function children(ShapedArrayType $type, Shell $shell, RootNodeBuilder $rootBuilder): array { @@ -62,7 +61,7 @@ final class ShapedArrayNodeBuilder implements NodeBuilder if (! array_key_exists($key, $value)) { if (! $element->isOptional()) { - $children[$key] = Node::error($child, new ShapedArrayElementMissing($element)); + $children[$key] = TreeNode::error($child, new ShapedArrayElementMissing($element)); } continue; @@ -82,7 +81,7 @@ final class ShapedArrayNodeBuilder implements NodeBuilder } /** - * @param array $children + * @param array $children * @return mixed[]|null */ private function buildArray(array $children): ?array diff --git a/src/Mapper/Tree/Builder/ShellVisitorNodeBuilder.php b/src/Mapper/Tree/Builder/ShellVisitorNodeBuilder.php index d9ad326..a498fba 100644 --- a/src/Mapper/Tree/Builder/ShellVisitorNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ShellVisitorNodeBuilder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Mapper\Tree\Visitor\ShellVisitor; @@ -22,7 +21,7 @@ final class ShellVisitorNodeBuilder implements NodeBuilder $this->visitors = $visitors; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { foreach ($this->visitors as $visitor) { $shell = $visitor->visit($shell); diff --git a/src/Mapper/Tree/Builder/StrictNodeBuilder.php b/src/Mapper/Tree/Builder/StrictNodeBuilder.php index bc55e85..c261176 100644 --- a/src/Mapper/Tree/Builder/StrictNodeBuilder.php +++ b/src/Mapper/Tree/Builder/StrictNodeBuilder.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Exception\MissingNodeValue; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Utility\TypeHelper; @@ -22,7 +21,7 @@ final class StrictNodeBuilder implements NodeBuilder $this->flexible = $flexible; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { if (! $this->flexible) { TypeHelper::checkPermissiveType($shell->type()); @@ -30,7 +29,7 @@ final class StrictNodeBuilder implements NodeBuilder if (! $shell->hasValue()) { if ($this->flexible && $shell->type()->accepts(null)) { - return Node::leaf($shell, null); + return TreeNode::leaf($shell, null); } throw new MissingNodeValue($shell->type()); diff --git a/src/Mapper/Tree/Builder/TreeNode.php b/src/Mapper/Tree/Builder/TreeNode.php new file mode 100644 index 0000000..a643082 --- /dev/null +++ b/src/Mapper/Tree/Builder/TreeNode.php @@ -0,0 +1,169 @@ + */ + private array $children = []; + + /** @var array */ + private array $messages = []; + + private bool $valid = true; + + /** + * @param mixed $value + */ + private function __construct(Shell $shell, $value) + { + $this->shell = $shell; + $this->value = $value; + } + + /** + * @param mixed $value + */ + public static function leaf(Shell $shell, $value): self + { + $instance = new self($shell, $value); + $instance->check(); + + return $instance; + } + + /** + * @param mixed $value + * @param array $children + */ + public static function branch(Shell $shell, $value, array $children): self + { + $instance = new self($shell, $value); + + foreach ($children as $child) { + $name = $child->name(); + + if (isset($instance->children[$name])) { + throw new DuplicatedNodeChild($name); + } + + $instance->children[$name] = $child; + } + + $instance->check(); + + return $instance; + } + + /** + * @param Throwable&Message $message + */ + public static function error(Shell $shell, Throwable $message): self + { + return (new self($shell, null))->withMessage($message); + } + + public function name(): string + { + return $this->shell->name(); + } + + public function isValid(): bool + { + return $this->valid; + } + + /** + * @param mixed $value + */ + public function withValue($value): self + { + $clone = clone $this; + $clone->value = $value; + $clone->check(); + + return $clone; + } + + /** + * @return mixed + */ + public function value() + { + assert($this->valid, "Trying to get value of an invalid node at path `{$this->shell->path()}`."); + + return $this->value; + } + + public function withMessage(Message $message): self + { + $clone = clone $this; + $clone->messages[] = $message; + $clone->valid = $clone->valid && ! $message instanceof Throwable; + + return $clone; + } + + /** + * @return array + */ + public function children(): array + { + return $this->children; + } + + public function node(): Node + { + return $this->buildNode($this); + } + + private function check(): void + { + foreach ($this->children as $child) { + if (! $child->valid) { + $this->valid = false; + + return; + } + } + + if ($this->valid && ! $this->shell->type()->accepts($this->value)) { + throw new InvalidNodeValue($this->value, $this->shell->type()); + } + } + + private function buildNode(self $self): Node + { + return new Node( + $self->shell->isRoot(), + $self->shell->name(), + $self->shell->path(), + (string)$self->shell->type(), + $this->shell->hasValue(), + $this->shell->hasValue() ? $this->shell->value() : null, + $self->valid ? $self->value : null, + $self->messages, + array_map( + fn (self $child) => $self->buildNode($child), + $self->children + ) + ); + } +} diff --git a/src/Mapper/Tree/Builder/UnionNodeBuilder.php b/src/Mapper/Tree/Builder/UnionNodeBuilder.php index ae9f437..702af7f 100644 --- a/src/Mapper/Tree/Builder/UnionNodeBuilder.php +++ b/src/Mapper/Tree/Builder/UnionNodeBuilder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\Resolver\Union\UnionNarrower; use CuyZ\Valinor\Type\Types\UnionType; @@ -22,7 +21,7 @@ final class UnionNodeBuilder implements NodeBuilder $this->unionNarrower = $unionNarrower; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $type = $shell->type(); diff --git a/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php b/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php index 6176daf..77fbf8f 100644 --- a/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Builder; use CuyZ\Valinor\Definition\FunctionsContainer; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Shell; /** @internal */ @@ -21,7 +20,7 @@ final class ValueAlteringNodeBuilder implements NodeBuilder $this->functions = $functions; } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { $node = $this->delegate->build($shell, $rootBuilder); diff --git a/src/Mapper/Tree/Exception/CannotGetInvalidNodeValue.php b/src/Mapper/Tree/Exception/CannotGetInvalidNodeValue.php deleted file mode 100644 index f7e08e6..0000000 --- a/src/Mapper/Tree/Exception/CannotGetInvalidNodeValue.php +++ /dev/null @@ -1,20 +0,0 @@ -path()}`.", - 1630680246 - ); - } -} diff --git a/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php b/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php new file mode 100644 index 0000000..773c94f --- /dev/null +++ b/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php @@ -0,0 +1,19 @@ +isValid()`.", + 1657466305 + ); + } +} diff --git a/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php b/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php new file mode 100644 index 0000000..b3dbde2 --- /dev/null +++ b/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php @@ -0,0 +1,19 @@ +sourceFilled()`.", + 1657466107 + ); + } +} diff --git a/src/Mapper/Tree/Message/Formatter/MessageMapFormatter.php b/src/Mapper/Tree/Message/Formatter/MessageMapFormatter.php index fbac693..93789f9 100644 --- a/src/Mapper/Tree/Message/Formatter/MessageMapFormatter.php +++ b/src/Mapper/Tree/Message/Formatter/MessageMapFormatter.php @@ -37,7 +37,7 @@ use function is_callable; * 'Some message content' => 'New content / previous: {original_message}', * * // Will match if the given message is an instance of `SomeError` - * SomeError::class => 'New content / value: {original_value}', + * SomeError::class => 'New content / value: {source_value}', * * // A callback can be used to get access to the message instance * OtherError::class => function (NodeMessage $message): string { diff --git a/src/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatter.php b/src/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatter.php index 93692f0..8e20e19 100644 --- a/src/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatter.php +++ b/src/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatter.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Message\Formatter; use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage; -use CuyZ\Valinor\Utility\TypeHelper; use Throwable; /** @@ -44,9 +43,9 @@ final class PlaceHolderMessageFormatter implements MessageFormatter $body = sprintf($message->body(), ...$this->values ?: [ $message->code(), $originalMessage instanceof Throwable ? $originalMessage->getMessage() : $originalMessage->__toString(), - TypeHelper::dump($message->type()), - $message->name(), - $message->path(), + "`{$message->node()->type()}`", + $message->node()->name(), + $message->node()->path(), ]); return $message->withBody($body); diff --git a/src/Mapper/Tree/Message/NodeMessage.php b/src/Mapper/Tree/Message/NodeMessage.php index ff8485f..6ad89aa 100644 --- a/src/Mapper/Tree/Message/NodeMessage.php +++ b/src/Mapper/Tree/Message/NodeMessage.php @@ -4,18 +4,15 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree\Message; -use CuyZ\Valinor\Definition\Attributes; -use CuyZ\Valinor\Mapper\Tree\Shell; -use CuyZ\Valinor\Type\Type; +use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Utility\String\StringFormatter; -use CuyZ\Valinor\Utility\TypeHelper; use CuyZ\Valinor\Utility\ValueDumper; use Throwable; /** @api */ final class NodeMessage implements Message, HasCode { - private Shell $shell; + private Node $node; private Message $message; @@ -23,9 +20,9 @@ final class NodeMessage implements Message, HasCode private string $locale = StringFormatter::DEFAULT_LOCALE; - public function __construct(Shell $shell, Message $message) + public function __construct(Node $node, Message $message) { - $this->shell = $shell; + $this->node = $node; $this->message = $message; if ($this->message instanceof TranslatableMessage) { @@ -37,6 +34,11 @@ final class NodeMessage implements Message, HasCode } } + public function node(): Node + { + return $this->node; + } + public function withLocale(string $locale): self { $clone = clone $this; @@ -63,32 +65,38 @@ final class NodeMessage implements Message, HasCode return $this->body; } + /** + * @deprecated use `$message->node()->name()` instead + */ public function name(): string { - return $this->shell->name(); - } - - public function path(): string - { - return $this->shell->path(); - } - - public function type(): Type - { - return $this->shell->type(); - } - - public function attributes(): Attributes - { - return $this->shell->attributes(); + return $this->node->name(); } /** + * @deprecated use `$message->node()->path()` instead + */ + public function path(): string + { + return $this->node->path(); + } + + /** + * @deprecated use `$message->node()->type()` instead + */ + public function type(): string + { + return $this->node->type(); + } + + /** + * @deprecated use `$message->node()->mappedValue()` instead + * * @return mixed */ public function value() { - return $this->shell->value(); + return $this->node->mappedValue(); } public function originalMessage(): Message @@ -126,10 +134,11 @@ final class NodeMessage implements Message, HasCode { $parameters = [ 'message_code' => $this->code(), - 'node_name' => $this->shell->name(), - 'node_path' => $this->shell->path(), - 'node_type' => TypeHelper::dump($this->shell->type()), - 'original_value' => ValueDumper::dump($this->shell->hasValue() ? $this->shell->value() : '*missing*'), + 'node_name' => $this->node->name(), + 'node_path' => $this->node->path(), + 'node_type' => "`{$this->node->type()}`", + 'source_value' => $sourceValue = $this->node->sourceFilled() ? ValueDumper::dump($this->node->sourceValue()) : '*missing*', + 'original_value' => $sourceValue, // @deprecated 'original_message' => $this->message instanceof Throwable ? $this->message->getMessage() : $this->message->__toString(), ]; diff --git a/src/Mapper/Tree/Node.php b/src/Mapper/Tree/Node.php index 53aada3..5ba90c2 100644 --- a/src/Mapper/Tree/Node.php +++ b/src/Mapper/Tree/Node.php @@ -4,148 +4,134 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper\Tree; -use CuyZ\Valinor\Definition\Attributes; -use CuyZ\Valinor\Mapper\Tree\Exception\CannotGetInvalidNodeValue; -use CuyZ\Valinor\Mapper\Tree\Exception\DuplicatedNodeChild; -use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue; +use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeHasNoMappedValue; +use CuyZ\Valinor\Mapper\Tree\Exception\SourceValueWasNotFilled; use CuyZ\Valinor\Mapper\Tree\Message\Message; use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage; -use CuyZ\Valinor\Type\Type; -use Throwable; /** @api */ final class Node { - private Shell $shell; + private bool $isRoot; + + private string $name; + + private string $path; + + private string $type; + + private bool $sourceFilled; /** @var mixed */ - private $value; + private $sourceValue; - /** @var array */ - private array $children = []; + private bool $isValid = true; + + /** @var mixed */ + private $mappedValue; /** @var array */ private array $messages = []; - private bool $valid = true; + /** @var array */ + private array $children; /** - * @param mixed $value + * @param mixed $sourceValue + * @param mixed $mappedValue + * @param array $messages + * @param array $children */ - private function __construct(Shell $shell, $value) - { - $this->shell = $shell; - $this->value = $value; - } + public function __construct( + bool $isRoot, + string $name, + string $path, + string $type, + bool $sourceFilled, + $sourceValue, + $mappedValue, + array $messages, + array $children + ) { + $this->isRoot = $isRoot; + $this->name = $name; + $this->path = $path; + $this->type = $type; + $this->sourceFilled = $sourceFilled; + $this->sourceValue = $sourceValue; + $this->mappedValue = $mappedValue; + $this->children = $children; - /** - * @param mixed $value - */ - public static function leaf(Shell $shell, $value): self - { - $instance = new self($shell, $value); - $instance->check(); + foreach ($messages as $message) { + $message = new NodeMessage($this, $message); - return $instance; - } - - /** - * @param mixed $value - * @param array $children - */ - public static function branch(Shell $shell, $value, array $children): self - { - $instance = new self($shell, $value); - - foreach ($children as $child) { - $name = $child->name(); - - if (isset($instance->children[$name])) { - throw new DuplicatedNodeChild($name); - } - - $instance->children[$name] = $child; + $this->messages[] = $message; + $this->isValid = $this->isValid && ! $message->isError(); } - - $instance->check(); - - return $instance; - } - - /** - * @param Throwable&Message $message - */ - public static function error(Shell $shell, Throwable $message): self - { - return (new self($shell, null))->withMessage($message); - } - - public function name(): string - { - return $this->shell->name(); } public function isRoot(): bool { - return $this->shell->isRoot(); + return $this->isRoot; + } + + public function name(): string + { + return $this->name; } public function path(): string { - return $this->shell->path(); + return $this->path; } - public function type(): Type + public function type(): string { - return $this->shell->type(); + return $this->type; } - public function attributes(): Attributes + public function sourceFilled(): bool { - return $this->shell->attributes(); - } - - /** - * @param mixed $value - */ - public function withValue($value): self - { - $clone = clone $this; - $clone->value = $value; - $clone->check(); - - return $clone; + return $this->sourceFilled; } /** * @return mixed */ - public function value() + public function sourceValue() { - if (! $this->valid) { - throw new CannotGetInvalidNodeValue($this); + if (! $this->sourceFilled) { + throw new SourceValueWasNotFilled($this->path); } - return $this->value; + return $this->sourceValue; + } + + public function isValid(): bool + { + return $this->isValid; } /** - * @return array + * @return mixed */ - public function children(): array + public function mappedValue() { - return $this->children; + if (! $this->isValid) { + throw new InvalidNodeHasNoMappedValue($this->path); + } + + return $this->mappedValue; } - public function withMessage(Message $message): self + /** + * @deprecated use `$node->mappedValue()` instead + * + * @return mixed + */ + public function value() { - $message = new NodeMessage($this->shell, $message); - - $clone = clone $this; - $clone->messages[] = $message; - $clone->valid = $clone->valid && ! $message->isError(); - - return $clone; + return $this->mappedValue(); } /** @@ -156,23 +142,11 @@ final class Node return $this->messages; } - public function isValid(): bool + /** + * @return array + */ + public function children(): array { - return $this->valid; - } - - private function check(): void - { - foreach ($this->children as $child) { - if (! $child->valid) { - $this->valid = false; - - return; - } - } - - if ($this->valid && ! $this->shell->type()->accepts($this->value)) { - throw new InvalidNodeValue($this->value, $this->shell->type()); - } + return $this->children; } } diff --git a/src/Mapper/TreeMapperContainer.php b/src/Mapper/TreeMapperContainer.php index 60d8310..59279c7 100644 --- a/src/Mapper/TreeMapperContainer.php +++ b/src/Mapper/TreeMapperContainer.php @@ -6,7 +6,7 @@ namespace CuyZ\Valinor\Mapper; use CuyZ\Valinor\Mapper\Exception\InvalidMappingTypeSignature; use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder; -use CuyZ\Valinor\Mapper\Tree\Node; +use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode; use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Type\Parser\Exception\InvalidType; use CuyZ\Valinor\Type\Parser\TypeParser; @@ -29,7 +29,7 @@ final class TreeMapperContainer implements TreeMapper $node = $this->node($signature, $source); if (! $node->isValid()) { - throw new MappingError($node); + throw new MappingError($node->node()); } return $node->value(); @@ -38,7 +38,7 @@ final class TreeMapperContainer implements TreeMapper /** * @param mixed $source */ - private function node(string $signature, $source): Node + private function node(string $signature, $source): TreeNode { try { $type = $this->typeParser->parse($signature); diff --git a/tests/Fake/Mapper/Tree/Builder/FakeNodeBuilder.php b/tests/Fake/Mapper/Tree/Builder/FakeNodeBuilder.php index 06aa21b..e7ee973 100644 --- a/tests/Fake/Mapper/Tree/Builder/FakeNodeBuilder.php +++ b/tests/Fake/Mapper/Tree/Builder/FakeNodeBuilder.php @@ -6,16 +6,16 @@ namespace CuyZ\Valinor\Tests\Fake\Mapper\Tree\Builder; use CuyZ\Valinor\Mapper\Tree\Builder\NodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder; -use CuyZ\Valinor\Mapper\Tree\Node; +use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode; use CuyZ\Valinor\Mapper\Tree\Shell; final class FakeNodeBuilder implements NodeBuilder { - /** @var null|callable(Shell): Node */ + /** @var null|callable(Shell): TreeNode */ private $callback; /** - * @param null|callable(Shell): Node $callback + * @param null|callable(Shell): TreeNode $callback */ public function __construct(callable $callback = null) { @@ -24,12 +24,12 @@ final class FakeNodeBuilder implements NodeBuilder } } - public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node + public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { if (isset($this->callback)) { return ($this->callback)($shell); } - return Node::leaf($shell, $shell->value()); + return TreeNode::leaf($shell, $shell->value()); } } diff --git a/tests/Fake/Mapper/FakeNode.php b/tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php similarity index 70% rename from tests/Fake/Mapper/FakeNode.php rename to tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php index c2112b1..4d921dd 100644 --- a/tests/Fake/Mapper/FakeNode.php +++ b/tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php @@ -2,20 +2,21 @@ declare(strict_types=1); -namespace CuyZ\Valinor\Tests\Fake\Mapper; +namespace CuyZ\Valinor\Tests\Fake\Mapper\Tree\Builder; use CuyZ\Valinor\Definition\Attributes; +use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode; use CuyZ\Valinor\Mapper\Tree\Message\Message; -use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; +use CuyZ\Valinor\Tests\Fake\Mapper\FakeShell; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use CuyZ\Valinor\Type\Type; use Throwable; -final class FakeNode +final class FakeTreeNode { - public static function any(): Node + public static function any(): TreeNode { return self::leaf(FakeType::permissive(), []); } @@ -23,18 +24,18 @@ final class FakeNode /** * @param mixed $value */ - public static function leaf(Type $type, $value): Node + public static function leaf(Type $type, $value): TreeNode { $shell = FakeShell::new($type, $value); - return Node::leaf($shell, $value); + return TreeNode::leaf($shell, $value); } /** * @param array $children * @param mixed $value */ - public static function branch(array $children, Type $type = null, $value = null): Node + public static function branch(array $children, Type $type = null, $value = null): TreeNode { $shell = FakeShell::new($type ?? FakeType::permissive(), $value); $nodes = []; @@ -47,7 +48,7 @@ final class FakeNode $child['attributes'] ?? new FakeAttributes(), )->withValue($childValue); - $node = Node::leaf($childShell, $childValue); + $node = TreeNode::leaf($childShell, $childValue); if (isset($child['message'])) { $node = $node->withMessage($child['message']); @@ -56,16 +57,16 @@ final class FakeNode $nodes[] = $node; } - return Node::branch($shell, $value, $nodes); + return TreeNode::branch($shell, $value, $nodes); } /** * @param Throwable&Message $error */ - public static function error(Throwable $error = null): Node + public static function error(Throwable $error = null): TreeNode { $shell = FakeShell::new(FakeType::permissive(), []); - return Node::error($shell, $error ?? new FakeErrorMessage()); + return TreeNode::error($shell, $error ?? new FakeErrorMessage()); } } diff --git a/tests/Fake/Mapper/Tree/FakeNode.php b/tests/Fake/Mapper/Tree/FakeNode.php new file mode 100644 index 0000000..43d28c9 --- /dev/null +++ b/tests/Fake/Mapper/Tree/FakeNode.php @@ -0,0 +1,48 @@ + $children + */ + public static function branch(array $children): Node + { + return self::build([], $children); + } + + public static function withMessage(Message $message): Node + { + return self::build([$message], []); + } + + /** + * @param array $messages + * @param array $children + */ + private static function build(array $messages, array $children): Node + { + return new Node( + true, + 'nodeName', + 'some.node.path', + 'string', + true, + 'some source value', + 'some value', + $messages, + $children, + ); + } +} diff --git a/tests/Fake/Mapper/Tree/Message/FakeNodeMessage.php b/tests/Fake/Mapper/Tree/Message/FakeNodeMessage.php index 96d586e..3b872fe 100644 --- a/tests/Fake/Mapper/Tree/Message/FakeNodeMessage.php +++ b/tests/Fake/Mapper/Tree/Message/FakeNodeMessage.php @@ -6,17 +6,25 @@ namespace CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message; use CuyZ\Valinor\Mapper\Tree\Message\Message; use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeShell; +use CuyZ\Valinor\Tests\Fake\Mapper\Tree\FakeNode; final class FakeNodeMessage { public static function any(): NodeMessage { - return self::with(new FakeMessage()); + return self::build(new FakeMessage()); } - public static function with(Message $message): NodeMessage + public static function withMessage(Message $message): NodeMessage { - return new NodeMessage(FakeShell::any(), $message); + return self::build($message); + } + + private static function build(Message $message): NodeMessage + { + return new NodeMessage( + FakeNode::any(), + $message + ); } } diff --git a/tests/Unit/Mapper/MappingErrorTest.php b/tests/Unit/Mapper/MappingErrorTest.php index 193a572..3225daf 100644 --- a/tests/Unit/Mapper/MappingErrorTest.php +++ b/tests/Unit/Mapper/MappingErrorTest.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace CuyZ\Valinor\Tests\Unit\Mapper; use CuyZ\Valinor\Mapper\MappingError; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeNode; +use CuyZ\Valinor\Tests\Fake\Mapper\Tree\FakeNode; use PHPUnit\Framework\TestCase; final class MappingErrorTest extends TestCase diff --git a/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php b/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php new file mode 100644 index 0000000..f9357ad --- /dev/null +++ b/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php @@ -0,0 +1,182 @@ +node(); + + self::assertTrue($node->isRoot()); + self::assertSame('', $node->name()); + self::assertSame('', $node->path()); + self::assertSame((string)$type, $node->type()); + self::assertTrue($node->sourceFilled()); + self::assertSame('some source value', $node->sourceValue()); + self::assertTrue($node->isValid()); + self::assertSame('some value', $node->mappedValue()); + } + + public function test_node_leaf_with_incorrect_value_throws_exception(): void + { + $type = new FakeType(); + + $this->expectException(InvalidNodeValue::class); + $this->expectExceptionCode(1630678334); + $this->expectExceptionMessage("Value 'foo' does not match type `$type`."); + + FakeTreeNode::leaf($type, 'foo'); + } + + public function test_node_branch_values_can_be_retrieved(): void + { + $typeChildA = FakeType::permissive(); + $typeChildB = FakeType::permissive(); + $attributesChildA = new FakeAttributes(); + $attributesChildB = new FakeAttributes(); + + $children = FakeTreeNode::branch([ + 'foo' => ['type' => $typeChildA, 'value' => 'foo', 'attributes' => $attributesChildA], + 'bar' => ['type' => $typeChildB, 'value' => 'bar', 'attributes' => $attributesChildB], + ])->children(); + + self::assertSame('foo', $children['foo']->node()->name()); + self::assertSame('foo', $children['foo']->node()->path()); + self::assertFalse($children['foo']->node()->isRoot()); + self::assertSame('bar', $children['bar']->node()->name()); + self::assertSame('bar', $children['bar']->node()->path()); + self::assertFalse($children['bar']->node()->isRoot()); + } + + public function test_node_branch_with_duplicated_child_name_throws_exception(): void + { + $this->expectException(DuplicatedNodeChild::class); + $this->expectExceptionCode(1634045114); + $this->expectExceptionMessage('The child `foo` is duplicated in the branch.'); + + FakeTreeNode::branch([ + ['name' => 'foo'], + ['name' => 'foo'], + ]); + } + + public function test_node_branch_with_incorrect_value_throws_exception(): void + { + $type = new FakeType(); + + $this->expectException(InvalidNodeValue::class); + $this->expectExceptionCode(1630678334); + $this->expectExceptionMessage("Value 'foo' does not match type `$type`."); + + FakeTreeNode::branch([], $type, 'foo'); + } + + public function test_node_error_values_can_be_retrieved(): void + { + $message = new FakeErrorMessage(); + $node = FakeTreeNode::error($message); + + self::assertFalse($node->isValid()); + self::assertSame('some error message', (string)$node->node()->messages()[0]); + } + + public function test_get_value_from_invalid_node_throws_exception(): void + { + $node = FakeTreeNode::error(); + + $this->expectException(AssertionError::class); + + $node->value(); + } + + public function test_branch_node_with_invalid_child_is_invalid(): void + { + $node = FakeTreeNode::branch([ + 'foo' => [], + 'bar' => ['message' => new FakeErrorMessage()], + ]); + + self::assertFalse($node->isValid()); + } + + public function test_node_with_value_returns_node_with_value(): void + { + $nodeA = FakeTreeNode::any(); + $nodeB = $nodeA->withValue('bar'); + + self::assertNotSame($nodeA, $nodeB); + self::assertSame('bar', $nodeB->value()); + self::assertTrue($nodeB->isValid()); + } + + public function test_node_with_invalid_value_returns_invalid_node(): void + { + $type = FakeType::accepting('foo'); + + $this->expectException(InvalidNodeValue::class); + $this->expectExceptionCode(1630678334); + $this->expectExceptionMessage("Value 1337 does not match type `$type`."); + + FakeTreeNode::leaf($type, 'foo')->withValue(1337); + } + + public function test_node_with_invalid_value_for_object_type_returns_invalid_node(): void + { + $object = new stdClass(); + $type = FakeObjectType::accepting($object); + + $this->expectException(InvalidNodeValue::class); + $this->expectExceptionCode(1630678334); + $this->expectExceptionMessage('Invalid value 1337.'); + + FakeTreeNode::leaf($type, $object)->withValue(1337); + } + + public function test_node_with_messages_returns_node_with_messages(): void + { + $messageA = new FakeMessage('some message A'); + $messageB = new FakeMessage('some message B'); + + $nodeA = FakeTreeNode::any(); + $nodeB = $nodeA->withMessage($messageA)->withMessage($messageB); + + self::assertNotSame($nodeA, $nodeB); + self::assertTrue($nodeB->isValid()); + self::assertSame('some message A', (string)$nodeB->node()->messages()[0]); + self::assertSame('some message B', (string)$nodeB->node()->messages()[1]); + } + + public function test_node_with_error_message_returns_invalid_node(): void + { + $message = new FakeMessage(); + $errorMessage = new FakeErrorMessage(); + + $nodeA = FakeTreeNode::any(); + $nodeB = $nodeA->withMessage($message)->withMessage($errorMessage); + + self::assertNotSame($nodeA, $nodeB); + self::assertFalse($nodeB->isValid()); + self::assertSame('some message', (string)$nodeB->node()->messages()[0]); + self::assertSame('some error message', (string)$nodeB->node()->messages()[1]); + } +} diff --git a/tests/Unit/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatterTest.php b/tests/Unit/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatterTest.php index 116da6a..09d3a1a 100644 --- a/tests/Unit/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatterTest.php +++ b/tests/Unit/Mapper/Tree/Message/Formatter/PlaceHolderMessageFormatterTest.php @@ -5,32 +5,26 @@ declare(strict_types=1); namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree\Message\Formatter; use CuyZ\Valinor\Mapper\Tree\Message\Formatter\PlaceHolderMessageFormatter; -use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeShell; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeNodeMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\Formatter\FakeMessageFormatter; -use CuyZ\Valinor\Tests\Fake\Type\FakeType; use PHPUnit\Framework\TestCase; final class PlaceHolderMessageFormatterTest extends TestCase { public function test_format_message_replaces_placeholders_with_default_values(): void { - $type = FakeType::permissive(); - $shell = FakeShell::any()->child('foo', $type)->withValue('some value'); - - $message = new NodeMessage($shell, new FakeMessage('some message')); + $message = FakeNodeMessage::withMessage(new FakeMessage('some message')); $message = (new FakeMessageFormatter('%1$s / %2$s / %3$s / %4$s / %5$s'))->format($message); $message = (new PlaceHolderMessageFormatter())->format($message); - self::assertSame("some_code / some message / `$type` / foo / foo", (string)$message); + self::assertSame("some_code / some message / `string` / nodeName / some.node.path", (string)$message); } - public function test_format_message_replaces_correct_original_value_if_throwable(): void + public function test_format_message_replaces_correct_source_value_if_throwable(): void { - $message = FakeNodeMessage::with(new FakeErrorMessage('some error message')); + $message = FakeNodeMessage::withMessage(new FakeErrorMessage('some error message')); $message = (new FakeMessageFormatter('original: %2$s'))->format($message); $message = (new PlaceHolderMessageFormatter())->format($message); @@ -41,7 +35,7 @@ final class PlaceHolderMessageFormatterTest extends TestCase { $formatter = new PlaceHolderMessageFormatter('foo', 'bar'); - $message = FakeNodeMessage::with(new FakeMessage('%1$s / %2$s')); + $message = FakeNodeMessage::withMessage(new FakeMessage('%1$s / %2$s')); $message = $formatter->format($message); self::assertSame('foo / bar', (string)$message); diff --git a/tests/Unit/Mapper/Tree/Message/Formatter/TranslationMessageFormatterTest.php b/tests/Unit/Mapper/Tree/Message/Formatter/TranslationMessageFormatterTest.php index 45658c4..b701199 100644 --- a/tests/Unit/Mapper/Tree/Message/Formatter/TranslationMessageFormatterTest.php +++ b/tests/Unit/Mapper/Tree/Message/Formatter/TranslationMessageFormatterTest.php @@ -20,7 +20,7 @@ final class TranslationMessageFormatterTest extends TestCase ], ]); - $message = FakeNodeMessage::with(new FakeMessage('some key')); + $message = FakeNodeMessage::withMessage(new FakeMessage('some key')); $message = $formatter->format($message); self::assertSame('some message', (string)$message); @@ -38,7 +38,7 @@ final class TranslationMessageFormatterTest extends TestCase 'some other message' ); - $message = FakeNodeMessage::with(new FakeMessage('some key')); + $message = FakeNodeMessage::withMessage(new FakeMessage('some key')); $message = $formatter->format($message); self::assertSame('some other message', (string)$message); @@ -57,7 +57,7 @@ final class TranslationMessageFormatterTest extends TestCase ], ]); - $message = FakeNodeMessage::with(new FakeMessage('some key')); + $message = FakeNodeMessage::withMessage(new FakeMessage('some key')); $message = $formatter->format($message); self::assertSame('some other message', (string)$message); @@ -79,7 +79,7 @@ final class TranslationMessageFormatterTest extends TestCase ], ]); - $message = FakeNodeMessage::with(new FakeMessage('some other key')); + $message = FakeNodeMessage::withMessage(new FakeMessage('some other key')); $message = $formatter->format($message); self::assertSame('some other message', (string)$message); @@ -94,7 +94,7 @@ final class TranslationMessageFormatterTest extends TestCase ); $originalMessage = new FakeTranslatableMessage('Value {value} is not accepted.', ['value' => 'foo']); - $message = FakeNodeMessage::with($originalMessage); + $message = FakeNodeMessage::withMessage($originalMessage); $message = $formatter->format($message); self::assertSame('Value foo is not accepted!', (string)$message); diff --git a/tests/Unit/Mapper/Tree/Message/MessagesFlattenerTest.php b/tests/Unit/Mapper/Tree/Message/MessagesFlattenerTest.php index 55b8790..57e8439 100644 --- a/tests/Unit/Mapper/Tree/Message/MessagesFlattenerTest.php +++ b/tests/Unit/Mapper/Tree/Message/MessagesFlattenerTest.php @@ -5,7 +5,8 @@ declare(strict_types=1); namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree\Message; use CuyZ\Valinor\Mapper\Tree\Message\MessagesFlattener; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeNode; +use CuyZ\Valinor\Mapper\Tree\Node; +use CuyZ\Valinor\Tests\Fake\Mapper\Tree\FakeNode; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeMessage; use PHPUnit\Framework\TestCase; @@ -18,10 +19,20 @@ final class MessagesFlattenerTest extends TestCase $errorA = new FakeErrorMessage('some error message A'); $errorB = new FakeErrorMessage('some error message B'); - $node = FakeNode::branch([ - 'foo' => ['message' => $messageA], - 'bar' => ['message' => $errorA], - ])->withMessage($errorB); + $node = new Node( + true, + 'nodeName', + 'some.node.path', + 'string', + true, + 'some source value', + 'some value', + [$errorB], + [ + 'foo' => FakeNode::withMessage($messageA), + 'bar' => FakeNode::withMessage($errorA), + ] + ); $messages = [...(new MessagesFlattener($node))->errors()]; diff --git a/tests/Unit/Mapper/Tree/Message/NodeMessageTest.php b/tests/Unit/Mapper/Tree/Message/NodeMessageTest.php index 00ce362..3c4d03b 100644 --- a/tests/Unit/Mapper/Tree/Message/NodeMessageTest.php +++ b/tests/Unit/Mapper/Tree/Message/NodeMessageTest.php @@ -6,14 +6,11 @@ namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree\Message; use CuyZ\Valinor\Mapper\Tree\Message\Message; use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeShell; +use CuyZ\Valinor\Tests\Fake\Mapper\Tree\FakeNode; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeNodeMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeTranslatableMessage; -use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\Formatter\FakeMessageFormatter; -use CuyZ\Valinor\Tests\Fake\Type\FakeType; use PHPUnit\Framework\TestCase; final class NodeMessageTest extends TestCase @@ -21,24 +18,27 @@ final class NodeMessageTest extends TestCase public function test_node_properties_can_be_accessed(): void { $originalMessage = new FakeMessage(); - $type = FakeType::permissive(); - $attributes = new FakeAttributes(); - $shell = FakeShell::any()->child('foo', $type, $attributes)->withValue('some value'); - $message = new NodeMessage($shell, $originalMessage); + $message = new NodeMessage(FakeNode::any(), $originalMessage); - self::assertSame('foo', $message->name()); - self::assertSame('foo', $message->path()); + self::assertSame('nodeName', $message->node()->name()); + self::assertSame('nodeName', $message->name()); + self::assertSame('some.node.path', $message->node()->path()); + self::assertSame('some.node.path', $message->path()); + self::assertSame('string', $message->node()->type()); + self::assertSame('string', $message->type()); + self::assertSame('some source value', $message->node()->sourceValue()); + self::assertSame('some value', $message->node()->mappedValue()); + self::assertSame('some value', $message->node()->value()); self::assertSame('some value', $message->value()); - self::assertSame($type, $message->type()); - self::assertSame($attributes, $message->attributes()); self::assertSame($originalMessage, $message->originalMessage()); + self::assertFalse($message->isError()); } public function test_message_is_error_if_original_message_is_throwable(): void { $originalMessage = new FakeErrorMessage(); - $message = new NodeMessage(FakeShell::any(), $originalMessage); + $message = FakeNodeMessage::withMessage($originalMessage); self::assertTrue($message->isError()); self::assertSame('1652883436', $message->code()); @@ -48,34 +48,23 @@ final class NodeMessageTest extends TestCase public function test_parameters_are_replaced_in_body(): void { $originalMessage = new FakeTranslatableMessage('some original message', ['some_parameter' => 'some parameter value']); - $type = FakeType::permissive(); - $shell = FakeShell::any()->child('foo', $type)->withValue('some value'); - $message = new NodeMessage($shell, $originalMessage); - $message = $message->withBody('{message_code} / {node_name} / {node_path} / {node_type} / {original_value} / {original_message} / {some_parameter}'); + $message = new NodeMessage(FakeNode::any(), $originalMessage); + $message = $message->withBody('{message_code} / {node_name} / {node_path} / {node_type} / {original_value} / {source_value} / {original_message} / {some_parameter}'); - self::assertSame("1652902453 / foo / foo / `$type` / 'some value' / some original message (toString) / some parameter value", (string)$message); + self::assertSame("1652902453 / nodeName / some.node.path / `string` / 'some source value' / 'some source value' / some original message (toString) / some parameter value", (string)$message); } public function test_replaces_correct_original_message_if_throwable(): void { - $message = new NodeMessage(FakeShell::any(), new FakeErrorMessage('some error message')); + $originalMessage = new FakeErrorMessage('some error message'); + + $message = FakeNodeMessage::withMessage($originalMessage); $message = $message->withBody('original: {original_message}'); self::assertSame('original: some error message', (string)$message); } - public function test_format_message_uses_formatter_to_replace_content(): void - { - $originalMessage = new FakeMessage('some message'); - - $message = new NodeMessage(FakeShell::any(), $originalMessage); - $formattedMessage = (new FakeMessageFormatter())->format($message); - - self::assertNotSame($message, $formattedMessage); - self::assertSame('formatted: some message', (string)$formattedMessage); - } - public function test_custom_body_returns_clone(): void { $messageA = FakeNodeMessage::any(); @@ -96,7 +85,7 @@ final class NodeMessageTest extends TestCase { $originalMessage = new FakeTranslatableMessage('un message: {value, spellout}', ['value' => '42']); - $message = new NodeMessage(FakeShell::any(), $originalMessage); + $message = FakeNodeMessage::withMessage($originalMessage); $message = $message->withLocale('fr'); self::assertSame('un message: quarante-deux', (string)$message); @@ -111,7 +100,7 @@ final class NodeMessageTest extends TestCase } }; - $message = new NodeMessage(FakeShell::any(), $originalMessage); + $message = FakeNodeMessage::withMessage($originalMessage); self::assertSame('unknown', $message->code()); } diff --git a/tests/Unit/Mapper/Tree/NodeTest.php b/tests/Unit/Mapper/Tree/NodeTest.php index be3d20a..9f46fd1 100644 --- a/tests/Unit/Mapper/Tree/NodeTest.php +++ b/tests/Unit/Mapper/Tree/NodeTest.php @@ -4,177 +4,99 @@ declare(strict_types=1); namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree; -use CuyZ\Valinor\Mapper\Tree\Exception\CannotGetInvalidNodeValue; -use CuyZ\Valinor\Mapper\Tree\Exception\DuplicatedNodeChild; -use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeNode; +use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeHasNoMappedValue; +use CuyZ\Valinor\Mapper\Tree\Exception\SourceValueWasNotFilled; +use CuyZ\Valinor\Mapper\Tree\Node; +use CuyZ\Valinor\Tests\Fake\Mapper\Tree\FakeNode; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeMessage; -use CuyZ\Valinor\Tests\Fake\Type\FakeObjectType; -use CuyZ\Valinor\Tests\Fake\Type\FakeType; use PHPUnit\Framework\TestCase; -use stdClass; final class NodeTest extends TestCase { - public function test_node_leaf_values_can_be_retrieved(): void + public function test_properties_can_be_accessed(): void { - $type = FakeType::permissive(); - $value = 'some node value'; + $message = new FakeMessage('some message'); + $child = FakeNode::any(); - $node = FakeNode::leaf($type, $value); + $node = new Node( + true, + 'nodeName', + 'some.node.path', + 'string', + true, + 'some source value', + 'some value', + [$message], + [$child] + ); - self::assertSame($type, $node->type()); - self::assertSame($value, $node->value()); - self::assertTrue($node->isRoot()); - self::assertTrue($node->isValid()); + self::assertSame(true, $node->isRoot()); + self::assertSame('nodeName', $node->name()); + self::assertSame('some.node.path', $node->path()); + self::assertSame('string', $node->type()); + self::assertSame('some source value', $node->sourceValue()); + self::assertSame('some value', $node->value()); + self::assertSame('some value', $node->mappedValue()); + self::assertSame(true, $node->isValid()); + self::assertSame('some message', (string)$node->messages()[0]); + self::assertSame([$child], $node->children()); } - public function test_node_leaf_with_incorrect_value_throws_exception(): void - { - $type = new FakeType(); - - $this->expectException(InvalidNodeValue::class); - $this->expectExceptionCode(1630678334); - $this->expectExceptionMessage("Value 'foo' does not match type `$type`."); - - FakeNode::leaf($type, 'foo'); - } - - public function test_node_branch_values_can_be_retrieved(): void - { - $typeChildA = FakeType::permissive(); - $typeChildB = FakeType::permissive(); - $attributesChildA = new FakeAttributes(); - $attributesChildB = new FakeAttributes(); - - $children = FakeNode::branch([ - 'foo' => ['type' => $typeChildA, 'value' => 'foo', 'attributes' => $attributesChildA], - 'bar' => ['type' => $typeChildB, 'value' => 'bar', 'attributes' => $attributesChildB], - ])->children(); - - self::assertSame('foo', $children['foo']->name()); - self::assertSame('foo', $children['foo']->path()); - self::assertFalse($children['foo']->isRoot()); - self::assertSame($attributesChildA, $children['foo']->attributes()); - self::assertSame('bar', $children['bar']->name()); - self::assertSame('bar', $children['bar']->path()); - self::assertFalse($children['bar']->isRoot()); - self::assertSame($attributesChildB, $children['bar']->attributes()); - } - - public function test_node_branch_with_duplicated_child_name_throws_exception(): void - { - $this->expectException(DuplicatedNodeChild::class); - $this->expectExceptionCode(1634045114); - $this->expectExceptionMessage('The child `foo` is duplicated in the branch.'); - - FakeNode::branch([ - ['name' => 'foo'], - ['name' => 'foo'], - ]); - } - - public function test_node_branch_with_incorrect_value_throws_exception(): void - { - $type = new FakeType(); - - $this->expectException(InvalidNodeValue::class); - $this->expectExceptionCode(1630678334); - $this->expectExceptionMessage("Value 'foo' does not match type `$type`."); - - FakeNode::branch([], $type, 'foo'); - } - - public function test_node_error_values_can_be_retrieved(): void + public function test_error_message_makes_node_not_valid(): void { $message = new FakeErrorMessage(); - $node = FakeNode::error($message); - self::assertFalse($node->isValid()); - self::assertSame('some error message', (string)$node->messages()[0]); + $node = new Node( + true, + 'nodeName', + 'some.node.path', + 'string', + true, + 'some source value', + 'some value', + [$message], + [] + ); + + self::assertSame(false, $node->isValid()); } - public function test_get_value_from_invalid_node_throws_exception(): void + public function test_get_source_value_not_filled_throws_exception(): void { - $node = FakeNode::error(); + $this->expectException(SourceValueWasNotFilled::class); + $this->expectExceptionCode(1657466107); + $this->expectExceptionMessage('Source was not filled at path `some.node.path`; use method `$node->sourceFilled()`.'); - $this->expectException(CannotGetInvalidNodeValue::class); - $this->expectExceptionCode(1630680246); - $this->expectExceptionMessage('Trying to get value of an invalid node at path ``.'); - - $node->value(); + (new Node( + true, + 'nodeName', + 'some.node.path', + 'string', + false, + null, + 'some value', + [], + [] + ))->sourceValue(); } - public function test_branch_node_with_invalid_child_is_invalid(): void + public function test_get_mapped_value_from_invalid_node_throws_exception(): void { - $node = FakeNode::branch([ - 'foo' => [], - 'bar' => ['message' => new FakeErrorMessage()], - ]); + $this->expectException(InvalidNodeHasNoMappedValue::class); + $this->expectExceptionCode(1657466305); + $this->expectExceptionMessage('Cannot get mapped value for invalid node at path `some.node.path`; use method `$node->isValid()`.'); - self::assertFalse($node->isValid()); - } - - public function test_node_with_value_returns_node_with_value(): void - { - $nodeA = FakeNode::any(); - $nodeB = $nodeA->withValue('bar'); - - self::assertNotSame($nodeA, $nodeB); - self::assertSame('bar', $nodeB->value()); - self::assertTrue($nodeB->isValid()); - } - - public function test_node_with_invalid_value_returns_invalid_node(): void - { - $type = FakeType::accepting('foo'); - - $this->expectException(InvalidNodeValue::class); - $this->expectExceptionCode(1630678334); - $this->expectExceptionMessage("Value 1337 does not match type `$type`."); - - FakeNode::leaf($type, 'foo')->withValue(1337); - } - - public function test_node_with_invalid_value_for_object_type_returns_invalid_node(): void - { - $object = new stdClass(); - $type = FakeObjectType::accepting($object); - - $this->expectException(InvalidNodeValue::class); - $this->expectExceptionCode(1630678334); - $this->expectExceptionMessage('Invalid value 1337.'); - - FakeNode::leaf($type, $object)->withValue(1337); - } - - public function test_node_with_messages_returns_node_with_messages(): void - { - $messageA = new FakeMessage('some message A'); - $messageB = new FakeMessage('some message B'); - - $nodeA = FakeNode::any(); - $nodeB = $nodeA->withMessage($messageA)->withMessage($messageB); - - self::assertNotSame($nodeA, $nodeB); - self::assertTrue($nodeB->isValid()); - self::assertSame('some message A', (string)$nodeB->messages()[0]); - self::assertSame('some message B', (string)$nodeB->messages()[1]); - } - - public function test_node_with_error_message_returns_invalid_node(): void - { - $message = new FakeMessage(); - $errorMessage = new FakeErrorMessage(); - - $nodeA = FakeNode::any(); - $nodeB = $nodeA->withMessage($message)->withMessage($errorMessage); - - self::assertNotSame($nodeA, $nodeB); - self::assertFalse($nodeB->isValid()); - self::assertSame('some message', (string)$nodeB->messages()[0]); - self::assertSame('some error message', (string)$nodeB->messages()[1]); + (new Node( + true, + 'nodeName', + 'some.node.path', + 'string', + true, + 'some source value', + null, + [new FakeErrorMessage()], + [] + ))->mappedValue(); } } diff --git a/tests/Unit/Mapper/Tree/NodeTraverserTest.php b/tests/Unit/Mapper/Tree/NodeTraverserTest.php index 6ed4c1d..fb08c61 100644 --- a/tests/Unit/Mapper/Tree/NodeTraverserTest.php +++ b/tests/Unit/Mapper/Tree/NodeTraverserTest.php @@ -6,24 +6,26 @@ namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree; use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\NodeTraverser; -use CuyZ\Valinor\Tests\Fake\Mapper\FakeNode; +use CuyZ\Valinor\Tests\Fake\Mapper\Tree\FakeNode; use PHPUnit\Framework\TestCase; final class NodeTraverserTest extends TestCase { public function test_nodes_are_visited(): void { - $node = FakeNode::branch([ - 'foo' => [], - 'bar' => [], - ]); + $children = [ + 'foo' => FakeNode::any(), + 'bar' => FakeNode::any(), + ]; + + $node = FakeNode::branch($children); $visited = [...(new NodeTraverser( fn (Node $node) => $node ))->traverse($node)]; self::assertContains($node, $visited); - self::assertContains($node->children()['foo'], $visited); - self::assertContains($node->children()['bar'], $visited); + self::assertContains($children['foo'], $visited); + self::assertContains($children['bar'], $visited); } }