aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/bakame/http-structured-fields/src/Validation
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/bakame/http-structured-fields/src/Validation')
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php26
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php103
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php244
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/Result.php34
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php19
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php68
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/Violation.php12
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ViolationList.php169
8 files changed, 675 insertions, 0 deletions
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php b/vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php
new file mode 100644
index 000000000..3f6ed43ae
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+/**
+ * General Error Code-.
+ *
+ * When adding new codes the name MUST be prefixed with
+ * a `@` to avoid conflicting with parameters keys.
+ */
+enum ErrorCode: string
+{
+ case ItemFailedParsing = '@item.failed.parsing';
+ case ItemValueFailedValidation = '@item.value.failed.validation';
+ case ParametersFailedParsing = '@parameters.failed.parsing';
+ case ParametersMissingConstraints = '@parameters.missing.constraints';
+ case ParametersFailedCriteria = '@parameters.failed.criteria';
+
+ /**
+ * @return array<string>
+ */
+ public static function list(): array
+ {
+ return array_map(fn (self $case) => $case->value, self::cases());
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php b/vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php
new file mode 100644
index 000000000..21d8cdd2f
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\StructuredFieldProvider;
+use Bakame\Http\StructuredFields\SyntaxError;
+use Stringable;
+
+/**
+ * Structured field Item validator.
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ */
+final class ItemValidator
+{
+ /** @var callable(SfType): (string|bool) */
+ private mixed $valueConstraint;
+ private ParametersValidator $parametersConstraint;
+
+ /**
+ * @param callable(SfType): (string|bool) $valueConstraint
+ */
+ private function __construct(
+ callable $valueConstraint,
+ ParametersValidator $parametersConstraint,
+ ) {
+ $this->valueConstraint = $valueConstraint;
+ $this->parametersConstraint = $parametersConstraint;
+ }
+
+ public static function new(): self
+ {
+ return new self(fn (mixed $value) => false, ParametersValidator::new());
+ }
+
+ /**
+ * Validates the Item value.
+ *
+ * On success populate the result item property
+ * On failure populates the result errors property
+ *
+ * @param callable(SfType): (string|bool) $constraint
+ */
+ public function value(callable $constraint): self
+ {
+ return new self($constraint, $this->parametersConstraint);
+ }
+
+ /**
+ * Validates the Item parameters as a whole.
+ *
+ * On failure populates the result errors property
+ */
+ public function parameters(ParametersValidator $constraint): self
+ {
+ return new self($this->valueConstraint, $constraint);
+ }
+
+ public function __invoke(Item|Stringable|string $item): bool|string
+ {
+ $result = $this->validate($item);
+
+ return $result->isSuccess() ? true : (string) $result->errors;
+ }
+
+ /**
+ * Validates the structured field Item.
+ */
+ public function validate(Item|Stringable|string $item): Result
+ {
+ $violations = new ViolationList();
+ if (!$item instanceof Item) {
+ try {
+ $item = Item::fromHttpValue($item);
+ } catch (SyntaxError $exception) {
+ $violations->add(ErrorCode::ItemFailedParsing->value, new Violation('The item string could not be parsed.', previous: $exception));
+
+ return Result::failed($violations);
+ }
+ }
+
+ try {
+ $itemValue = $item->value($this->valueConstraint);
+ } catch (Violation $exception) {
+ $itemValue = null;
+ $violations->add(ErrorCode::ItemValueFailedValidation->value, $exception);
+ }
+
+ $validate = $this->parametersConstraint->validate($item->parameters());
+ $violations->addAll($validate->errors);
+ if ($violations->isNotEmpty()) {
+ return Result::failed($violations);
+ }
+
+ /** @var ValidatedParameters $validatedParameters */
+ $validatedParameters = $validate->data;
+
+ return Result::success(new ValidatedItem($itemValue, $validatedParameters));
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php b/vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php
new file mode 100644
index 000000000..e851b9686
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php
@@ -0,0 +1,244 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\Parameters;
+use Bakame\Http\StructuredFields\StructuredFieldProvider;
+use Bakame\Http\StructuredFields\SyntaxError;
+use Stringable;
+
+/**
+ * Structured field Item validator.
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ *
+ * @phpstan-type SfParameterKeyRule array{validate?:callable(SfType): (bool|string), required?:bool|string, default?:SfType|null}
+ * @phpstan-type SfParameterIndexRule array{validate?:callable(SfType, string): (bool|string), required?:bool|string, default?:array{0:string, 1:SfType}|array{}}
+ */
+final class ParametersValidator
+{
+ public const USE_KEYS = 1;
+ public const USE_INDICES = 2;
+
+ /** @var ?callable(Parameters): (string|bool) */
+ private mixed $criteria;
+ private int $type;
+ /** @var array<string, SfParameterKeyRule>|array<int, SfParameterIndexRule> */
+ private array $filterConstraints;
+
+ /**
+ * @param ?callable(Parameters): (string|bool) $criteria
+ * @param array<string, SfParameterKeyRule>|array<int, SfParameterIndexRule> $filterConstraints
+ */
+ private function __construct(
+ ?callable $criteria = null,
+ int $type = self::USE_KEYS,
+ array $filterConstraints = [],
+ ) {
+ $this->criteria = $criteria;
+ $this->type = $type;
+ $this->filterConstraints = $filterConstraints;
+ }
+
+ public static function new(): self
+ {
+ return new self();
+ }
+
+ /**
+ * Validates the Item parameters as a whole.
+ *
+ * On failure populates the result errors property
+ *
+ * @param ?callable(Parameters): (string|bool) $criteria
+ */
+ public function filterByCriteria(?callable $criteria, int $type = self::USE_KEYS): self
+ {
+ return new self($criteria, [] === $this->filterConstraints ? $type : $this->type, $this->filterConstraints);
+ }
+
+ /**
+ * Validate each parameters value per name.
+ *
+ * On success populate the result item property
+ * On failure populates the result errors property
+ *
+ * @param array<string, SfParameterKeyRule> $constraints
+ */
+ public function filterByKeys(array $constraints): self
+ {
+ return new self($this->criteria, self::USE_KEYS, $constraints);
+ }
+
+ /**
+ * Validate each parameters value per indices.
+ *
+ * On success populate the result item property
+ * On failure populates the result errors property
+ *
+ * @param array<int, SfParameterIndexRule> $constraints
+ */
+ public function filterByIndices(array $constraints): self
+ {
+ return new self($this->criteria, self::USE_INDICES, $constraints);
+ }
+
+ public function __invoke(Parameters|Stringable|string $parameters): bool|string
+ {
+ $result = $this->validate($parameters);
+
+ return $result->isSuccess() ? true : (string) $result->errors;
+ }
+
+ /**
+ * Validates the structured field Item.
+ */
+ public function validate(Parameters|Stringable|string $parameters): Result
+ {
+ $violations = new ViolationList();
+ if (!$parameters instanceof Parameters) {
+ try {
+ $parameters = Parameters::fromHttpValue($parameters);
+ } catch (SyntaxError $exception) {
+ $violations->add(ErrorCode::ParametersFailedParsing->value, new Violation('The parameters string could not be parsed.', previous: $exception));
+
+ return Result::failed($violations);
+ }
+ }
+
+ if ([] === $this->filterConstraints && null === $this->criteria) {
+ $violations->add(ErrorCode::ParametersMissingConstraints->value, new Violation('The parameters constraints are missing.'));
+ }
+
+ $parsedParameters = new ValidatedParameters();
+ if ([] !== $this->filterConstraints) {
+ $parsedParameters = match ($this->type) {
+ self::USE_INDICES => $this->validateByIndices($parameters),
+ default => $this->validateByKeys($parameters),
+ };
+
+ if ($parsedParameters->isFailed()) {
+ $violations->addAll($parsedParameters->errors);
+ } else {
+ $parsedParameters = $parsedParameters->data;
+ }
+ }
+
+ $errorMessage = $this->validateByCriteria($parameters);
+ if (!is_bool($errorMessage)) {
+ $violations->add(ErrorCode::ParametersFailedCriteria->value, new Violation($errorMessage));
+ }
+
+ /** @var ValidatedParameters $parsedParameters */
+ $parsedParameters = $parsedParameters ?? new ValidatedParameters();
+ if ([] === $this->filterConstraints && true === $errorMessage) {
+ $parsedParameters = new ValidatedParameters(match ($this->type) {
+ self::USE_KEYS => $this->toAssociative($parameters),
+ default => $this->toList($parameters),
+ });
+ }
+
+ return match ($violations->isNotEmpty()) {
+ true => Result::failed($violations),
+ default => Result::success($parsedParameters),
+ };
+ }
+
+ private function validateByCriteria(Parameters $parameters): bool|string
+ {
+ if (null === $this->criteria) {
+ return true;
+ }
+
+ $errorMessage = ($this->criteria)($parameters);
+ if (true === $errorMessage) {
+ return true;
+ }
+
+ if (!is_string($errorMessage) || '' === trim($errorMessage)) {
+ $errorMessage = 'The parameters constraints are not met.';
+ }
+
+ return $errorMessage;
+ }
+
+ /**
+ * Validate the current parameter object using its keys and return the parsed values and the errors.
+ *
+ * @return Result<ValidatedParameters>|Result<null>
+ */
+ private function validateByKeys(Parameters $parameters): Result /* @phpstan-ignore-line */
+ {
+ $data = [];
+ $violations = new ViolationList();
+ /**
+ * @var string $key
+ * @var SfParameterKeyRule $rule
+ */
+ foreach ($this->filterConstraints as $key => $rule) {
+ try {
+ $data[$key] = $parameters->valueByKey($key, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? null);
+ } catch (Violation $exception) {
+ $violations[$key] = $exception;
+ }
+ }
+
+ return match ($violations->isNotEmpty()) {
+ true => Result::failed($violations),
+ default => Result::success(new ValidatedParameters($data)),
+ };
+ }
+
+ /**
+ * Validate the current parameter object using its indices and return the parsed values and the errors.
+ */
+ public function validateByIndices(Parameters $parameters): Result
+ {
+ $data = [];
+ $violations = new ViolationList();
+ /**
+ * @var int $index
+ * @var SfParameterIndexRule $rule
+ */
+ foreach ($this->filterConstraints as $index => $rule) {
+ try {
+ $data[$index] = $parameters->valueByIndex($index, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? []);
+ } catch (Violation $exception) {
+ $violations[$index] = $exception;
+ }
+ }
+
+ return match ($violations->isNotEmpty()) {
+ true => Result::failed($violations),
+ default => Result::success(new ValidatedParameters($data)),
+ };
+ }
+
+ /**
+ * @return array<string,SfType>
+ */
+ private function toAssociative(Parameters $parameters): array
+ {
+ $assoc = [];
+ foreach ($parameters as $parameter) {
+ $assoc[$parameter[0]] = $parameter[1]->value();
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * @return array<int, array{0:string, 1:SfType}>
+ */
+ private function toList(Parameters $parameters): array
+ {
+ $list = [];
+ foreach ($parameters as $index => $parameter) {
+ $list[$index] = [$parameter[0], $parameter[1]->value()];
+ }
+
+ return $list;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/Result.php b/vendor/bakame/http-structured-fields/src/Validation/Result.php
new file mode 100644
index 000000000..f765efda0
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/Result.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+final class Result
+{
+ private function __construct(
+ public readonly ValidatedParameters|ValidatedItem|null $data,
+ public readonly ViolationList $errors,
+ ) {
+ }
+
+ public function isSuccess(): bool
+ {
+ return $this->errors->isEmpty();
+ }
+
+ public function isFailed(): bool
+ {
+ return $this->errors->isNotEmpty();
+ }
+
+ public static function success(ValidatedItem|ValidatedParameters $data): self
+ {
+ return new self($data, new ViolationList());
+ }
+
+ public static function failed(ViolationList $errors): self
+ {
+ return new self(null, $errors);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php b/vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php
new file mode 100644
index 000000000..df61a1aa8
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\Bytes;
+use Bakame\Http\StructuredFields\DisplayString;
+use Bakame\Http\StructuredFields\Token;
+use DateTimeImmutable;
+
+final class ValidatedItem
+{
+ public function __construct(
+ public readonly Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $value,
+ public readonly ValidatedParameters $parameters = new ValidatedParameters(),
+ ) {
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php b/vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php
new file mode 100644
index 000000000..c7f5b5eb6
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\ForbiddenOperation;
+use Bakame\Http\StructuredFields\InvalidOffset;
+use Bakame\Http\StructuredFields\StructuredFieldProvider;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ *
+ * @implements ArrayAccess<array-key, array{0:string, 1:SfType}|array{}|SfType|null>
+ * @implements IteratorAggregate<array-key, array{0:string, 1:SfType}|array{}|SfType|null>
+ */
+final class ValidatedParameters implements ArrayAccess, Countable, IteratorAggregate
+{
+ /**
+ * @param array<array-key, array{0:string, 1:SfType}|array{}|SfType|null> $values
+ */
+ public function __construct(
+ private readonly array $values = [],
+ ) {
+ }
+
+ public function count(): int
+ {
+ return count($this->values);
+ }
+
+ public function getIterator(): Iterator
+ {
+ yield from $this->values;
+ }
+
+ public function offsetExists($offset): bool
+ {
+ return array_key_exists($offset, $this->values);
+ }
+
+ public function offsetGet($offset): mixed
+ {
+ return $this->offsetExists($offset) ? $this->values[$offset] : throw InvalidOffset::dueToMemberNotFound($offset);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ /**
+ * @return array<array-key, array{0:string, 1:SfType}|array{}|SfType|null>
+ */
+ public function all(): array
+ {
+ return $this->values;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/Violation.php b/vendor/bakame/http-structured-fields/src/Validation/Violation.php
new file mode 100644
index 000000000..ec666de98
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/Violation.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\StructuredFieldError;
+use LogicException;
+
+final class Violation extends LogicException implements StructuredFieldError
+{
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ViolationList.php b/vendor/bakame/http-structured-fields/src/Validation/ViolationList.php
new file mode 100644
index 000000000..8cbf5e741
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ViolationList.php
@@ -0,0 +1,169 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\InvalidOffset;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+use Stringable;
+use TypeError;
+
+use function array_filter;
+use function array_map;
+use function count;
+use function implode;
+use function is_int;
+
+use const ARRAY_FILTER_USE_BOTH;
+
+/**
+ * @implements IteratorAggregate<array-key,Violation>
+ * @implements ArrayAccess<array-key,Violation>
+ */
+final class ViolationList implements IteratorAggregate, Countable, ArrayAccess, Stringable
+{
+ /** @var array<Violation> */
+ private array $errors = [];
+
+ /**
+ * @param iterable<array-key, Violation> $errors
+ */
+ public function __construct(iterable $errors = [])
+ {
+ $this->addAll($errors);
+ }
+
+ public function count(): int
+ {
+ return count($this->errors);
+ }
+
+ public function getIterator(): Iterator
+ {
+ yield from $this->errors;
+ }
+
+ public function __toString(): string
+ {
+ return implode(PHP_EOL, array_map(fn (Violation $e): string => $e->getMessage(), $this->errors));
+ }
+
+ /**
+ * @return array<array-key, string>
+ */
+ public function summary(): array
+ {
+ return array_map(fn (Violation $e): string => $e->getMessage(), $this->errors);
+ }
+
+ public function isEmpty(): bool
+ {
+ return [] === $this->errors;
+ }
+
+ public function isNotEmpty(): bool
+ {
+ return ! $this->isEmpty();
+ }
+
+ /**
+ * @param string|int $offset
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ /**
+ * @param string|int $offset
+ *
+ * @return Violation
+ */
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * @param string|int $offset
+ */
+ public function offsetUnset(mixed $offset): void
+ {
+ unset($this->errors[$offset]);
+ }
+
+ /**
+ * @param string|int|null $offset
+ * @param Violation $value
+ */
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ if (null === $offset) {
+ throw new TypeError('null can not be used as a valid offset value.');
+ }
+ $this->add($offset, $value);
+ }
+
+ public function has(string|int $offset): bool
+ {
+ if (is_int($offset)) {
+ return null !== $this->filterIndex($offset);
+ }
+
+ return array_key_exists($offset, $this->errors);
+ }
+
+ public function get(string|int $offset): Violation
+ {
+ return $this->errors[$this->filterIndex($offset) ?? throw InvalidOffset::dueToIndexNotFound($offset)];
+ }
+
+ public function add(string|int $offset, Violation $error): void
+ {
+ $this->errors[$offset] = $error;
+ }
+
+ /**
+ * @param iterable<array-key, Violation> $errors
+ */
+ public function addAll(iterable $errors): void
+ {
+ foreach ($errors as $offset => $error) {
+ $this->add($offset, $error);
+ }
+ }
+
+ private function filterIndex(string|int $index, int|null $max = null): string|int|null
+ {
+ if (!is_int($index)) {
+ return $index;
+ }
+
+ $max ??= count($this->errors);
+
+ return match (true) {
+ [] === $this->errors,
+ 0 > $max + $index,
+ 0 > $max - $index - 1 => null,
+ 0 > $index => $max + $index,
+ default => $index,
+ };
+ }
+
+ /**
+ * @param callable(Violation, array-key): bool $callback
+ */
+ public function filter(callable $callback): self
+ {
+ return new self(array_filter($this->errors, $callback, ARRAY_FILTER_USE_BOTH));
+ }
+
+ public function toException(): Violation
+ {
+ return new Violation((string) $this);
+ }
+}