diff --git a/src/Exceptions/AcceptDebugContext.php b/src/Exceptions/AcceptDebugContext.php new file mode 100644 index 0000000..5b8606a --- /dev/null +++ b/src/Exceptions/AcceptDebugContext.php @@ -0,0 +1,37 @@ + + */ +trait AcceptDebugContext +{ + public ?Context $context = null; + +// public function context(callable $code): self +// { +// $this->context ??= new Context(); +// $code($this->context, $this); +// return $this; +// } + + public function tag(string $key, mixed $value): self + { + $this->context ??= new Context(); + $this->context->tag($key, $value); + return $this; + } + + public function push(string $key, mixed $value): self + { + $this->context ??= new Context(); + $this->context->push($key, $value); + return $this; + } +} diff --git a/src/Exceptions/AcceptsDebugContext.php b/src/Exceptions/AcceptsDebugContext.php new file mode 100644 index 0000000..02dc846 --- /dev/null +++ b/src/Exceptions/AcceptsDebugContext.php @@ -0,0 +1,19 @@ + + */ +interface AcceptsDebugContext +{ +// public function context(callable $code): self; + + public function tag(string $key, mixed $value): self; + + public function push(string $key, mixed $value): self; +} diff --git a/src/Exceptions/ChildKeyCollision.php b/src/Exceptions/ChildKeyCollision.php new file mode 100644 index 0000000..0ffc127 --- /dev/null +++ b/src/Exceptions/ChildKeyCollision.php @@ -0,0 +1,17 @@ + + */ +final class ChildKeyCollision extends RuntimeException implements FailsIntegrity, AcceptsDebugContext +{ + use AcceptDebugContext; +} diff --git a/src/Exceptions/ConfigurationIssue.php b/src/Exceptions/ConfigurationIssue.php new file mode 100644 index 0000000..2519c54 --- /dev/null +++ b/src/Exceptions/ConfigurationIssue.php @@ -0,0 +1,16 @@ + + */ +final class ConfigurationIssue extends LogicException implements FailsIntegrity +{ +} diff --git a/src/Exceptions/Context.php b/src/Exceptions/Context.php new file mode 100644 index 0000000..9a71249 --- /dev/null +++ b/src/Exceptions/Context.php @@ -0,0 +1,30 @@ + + */ +final class Context +{ + public array $bag = []; + + public function tag(string $key, mixed $value): self + { + $this->bag[$key] = $value; + return $this; + } + + public function push(string $key, mixed $value): self + { + if (!isset($this->bag[$key])) { + $this->bag[$key] = []; + } + $this->bag[$key][] = $value; + return $this; + } +} diff --git a/src/Exceptions/ExtractorReturnValueIssue.php b/src/Exceptions/ExtractorReturnValueIssue.php new file mode 100644 index 0000000..b4050d3 --- /dev/null +++ b/src/Exceptions/ExtractorReturnValueIssue.php @@ -0,0 +1,17 @@ + + */ +final class ExtractorReturnValueIssue extends LogicException implements FailsIntegrity, AcceptsDebugContext +{ + use AcceptDebugContext; +} diff --git a/src/Exceptions/FailsIntegrity.php b/src/Exceptions/FailsIntegrity.php new file mode 100644 index 0000000..7f40ddd --- /dev/null +++ b/src/Exceptions/FailsIntegrity.php @@ -0,0 +1,15 @@ + + */ +interface FailsIntegrity extends IndicatesTreeIssue +{ +} diff --git a/src/Exceptions/IndicatesInternalFault.php b/src/Exceptions/IndicatesInternalFault.php new file mode 100644 index 0000000..8a5e354 --- /dev/null +++ b/src/Exceptions/IndicatesInternalFault.php @@ -0,0 +1,18 @@ + + */ +interface IndicatesInternalFault extends IndicatesTreeIssue +{ + +} diff --git a/src/Exceptions/IndicatesTreeIssue.php b/src/Exceptions/IndicatesTreeIssue.php new file mode 100644 index 0000000..0cee8d9 --- /dev/null +++ b/src/Exceptions/IndicatesTreeIssue.php @@ -0,0 +1,16 @@ + + */ +interface IndicatesTreeIssue +{ + +} diff --git a/src/Exceptions/InternalLogicException.php b/src/Exceptions/InternalLogicException.php new file mode 100644 index 0000000..c24af11 --- /dev/null +++ b/src/Exceptions/InternalLogicException.php @@ -0,0 +1,16 @@ + + */ +final class InternalLogicException extends LogicException implements IndicatesInternalFault +{ +} diff --git a/src/Exceptions/InvalidInputData.php b/src/Exceptions/InvalidInputData.php new file mode 100644 index 0000000..4fdb243 --- /dev/null +++ b/src/Exceptions/InvalidInputData.php @@ -0,0 +1,17 @@ + + */ +final class InvalidInputData extends RuntimeException implements FailsIntegrity, AcceptsDebugContext +{ + use AcceptDebugContext; +} diff --git a/src/Exceptions/InvalidNodeFactoryReturnValue.php b/src/Exceptions/InvalidNodeFactoryReturnValue.php new file mode 100644 index 0000000..97186ec --- /dev/null +++ b/src/Exceptions/InvalidNodeFactoryReturnValue.php @@ -0,0 +1,29 @@ + + */ +final class InvalidNodeFactoryReturnValue extends LogicException implements FailsIntegrity, AcceptsDebugContext +{ + use AcceptDebugContext; + + public function __construct($message = null, $code = null, Throwable $previous = null) + { + parent::__construct( + $message ?? ('The node factory must return a movable node instance (' . MovableNodeContract::class . ').'), + $code ?? 0, + $previous, + ); + } +} diff --git a/src/MaterializedPath/InvalidTreePath.php b/src/Exceptions/InvalidTreePath.php similarity index 67% rename from src/MaterializedPath/InvalidTreePath.php rename to src/Exceptions/InvalidTreePath.php index 280e01c..3e9c79c 100644 --- a/src/MaterializedPath/InvalidTreePath.php +++ b/src/Exceptions/InvalidTreePath.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Dakujem\Oliva\MaterializedPath; +namespace Dakujem\Oliva\Exceptions; use RuntimeException; use Throwable; @@ -10,8 +10,10 @@ /** * @author Andrej Rypak */ -final class InvalidTreePath extends RuntimeException +final class InvalidTreePath extends RuntimeException implements FailsIntegrity, AcceptsDebugContext { + use AcceptDebugContext; + public function __construct($message = null, $code = null, Throwable $previous = null) { parent::__construct($message ?? 'The given value is not a valid tree path.', $code ?? 0, $previous); diff --git a/src/Exceptions/NodeNotMovable.php b/src/Exceptions/NodeNotMovable.php new file mode 100644 index 0000000..a37c7c0 --- /dev/null +++ b/src/Exceptions/NodeNotMovable.php @@ -0,0 +1,26 @@ + + */ +final class NodeNotMovable extends LogicException implements IndicatesTreeIssue +{ + public mixed $node; + + public function __construct(mixed $node, $message = null, $code = null, Throwable $previous = null) + { + $this->node = $node; + parent::__construct($message ?? 'Encountered a non-movable node while manipulating a tree.', $code ?? 0, $previous); + } +} diff --git a/src/MaterializedPath/Path.php b/src/MaterializedPath/Path.php index ea03c75..fc744d3 100644 --- a/src/MaterializedPath/Path.php +++ b/src/MaterializedPath/Path.php @@ -4,8 +4,9 @@ namespace Dakujem\Oliva\MaterializedPath; +use Dakujem\Oliva\Exceptions\ConfigurationIssue; +use Dakujem\Oliva\Exceptions\InvalidTreePath; use Dakujem\Oliva\TreeNodeContract; -use LogicException; /** * @author Andrej Rypak @@ -24,7 +25,7 @@ final class Path public static function delimited(string $delimiter, callable $accessor): callable { if (strlen($delimiter) !== 1) { - throw new LogicException('The delimiter must be a single character.'); + throw new ConfigurationIssue('The delimiter must be a single character.'); } return function (mixed $data, mixed $inputIndex = null, ?TreeNodeContract $node = null) use ( $delimiter, @@ -35,8 +36,11 @@ public static function delimited(string $delimiter, callable $accessor): callabl return []; } if (!is_string($path)) { - // TODO improve exceptions (index/path etc) - throw new InvalidTreePath('Invalid tree path returned by the accessor. A string is required.'); + throw (new InvalidTreePath('Invalid tree path returned by the accessor. A string is required.')) + ->tag('path', $path) + ->tag('data', $data) + ->tag('index', $inputIndex) + ->tag('node', $node); } $path = trim($path, $delimiter); if ('' === $path) { @@ -66,8 +70,11 @@ public static function fixed(int $levelWidth, callable $accessor): callable return []; } if (!is_string($path)) { - // TODO improve exceptions (index/path etc) - throw new InvalidTreePath('Invalid tree path returned by the accessor. A string is required.'); + throw (new InvalidTreePath('Invalid tree path returned by the accessor. A string is required.')) + ->tag('path', $path) + ->tag('data', $data) + ->tag('index', $inputIndex) + ->tag('node', $node); } return str_split($path, $levelWidth); }; diff --git a/src/MaterializedPath/Support/ShadowNode.php b/src/MaterializedPath/Support/ShadowNode.php index 4a20b1a..1194493 100644 --- a/src/MaterializedPath/Support/ShadowNode.php +++ b/src/MaterializedPath/Support/ShadowNode.php @@ -4,6 +4,7 @@ namespace Dakujem\Oliva\MaterializedPath\Support; +use Dakujem\Oliva\Exceptions\InternalLogicException; use Dakujem\Oliva\MovableNodeContract; use Dakujem\Oliva\Node; use Dakujem\Oliva\TreeNodeContract; @@ -58,7 +59,7 @@ public function realNode(): ?MovableNodeContract public function addChild(TreeNodeContract $child, string|int|null $key = null): self { if (!$child instanceof self) { - throw new LogicException('Invalid use of a shadow node. Only shadow nodes can be children of shadow nodes.'); + throw new InternalLogicException('Invalid use of a shadow node. Only shadow nodes can be children of shadow nodes.'); } return parent::addChild($child, $key); } @@ -66,7 +67,7 @@ public function addChild(TreeNodeContract $child, string|int|null $key = null): public function setParent(?TreeNodeContract $parent): self { if (!$parent instanceof self) { - throw new LogicException('Invalid use of a shadow node. Only shadow nodes can be parents of shadow nodes.'); + throw new InternalLogicException('Invalid use of a shadow node. Only shadow nodes can be parents of shadow nodes.'); } return parent::setParent($parent); } diff --git a/src/MaterializedPath/TreeBuilder.php b/src/MaterializedPath/TreeBuilder.php index e3f5ee2..dc1490f 100644 --- a/src/MaterializedPath/TreeBuilder.php +++ b/src/MaterializedPath/TreeBuilder.php @@ -4,13 +4,14 @@ namespace Dakujem\Oliva\MaterializedPath; +use Dakujem\Oliva\Exceptions\ExtractorReturnValueIssue; +use Dakujem\Oliva\Exceptions\InvalidInputData; +use Dakujem\Oliva\Exceptions\InvalidNodeFactoryReturnValue; use Dakujem\Oliva\MaterializedPath\Support\AlmostThere; use Dakujem\Oliva\MaterializedPath\Support\Register; use Dakujem\Oliva\MaterializedPath\Support\ShadowNode; use Dakujem\Oliva\MovableNodeContract; use Dakujem\Oliva\TreeNodeContract; -use LogicException; -use RuntimeException; /** * Materialized path tree builder. @@ -69,9 +70,11 @@ public function __construct( public function build(iterable $input): TreeNodeContract { - $root = $this->processInput($input)->root(); + $result = $this->processInput($input); + $root = $result->root(); if (null === $root) { - throw new RuntimeException('Corrupted input, no tree created.'); + throw (new InvalidInputData('Corrupted input, no tree created.')) + ->tag('result', $result); } return $root; } @@ -107,30 +110,47 @@ private function buildShadowTree( // Check for consistency. if (!$node instanceof MovableNodeContract) { - // TODO improve exceptions - throw new LogicException('The node factory must return a movable node instance.'); + throw (new InvalidNodeFactoryReturnValue()) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); } // Calculate the node's vector. $vector = $vectorExtractor($data, $inputIndex, $node); if (!is_array($vector)) { - // TODO improve exceptions - throw new LogicException('The vector calculator must return an array.'); + throw (new ExtractorReturnValueIssue('The vector extractor must return an array.')) + ->tag('vector', $vector) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); } foreach ($vector as $i) { if (!is_string($i) && !is_integer($i)) { - // TODO improve exceptions - throw new LogicException('The vector may only consist of strings or integers.'); + throw (new ExtractorReturnValueIssue('The vector may only consist of strings or integers.')) + ->tag('index', $i) + ->tag('vector', $vector) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); } } // Finally, connect the newly created shadow node to the shadow tree. // Make sure all the shadow nodes exist all the way to the root. - $this->connectNode( - new ShadowNode($node), - $vector, - $register, - ); + try { + $this->connectNode( + new ShadowNode($node), + $vector, + $register, + ); + } catch (InvalidInputData $e) { + throw $e + ->tag('vector', $vector) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); + } } // Pull the shadow root from the register. @@ -148,8 +168,7 @@ private function connectNode(ShadowNode $node, array $vector, Register $register if (null !== $existingNode) { // Check for node collisions. if (null !== $node->realNode() && null !== $existingNode->realNode()) { - // TODO improve exceptions - throw new LogicException('Duplicate node vector: ' . implode('.', $vector)); + throw new InvalidInputData('Duplicate node vector: ' . implode('.', $vector)); } $existingNode->fill($node->realNode()); return; diff --git a/src/Node.php b/src/Node.php index f7d1065..fbbf330 100644 --- a/src/Node.php +++ b/src/Node.php @@ -4,8 +4,8 @@ namespace Dakujem\Oliva; +use Dakujem\Oliva\Exceptions\ChildKeyCollision; use Dakujem\Oliva\Iterator\Traversal; -use Exception; use Generator; use IteratorAggregate; use JsonSerializable; @@ -160,7 +160,10 @@ public function addChild(TreeNodeContract $child, string|int|null $key = null): } elseif (!isset($this->children[$key])) { $this->children[$key] = $child; } else { - throw new Exception('Collision not allowed.'); + throw (new ChildKeyCollision('Collision not allowed: ' . $key)) + ->tag('parent', $this) + ->tag('child', $child) + ->tag('key', $key); } return $this; } diff --git a/src/Recursive/TreeBuilder.php b/src/Recursive/TreeBuilder.php index 3fad009..b89d952 100644 --- a/src/Recursive/TreeBuilder.php +++ b/src/Recursive/TreeBuilder.php @@ -4,10 +4,12 @@ namespace Dakujem\Oliva\Recursive; +use Dakujem\Oliva\Exceptions\ConfigurationIssue; +use Dakujem\Oliva\Exceptions\ExtractorReturnValueIssue; +use Dakujem\Oliva\Exceptions\InvalidInputData; +use Dakujem\Oliva\Exceptions\InvalidNodeFactoryReturnValue; use Dakujem\Oliva\MovableNodeContract; use Dakujem\Oliva\TreeNodeContract; -use InvalidArgumentException; -use LogicException; /** * Recursive tree builder. @@ -79,7 +81,7 @@ public function __construct( } elseif (is_callable($root)) { $this->root = $root; } else { - throw new InvalidArgumentException(); + throw new ConfigurationIssue('Invalid argument: The root detector must either be a string|int|null to compare against the parent refs, or a callable that returns truthy if a root is detected.'); } } @@ -123,25 +125,34 @@ private function processData( // Check for consistency. if (!$node instanceof MovableNodeContract) { - // TODO improve exceptions - throw new LogicException('The node factory must return a movable node instance.'); + throw (new InvalidNodeFactoryReturnValue())->tag('node', $node)->tag('data', $data)->tag('index', $inputIndex); } $selfRef = $selfRefExtractor($data, $inputIndex, $node); $parentRef = $parentRefExtractor($data, $inputIndex, $node); if (!is_string($selfRef) && !is_int($selfRef)) { - // TODO improve exceptions - throw new LogicException('Invalid "self reference" returned by the extractor. Requires a int|string unique to the given node.'); + throw (new ExtractorReturnValueIssue('Invalid "self reference" returned by the extractor. Requires a int|string unique to the given node.')) + ->tag('ref', $selfRef) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); } if (null !== $parentRef && !is_string($parentRef) && !is_int($parentRef)) { - // TODO improve exceptions - throw new LogicException('Invalid "parent reference" returned by the extractor. Requires a int|string uniquely pointing to "self reference" of another node, or `null`.'); + throw (new ExtractorReturnValueIssue('Invalid "parent reference" returned by the extractor. Requires a int|string uniquely pointing to "self reference" of another node, or `null`.')) + ->tag('parent', $parentRef) + ->tag('self', $selfRef) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); } if (isset($nodeRegister[$selfRef])) { - // TODO improve exceptions - throw new LogicException('Duplicate node reference: ' . $selfRef); + throw (new ExtractorReturnValueIssue('Duplicate node reference: ' . $selfRef)) + ->tag('ref', $selfRef) + ->tag('node', $node) + ->tag('data', $data) + ->tag('index', $inputIndex); } $nodeRegister[$selfRef] = $node; @@ -159,8 +170,8 @@ private function processData( } if (!$rootFound) { - // TODO improve exceptions - throw new LogicException('No root node found.'); + throw (new InvalidInputData('No root node found in the data.')) + ->tag('nodes', $nodeRegister); } // The tree reconstruction pass. diff --git a/src/Simple/NodeBuilder.php b/src/Simple/NodeBuilder.php index 1f9209e..ed53598 100644 --- a/src/Simple/NodeBuilder.php +++ b/src/Simple/NodeBuilder.php @@ -4,9 +4,11 @@ namespace Dakujem\Oliva\Simple; +use Dakujem\Oliva\Exceptions\ExtractorReturnValueIssue; +use Dakujem\Oliva\Exceptions\InvalidInputData; +use Dakujem\Oliva\Exceptions\InvalidNodeFactoryReturnValue; use Dakujem\Oliva\MovableNodeContract; use Dakujem\Oliva\TreeNodeContract; -use LogicException; /** * A trivial builder that ensures the children are bound to the respective parent. @@ -37,16 +39,18 @@ public function node( // Check for consistency. if (!$node instanceof MovableNodeContract) { - // TODO improve exceptions - throw new LogicException('The node factory must return a movable node instance.'); + throw (new InvalidNodeFactoryReturnValue())->tag('node', $node)->tag('data', $data); } // Bind the children. foreach ($children as $key => $child) { // Check for consistency. if (!$child instanceof MovableNodeContract) { - // TODO improve exceptions - throw new LogicException('The children must be movable node instances.'); + throw (new InvalidInputData('The children must be movable node instances.')) + ->tag('child', $child) + ->tag('key', $key) + ->tag('parent', $node) + ->tag('data', $data); } $child->setParent($node); $node->addChild($child, $key); diff --git a/src/Simple/TreeWrapper.php b/src/Simple/TreeWrapper.php index 85886d5..6b38333 100644 --- a/src/Simple/TreeWrapper.php +++ b/src/Simple/TreeWrapper.php @@ -4,9 +4,11 @@ namespace Dakujem\Oliva\Simple; +use Dakujem\Oliva\Exceptions\AcceptsDebugContext; +use Dakujem\Oliva\Exceptions\ExtractorReturnValueIssue; +use Dakujem\Oliva\Exceptions\InvalidNodeFactoryReturnValue; use Dakujem\Oliva\MovableNodeContract; use Dakujem\Oliva\TreeNodeContract; -use LogicException; /** * Simple tree builder. @@ -58,21 +60,28 @@ private function wrapNode( // Check for consistency. if (!$node instanceof MovableNodeContract) { - // TODO improve exceptions - throw new LogicException('The node factory must return a movable node instance.'); + throw (new InvalidNodeFactoryReturnValue()) + ->tag('node', $node) + ->tag('data', $data); } $childrenData = $childrenExtractor($data, $node); if (null !== $childrenData && !is_iterable($childrenData)) { - // TODO improve exceptions - throw new LogicException('Children data extractor must return an iterable collection containing children data.'); + throw (new ExtractorReturnValueIssue('Children data extractor must return an iterable collection containing children data.')) + ->tag('children', $childrenData) + ->tag('parent', $node) + ->tag('data', $data); } foreach ($childrenData ?? [] as $key => $childData) { - $child = $this->wrapNode( - data: $childData, - nodeFactory: $nodeFactory, - childrenExtractor: $childrenExtractor, - ); + try { + $child = $this->wrapNode( + data: $childData, + nodeFactory: $nodeFactory, + childrenExtractor: $childrenExtractor, + ); + } catch (AcceptsDebugContext $e) { + throw $e->push('nodes', $node); + } $child->setParent($node); $node->addChild($child, $key); } diff --git a/src/Tree.php b/src/Tree.php index 727654a..496590c 100644 --- a/src/Tree.php +++ b/src/Tree.php @@ -4,7 +4,7 @@ namespace Dakujem\Oliva; -use Exception; +use Dakujem\Oliva\Exceptions\NodeNotMovable; /** * A helper class for high-level tree operations. @@ -64,8 +64,7 @@ public static function unlink( return null; } if (!$parent instanceof MovableNodeContract) { - // TODO improve exceptions - throw new Exception('Parent not movable.'); + throw new NodeNotMovable($parent); } $node->setParent(null); $parent->removeChild($node); @@ -87,8 +86,7 @@ public static function linkChildren( ): MovableNodeContract { foreach ($children as $key => $child) { if (!$child instanceof MovableNodeContract) { - // TODO improve exceptions - throw new Exception('Child not movable.'); + throw new NodeNotMovable($child); } $originalParent = self::link($child, $parent, $key); if (null !== $parent && null !== $onParentUnlinked) { @@ -107,8 +105,7 @@ public static function unlinkChildren( ): void { foreach ($parent->children() as $key => $child) { if (!$child instanceof MovableNodeContract) { - // TODO improve exceptions - throw new Exception('Child not movable.'); + throw new NodeNotMovable($child); } $child->setParent(null); } @@ -134,8 +131,7 @@ public static function reindexTree( $seq = 0; foreach ($children as $childKey => $child) { if (!$child instanceof MovableNodeContract) { - // TODO improve exceptions - throw new Exception('Child not movable.'); + throw new NodeNotMovable($child); } $newKey = null !== $key ? $key($child, $childKey, $seq) : $childKey; $node->addChild($child, $newKey); diff --git a/tests/mptree.phpt b/tests/mptree.phpt index caa9ece..d0e3b2f 100644 --- a/tests/mptree.phpt +++ b/tests/mptree.phpt @@ -4,16 +4,19 @@ declare(strict_types=1); namespace Dakujem\Test; +use Dakujem\Oliva\Exceptions\ExtractorReturnValueIssue; +use Dakujem\Oliva\Exceptions\InvalidInputData; +use Dakujem\Oliva\Exceptions\InvalidNodeFactoryReturnValue; +use Dakujem\Oliva\Exceptions\InvalidTreePath; use Dakujem\Oliva\Iterator\PreOrderTraversal; use Dakujem\Oliva\MaterializedPath\Path; use Dakujem\Oliva\MaterializedPath\Support\AlmostThere; use Dakujem\Oliva\MaterializedPath\TreeBuilder; +use Dakujem\Oliva\MovableNodeContract; use Dakujem\Oliva\Node; use Dakujem\Oliva\Seed; use Dakujem\Oliva\Tree; use Dakujem\Oliva\TreeNodeContract; -use LogicException; -use RuntimeException; use Tester\Assert; require_once __DIR__ . '/setup.php'; @@ -94,36 +97,36 @@ class Item Assert::same([], $vectorExtractor(null)); Assert::throws(function () use ($vectorExtractor) { $vectorExtractor(4.2); - }, RuntimeException::class); // TODO improve + }, InvalidTreePath::class); // an empty input can not result in any tree Assert::throws(function () use ($builder) { $builder->build([]); - }, RuntimeException::class, 'Corrupted input, no tree created.'); // TODO improve + }, InvalidInputData::class, 'Corrupted input, no tree created.'); $failingBuilder = new TreeBuilder(fn() => null, fn() => []); Assert::throws(function () use ($failingBuilder) { $failingBuilder->build([null]); - }, LogicException::class, 'The node factory must return a movable node instance.'); // TODO improve + }, InvalidNodeFactoryReturnValue::class, 'The node factory must return a movable node instance (' . MovableNodeContract::class . ').'); $invalidVector = new TreeBuilder(fn() => new Node(null), fn() => null); Assert::throws(function () use ($invalidVector) { $invalidVector->build([null]); - }, LogicException::class, 'The vector calculator must return an array.'); // TODO improve + }, ExtractorReturnValueIssue::class, 'The vector extractor must return an array.'); $invalidVectorContents = new TreeBuilder(fn() => new Node(null), fn() => ['a', null]); Assert::throws(function () use ($invalidVectorContents) { $invalidVectorContents->build([null]); - }, LogicException::class, 'The vector may only consist of strings or integers.'); // TODO improve + }, ExtractorReturnValueIssue::class, 'The vector may only consist of strings or integers.'); $duplicateVector = new TreeBuilder(fn() => new Node(null), fn() => ['any']); Assert::throws(function () use ($duplicateVector) { $duplicateVector->build([null, null]); - }, LogicException::class, 'Duplicate node vector: any'); // TODO improve + }, InvalidInputData::class, 'Duplicate node vector: any'); diff --git a/tests/recursive.phpt b/tests/recursive.phpt index 2ae23cd..7c42f5a 100644 --- a/tests/recursive.phpt +++ b/tests/recursive.phpt @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Dakujem\Test; +use Dakujem\Oliva\Exceptions\InvalidInputData; use Dakujem\Oliva\Iterator\Filter; use Dakujem\Oliva\Iterator\PreOrderTraversal; use Dakujem\Oliva\Node; use Dakujem\Oliva\Recursive\TreeBuilder; use Dakujem\Oliva\Seed; use Dakujem\Oliva\TreeNodeContract; -use LogicException; use Tester\Assert; require_once __DIR__ . '/setup.php'; @@ -114,12 +114,12 @@ class Item Assert::throws( fn() => $builder->build([]), - LogicException::class, - 'No root node found.', + InvalidInputData::class, + 'No root node found in the data.', ); Assert::throws( fn() => $builder->build([new Item(id: 7, parent: 42)]), - LogicException::class, - 'No root node found.', + InvalidInputData::class, + 'No root node found in the data.', ); })();