* SPDX-License-Identifier: MIT */ declare(strict_types=1); namespace Respect\Validation; use Respect\Validation\Rules\Core\Nameable; use function array_filter; use function array_map; use function count; use function preg_match; final readonly class Result { /** @var array */ public array $children; /** @param array $parameters */ public function __construct( public bool $hasPassed, public mixed $input, public Rule $rule, public Id $id, public array $parameters = [], public string $template = Rule::TEMPLATE_STANDARD, public bool $hasInvertedMode = false, public Name|null $name = null, public Result|null $adjacent = null, public Path|null $path = null, Result ...$children, ) { $this->children = $children; } /** @param array $parameters */ public static function of( bool $hasPassed, mixed $input, Rule $rule, array $parameters = [], string $template = Rule::TEMPLATE_STANDARD, ): self { return new self($hasPassed, $input, $rule, Id::fromRule($rule), $parameters, $template); } /** @param array $parameters */ public static function failed( mixed $input, Rule $rule, array $parameters = [], string $template = Rule::TEMPLATE_STANDARD, ): self { return self::of(false, $input, $rule, $parameters, $template); } /** @param array $parameters */ public static function passed( mixed $input, Rule $rule, array $parameters = [], string $template = Rule::TEMPLATE_STANDARD, ): self { return self::of(true, $input, $rule, $parameters, $template); } public function asAdjacentOf(Result $result, string $prefix): Result { if ($this->allowsAdjacent()) { return clone ($result, [ 'id' => $this->id->withPrefix($prefix), 'adjacent' => $this->withInput($result->input), ]); } return clone ($this, [ 'input' => $result->input, 'children' => array_map( static fn(Result $child) => $child->asAdjacentOf($result, $prefix), $this->children, ), ]); } public function withTemplate(string $template): self { return clone($this, ['template' => $template]); } /** @param array $parameters */ public function withExtraParameters(array $parameters): self { // phpcs:ignore SlevomatCodingStandard.PHP.UselessParentheses return clone($this, ['parameters' => $parameters + $this->parameters]); } public function withId(Id $id): self { return clone($this, ['id' => $id]); } public function withIdFrom(Rule $rule): self { return clone($this, ['id' => Id::fromRule($rule)]); } public function withPath(Path $path): self { if ($this->path === $path) { return $this; } if ($this->path !== null) { $this->path->parent = $path; return $this; } return clone($this, [ 'path' => $path, 'adjacent' => $this->adjacent?->withPath($path), 'children' => array_map( static fn(Result $child) => $child->withPath($path), $this->children, ), ]); } public function withoutName(): self { if ($this->name === null) { return $this; } return clone ($this, [ 'name' => null, 'adjacent' => $this->adjacent?->withoutName(), 'children' => array_map( fn(Result $child) => $child->name === $this->name ? $child->withoutName() : $child, $this->children, ), ]); } public function withChildren(Result ...$children): self { if ($this->path === null) { return clone($this, ['children' => $children]); } return clone($this, ['children' => array_map(fn(Result $child) => $child->withPath($this->path), $children)]); } public function withName(Name $name): self { if ($this->path !== null && $this->name?->path !== $this->path) { $name = $name->withPath($this->path); } return clone($this, [ 'name' => $this->name ?? $name, 'adjacent' => $this->adjacent?->withName($name), 'children' => array_map( static fn(Result $child) => $child->path === null ? $child->withName($child->name ?? $name) : $child, $this->children, ), ]); } public function withNameFrom(Rule $rule): self { if ($rule instanceof Nameable && $rule->getName() !== null) { return clone($this, [ 'name' => $this->name ?? $rule->getName(), 'adjacent' => $this->adjacent?->withNameFrom($rule), 'children' => array_map( static fn(Result $child) => $child->withNameFrom($rule), $this->children, ), ]); } return $this; } public function withInput(mixed $input): self { $currentInput = $this->input; return clone($this, [ 'input' => $input, 'children' => array_map( static fn(Result $child) => $child->input === $currentInput ? $child->withInput($input) : $child, $this->children, ), ]); } public function withAdjacent(Result $adjacent): self { return clone($this, ['adjacent' => $adjacent]); } public function withToggledValidation(): self { return clone($this, [ 'hasPassed' => !$this->hasPassed, 'adjacent' => $this->adjacent?->withToggledValidation(), 'children' => array_map(static fn(Result $child) => $child->withToggledValidation(), $this->children), ]); } public function withToggledModeAndValidation(): self { return clone($this, [ 'hasPassed' => !$this->hasPassed, 'hasInvertedMode' => !$this->hasInvertedMode, 'adjacent' => $this->adjacent?->withToggledModeAndValidation(), 'children' => array_map( static fn(Result $child) => $child->withToggledModeAndValidation(), $this->children, ), ]); } public function hasCustomTemplate(): bool { return preg_match('/__[0-9a-z_]+_/', $this->template) === 0; } public function allowsAdjacent(): bool { if ($this->children === [] && !$this->hasCustomTemplate()) { return true; } $childrenThatAllowAdjacent = array_filter( $this->children, static fn(Result $child) => $child->allowsAdjacent(), ); return count($childrenThatAllowAdjacent) === 1; } }