| 
<?php
 /*
 * This file is part of Chevere.
 *
 * (c) Rodolfo Berrios <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
 declare(strict_types=1);
 
 namespace Chevere\Parameter;
 
 use Chevere\Parameter\Interfaces\ParameterInterface;
 use Chevere\Parameter\Interfaces\ParametersInterface;
 use Chevere\Parameter\Interfaces\TypeInterface;
 use Chevere\Parameter\Interfaces\UnionParameterInterface;
 use Chevere\Parameter\Traits\ArrayParameterTrait;
 use Chevere\Parameter\Traits\ExceptionErrorMessageTrait;
 use Chevere\Parameter\Traits\ParameterAssertArrayTypeTrait;
 use Chevere\Parameter\Traits\ParameterTrait;
 use InvalidArgumentException;
 use LogicException;
 use Throwable;
 use function Chevere\Message\message;
 
 final class UnionParameter implements UnionParameterInterface
 {
 use ParameterTrait;
 use ArrayParameterTrait;
 use ParameterAssertArrayTypeTrait;
 use ExceptionErrorMessageTrait;
 
 private mixed $default = null;
 
 public function __construct(
 private ParametersInterface $parameters,
 private string $description = '',
 ) {
 $this->type = $this->type();
 if ($parameters->count() < 2) {
 throw new LogicException(
 (string) message(
 'Must pass at least two parameters for union'
 )
 );
 }
 
 $this->parameters = $parameters;
 }
 
 public function __invoke(mixed $value): mixed
 {
 $messages = [];
 foreach ($this->parameters() as $name => $parameter) {
 try {
 return $parameter->__invoke($value);
 } catch (Throwable $e) {
 $messages[] = $this->getParameterError($parameter, $name, $e);
 }
 }
 $message = implode('; ', $messages);
 
 throw new InvalidArgumentException(
 (string) message(
 "Argument provided doesn't match union: %message%",
 message: $message,
 )
 );
 }
 
 public function withDefault(mixed $default): UnionParameterInterface
 {
 $this($default);
 $new = clone $this;
 $new->default = $default;
 
 return $new;
 }
 
 public function default(): mixed
 {
 return $this->default;
 }
 
 public function withAdded(ParameterInterface ...$parameter): UnionParameterInterface
 {
 $new = clone $this;
 foreach ($parameter as $name => $item) {
 $name = strval($name);
 $new->parameters = $new->parameters
 ->withRequired($name, $item);
 }
 
 return $new;
 }
 
 public function assertCompatible(UnionParameterInterface $parameter): void
 {
 $this->assertArrayType($parameter);
 }
 
 public function typeSchema(): string
 {
 return $this->type->primitive();
 }
 
 private function getParameterError(
 ParameterInterface $parameter,
 string $name,
 Throwable $e
 ): string {
 $type = $parameter::class;
 $message = $this->getExceptionMessage($e);
 
 return <<<PLAIN
 Parameter `{$name}` <{$type}>: {$message}
 PLAIN;
 }
 
 private function typeName(): string
 {
 return TypeInterface::UNION;
 }
 }
 
 |