3 declare(strict_types=1);
5 namespace CuyZ\Valinor\Mapper\Tree\Builder;
7 use CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable;
8 use CuyZ\Valinor\Mapper\Tree\Exception\UnexpectedShapedArrayKeys;
9 use CuyZ\Valinor\Mapper\Tree\Shell;
10 use CuyZ\Valinor\Type\Types\ShapedArrayType;
12 use function array_key_exists;
15 use function is_array;
18 final class ShapedArrayNodeBuilder implements NodeBuilder
20 public function __construct(private bool $allowSuperfluousKeys) {}
22 public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
24 $type = $shell->type();
25 $value = $shell->value();
27 assert($type instanceof ShapedArrayType);
29 if (! is_array($value)) {
30 throw new SourceMustBeIterable($value, $type);
33 $children = $this->children($type, $shell, $rootBuilder);
35 $array = $this->buildArray($children);
37 $node = TreeNode::branch($shell, $array, $children);
39 if (! $this->allowSuperfluousKeys && count($value) > count($children)) {
40 $node = $node->withMessage(new UnexpectedShapedArrayKeys($value, $children));
47 * @return array<TreeNode>
49 private function children(ShapedArrayType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
51 /** @var array<mixed> $value */
52 $value = $shell->value();
53 $elements = $type->elements();
56 foreach ($elements as $element) {
57 $key = $element->key()->value();
59 $child = $shell->child((string)$key, $element->type());
61 if (array_key_exists($key, $value)) {
62 $child = $child->withValue($value[$key]);
63 } elseif ($element->isOptional()) {
67 $children[$key] = $rootBuilder->build($child);
76 * @param array<TreeNode> $children
77 * @return mixed[]|null
79 private function buildArray(array $children): ?array
83 foreach ($children as $key => $child) {
84 if (! $child->isValid()) {
88 $array[$key] = $child->value();