build: полная сборка в git — серверные PHP-зависимости lib/Vendor (438 файлов, из пиннированного релиза v22.0.12)
Type checking / changes (push) Has been cancelled
Type checking / test (push) Has been cancelled
Type checking / typescript-summary (push) Has been cancelled
Node tests / changes (push) Has been cancelled
Node tests / test (push) Has been cancelled
Node tests / test-summary (push) Has been cancelled
Type checking / changes (push) Has been cancelled
Type checking / test (push) Has been cancelled
Type checking / typescript-summary (push) Has been cancelled
Node tests / changes (push) Has been cancelled
Node tests / test (push) Has been cancelled
Node tests / test-summary (push) Has been cancelled
Теперь master f7_talk = ПОЛНЫЙ деплой-артефакт: built js (наша сборка) + lib/Vendor (серверные PHP-deps) + патч Listener + version 22.0.12.1. Готов к брендингу dev-f7 и подписи dev-appstore. Апстрим-подписи (appinfo/signature.json) в репо нет — переподпишет dev-appstore при публикации.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper;
|
||||
|
||||
/** @api */
|
||||
interface ArgumentsMapper
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*
|
||||
* @throws MappingError
|
||||
*/
|
||||
public function mapArguments(callable $callable, mixed $source): array;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Messages;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Node;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\ValueDumper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class ArgumentsMapperError extends RuntimeException implements MappingError
|
||||
{
|
||||
private Node $node;
|
||||
|
||||
public function __construct(FunctionDefinition $function, Node $node)
|
||||
{
|
||||
$this->node = $node;
|
||||
|
||||
$errors = Messages::flattenFromNode($node)->errors();
|
||||
$errorsCount = count($errors);
|
||||
|
||||
if ($errorsCount === 1) {
|
||||
$body = $errors
|
||||
->toArray()[0]
|
||||
->withBody("Could not map arguments of `$function->signature`. An error occurred at path {node_path}: {original_message}")
|
||||
->toString();
|
||||
} else {
|
||||
$source = ValueDumper::dump($node->sourceValue());
|
||||
$body = "Could not map arguments of `$function->signature` with value $source. A total of $errorsCount errors were encountered.";
|
||||
}
|
||||
|
||||
parent::__construct($body, 1671115362);
|
||||
}
|
||||
|
||||
public function node(): Node
|
||||
{
|
||||
return $this->node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Parser\Exception\InvalidType;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidMappingTypeSignature extends RuntimeException
|
||||
{
|
||||
public function __construct(string $raw, InvalidType $exception)
|
||||
{
|
||||
parent::__construct(
|
||||
"Could not parse the type `$raw` that should be mapped: {$exception->getMessage()}",
|
||||
1630959692,
|
||||
$exception
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class TypeErrorDuringArgumentsMapping extends LogicException
|
||||
{
|
||||
public function __construct(FunctionDefinition $function, UnresolvableShellType $exception)
|
||||
{
|
||||
parent::__construct(
|
||||
"Could not map arguments of `$function->signature`: {$exception->getMessage()}",
|
||||
1711534351,
|
||||
$exception,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class TypeErrorDuringMapping extends LogicException
|
||||
{
|
||||
public function __construct(Type $type, UnresolvableShellType $exception)
|
||||
{
|
||||
parent::__construct(
|
||||
"Error while trying to map to `{$type->toString()}`: {$exception->getMessage()}",
|
||||
1711526329,
|
||||
$exception,
|
||||
);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Node;
|
||||
use Throwable;
|
||||
|
||||
/** @api */
|
||||
interface MappingError extends Throwable
|
||||
{
|
||||
public function node(): Node;
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Attributes;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ParameterDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\PropertyDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
|
||||
/** @internal */
|
||||
final class Argument
|
||||
{
|
||||
/** @var non-empty-string */
|
||||
private string $name;
|
||||
|
||||
/** @var non-empty-string */
|
||||
private string $signature;
|
||||
|
||||
private Type $type;
|
||||
|
||||
private mixed $defaultValue = null;
|
||||
|
||||
private bool $isRequired = true;
|
||||
|
||||
private Attributes $attributes;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $name
|
||||
* @param non-empty-string $signature
|
||||
*/
|
||||
public function __construct(string $name, string $signature, Type $type)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->signature = $signature;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public static function fromParameter(ParameterDefinition $parameter): self
|
||||
{
|
||||
$instance = new self($parameter->name, $parameter->signature, $parameter->type);
|
||||
$instance->attributes = $parameter->attributes;
|
||||
|
||||
if ($parameter->isOptional) {
|
||||
$instance->defaultValue = $parameter->defaultValue;
|
||||
$instance->isRequired = false;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public static function fromProperty(PropertyDefinition $property): self
|
||||
{
|
||||
$instance = new self($property->name, $property->signature, $property->type);
|
||||
$instance->attributes = $property->attributes;
|
||||
|
||||
if ($property->hasDefaultValue) {
|
||||
$instance->defaultValue = $property->defaultValue;
|
||||
$instance->isRequired = false;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function signature(): string
|
||||
{
|
||||
return $this->signature;
|
||||
}
|
||||
|
||||
public function type(): Type
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function defaultValue(): mixed
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return $this->isRequired;
|
||||
}
|
||||
|
||||
public function attributes(): Attributes
|
||||
{
|
||||
return $this->attributes ??= Attributes::empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use Countable;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ParameterDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Parameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Properties;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\PropertyDefinition;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @implements IteratorAggregate<Argument>
|
||||
*/
|
||||
final class Arguments implements IteratorAggregate, Countable
|
||||
{
|
||||
/** @var array<string, Argument> */
|
||||
private array $arguments = [];
|
||||
|
||||
public function __construct(Argument ...$arguments)
|
||||
{
|
||||
foreach ($arguments as $argument) {
|
||||
$this->arguments[$argument->name()] = $argument;
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromParameters(Parameters $parameters): self
|
||||
{
|
||||
return new self(...array_map(
|
||||
fn (ParameterDefinition $parameter) => Argument::fromParameter($parameter),
|
||||
[...$parameters],
|
||||
));
|
||||
}
|
||||
|
||||
public static function fromProperties(Properties $properties): self
|
||||
{
|
||||
return new self(...array_map(
|
||||
fn (PropertyDefinition $property) => Argument::fromProperty($property),
|
||||
[...$properties],
|
||||
));
|
||||
}
|
||||
|
||||
public function at(int $index): Argument
|
||||
{
|
||||
return array_values($this->arguments)[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function names(): array
|
||||
{
|
||||
return array_keys($this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Argument>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<Argument>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use Countable;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\CompositeTraversableType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ArrayKeyType;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @implements IteratorAggregate<Argument>
|
||||
*/
|
||||
final class ArgumentsValues implements IteratorAggregate, Countable
|
||||
{
|
||||
/** @var array<mixed> */
|
||||
private array $value = [];
|
||||
|
||||
private Arguments $arguments;
|
||||
|
||||
private bool $hasInvalidValue = false;
|
||||
|
||||
private bool $forInterface = false;
|
||||
|
||||
private bool $hadSingleArgument = false;
|
||||
|
||||
private function __construct(Arguments $arguments)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public static function forInterface(Arguments $arguments, Shell $shell): self
|
||||
{
|
||||
$self = new self($arguments);
|
||||
$self->forInterface = true;
|
||||
|
||||
if (count($arguments) > 0) {
|
||||
$self->transform($shell);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
public static function forClass(Arguments $arguments, Shell $shell): self
|
||||
{
|
||||
$self = new self($arguments);
|
||||
$self->transform($shell);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
public function hasInvalidValue(): bool
|
||||
{
|
||||
return $this->hasInvalidValue;
|
||||
}
|
||||
|
||||
public function hasValue(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->value);
|
||||
}
|
||||
|
||||
public function getValue(string $name): mixed
|
||||
{
|
||||
return $this->value[$name];
|
||||
}
|
||||
|
||||
public function hadSingleArgument(): bool
|
||||
{
|
||||
return $this->hadSingleArgument;
|
||||
}
|
||||
|
||||
private function transform(Shell $shell): void
|
||||
{
|
||||
$value = $shell->value();
|
||||
|
||||
$transformedValue = $this->transformValueForSingleArgument($value, $shell->allowSuperfluousKeys());
|
||||
|
||||
if (! is_array($transformedValue)) {
|
||||
$this->hasInvalidValue = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($transformedValue !== $value) {
|
||||
$this->hadSingleArgument = true;
|
||||
}
|
||||
|
||||
foreach ($this->arguments as $argument) {
|
||||
$name = $argument->name();
|
||||
|
||||
if (! array_key_exists($name, $transformedValue) && ! $argument->isRequired()) {
|
||||
$transformedValue[$name] = $argument->defaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
$this->value = $transformedValue;
|
||||
}
|
||||
|
||||
private function transformValueForSingleArgument(mixed $value, bool $allowSuperfluousKeys): mixed
|
||||
{
|
||||
if (count($this->arguments) !== 1) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$argument = $this->arguments->at(0);
|
||||
$name = $argument->name();
|
||||
$type = $argument->type();
|
||||
$isTraversableAndAllowsStringKeys = $type instanceof CompositeTraversableType
|
||||
&& $type->keyType() !== ArrayKeyType::integer();
|
||||
|
||||
if (is_array($value) && array_key_exists($name, $value)) {
|
||||
if ($this->forInterface || ! $isTraversableAndAllowsStringKeys || $allowSuperfluousKeys || count($value) === 1) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === [] && ! $isTraversableAndAllowsStringKeys) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return [$name => $value];
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->arguments);
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* This attribute allows a static method inside a class to be marked as a
|
||||
* constructor, that can be used by the mapper to instantiate the object. The
|
||||
* method must be public, static and return an instance of the class it is part
|
||||
* of.
|
||||
*
|
||||
* This attribute is a convenient replacement to the usage of the constructor
|
||||
* registration method: @see \OCA\Talk\Vendor\CuyZ\Valinor\MapperBuilder::registerConstructor()
|
||||
*
|
||||
* ```php
|
||||
* final readonly class Email
|
||||
* {
|
||||
* // When another constructor is registered for the class, the native
|
||||
* // constructor is disabled. To enable it again, it is mandatory to
|
||||
* // explicitly register it again.
|
||||
* #[\OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Constructor]
|
||||
* public function __construct(public string $value) {}
|
||||
*
|
||||
* #[\OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Constructor]
|
||||
* public static function createFrom(string $user, string $domainName): self
|
||||
* {
|
||||
* return new self($user . '@' . $domainName);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* (new \OCA\Talk\Vendor\CuyZ\Valinor\MapperBuilder())
|
||||
* ->mapper()
|
||||
* ->map(Email::class, [
|
||||
* 'userName' => 'john.doe',
|
||||
* 'domainName' => 'example.com',
|
||||
* ]); // john.doe@example.com
|
||||
* ```
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class Constructor {}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\CannotParseToDateTime;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Can be given to {@see MapperBuilder::registerConstructor()} to describe which
|
||||
* date formats should be allowed during mapping.
|
||||
*
|
||||
* By default, if this constructor is never registered, the dates will accept
|
||||
* any valid timestamp or RFC 3339-formatted value.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```php
|
||||
* (new \OCA\Talk\Vendor\CuyZ\Valinor\MapperBuilder())
|
||||
* // Both `Cookie` and `ATOM` formats will be accepted
|
||||
* ->registerConstructor(new DateTimeFormatConstructor(DATE_COOKIE, DATE_ATOM))
|
||||
* ->mapper()
|
||||
* ->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
|
||||
* ```
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DateTimeFormatConstructor
|
||||
{
|
||||
/** @var non-empty-list<non-empty-string> */
|
||||
private array $formats;
|
||||
|
||||
/**
|
||||
* @no-named-arguments
|
||||
* @param non-empty-string $format
|
||||
* @param non-empty-string ...$formats
|
||||
*/
|
||||
public function __construct(string $format, string ...$formats)
|
||||
{
|
||||
$this->formats = [$format, ...$formats];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<DateTime|DateTimeImmutable> $className
|
||||
* @param non-empty-string|int|float $value
|
||||
*/
|
||||
#[DynamicConstructor]
|
||||
public function __invoke(string $className, string|int|float $value): DateTimeInterface
|
||||
{
|
||||
foreach ($this->formats as $format) {
|
||||
$date = $className::createFromFormat($format, (string)$value) ?: null;
|
||||
|
||||
if ($date) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CannotParseToDateTime($this->formats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use Attribute;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\MapperBuilder;
|
||||
|
||||
/**
|
||||
* This attribute allows the registration of dynamic constructors used when
|
||||
* mapping implementations of interfaces or abstract classes.
|
||||
*
|
||||
* A constructor given to {@see MapperBuilder::registerConstructor()} with this
|
||||
* attribute will be called with the first parameter filled with the name of the
|
||||
* class the mapper needs to build.
|
||||
*
|
||||
* Note that the first parameter of the constructor has to be a string otherwise
|
||||
* an exception will be thrown on mapping.
|
||||
*
|
||||
* ```php
|
||||
* interface SomeInterfaceWithStaticConstructor
|
||||
* {
|
||||
* public static function from(string $value): self;
|
||||
* }
|
||||
*
|
||||
* final class SomeClassWithInheritedStaticConstructor implements SomeInterfaceWithStaticConstructor
|
||||
* {
|
||||
* private function __construct(private SomeValueObject $value) {}
|
||||
*
|
||||
* public static function from(string $value): self
|
||||
* {
|
||||
* return new self(new SomeValueObject($value));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* (new \OCA\Talk\Vendor\CuyZ\Valinor\MapperBuilder())
|
||||
* ->registerConstructor(
|
||||
* #[\OCA\Talk\Vendor\CuyZ\Valinor\Attribute\DynamicConstructor]
|
||||
* function (string $className, string $value): SomeInterfaceWithStaticConstructor {
|
||||
* return $className::from($value);
|
||||
* }
|
||||
* )
|
||||
* ->mapper()
|
||||
* ->map(SomeClassWithInheritedStaticConstructor::class, 'foo');
|
||||
* ```
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)]
|
||||
final class DynamicConstructor {}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function ksort;
|
||||
|
||||
/** @internal */
|
||||
final class CannotFindObjectBuilder extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body = 'Value {source_value} does not match any of {allowed_types}.';
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
/**
|
||||
* @param non-empty-list<ObjectBuilder> $builders
|
||||
*/
|
||||
public function __construct(array $builders)
|
||||
{
|
||||
$this->parameters = [
|
||||
'allowed_types' => (function () use ($builders) {
|
||||
$signatures = [];
|
||||
$sortedSignatures = [];
|
||||
|
||||
foreach ($builders as $builder) {
|
||||
$arguments = $builder->describeArguments();
|
||||
$count = count($arguments);
|
||||
$signature = TypeHelper::dumpArguments($arguments);
|
||||
|
||||
$signatures[$count][$signature] = null;
|
||||
}
|
||||
|
||||
ksort($signatures);
|
||||
|
||||
foreach ($signatures as $list) {
|
||||
foreach (array_keys($list) as $signature) {
|
||||
$sortedSignatures[] = $signature;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(', ', $sortedSignatures);
|
||||
})(),
|
||||
];
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1642183169);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class CannotInstantiateObject extends RuntimeException
|
||||
{
|
||||
public function __construct(ClassDefinition $class)
|
||||
{
|
||||
parent::__construct(
|
||||
"No available constructor found for class `{$class->name}`.",
|
||||
1646916477
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class CannotParseToDateTime extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body = 'Value {source_value} does not match any of the following formats: {formats}.';
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
/**
|
||||
* @param non-empty-list<non-empty-string> $formats
|
||||
*/
|
||||
public function __construct(array $formats)
|
||||
{
|
||||
$this->parameters = [
|
||||
'formats' => '`' . implode('`, `', $formats) . '`',
|
||||
];
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1630686564);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidConstructorClassTypeParameter extends LogicException
|
||||
{
|
||||
public function __construct(FunctionDefinition $function, Type $type)
|
||||
{
|
||||
parent::__construct(
|
||||
"Invalid type `{$type->toString()}` for the first parameter of the constructor `{$function->signature}`, it should be of type `class-string`.",
|
||||
1661517000
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\MethodDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidConstructorMethodWithAttributeReturnType extends LogicException
|
||||
{
|
||||
/**
|
||||
* @param class-string $expectedClassName
|
||||
*/
|
||||
public function __construct(string $expectedClassName, MethodDefinition $method)
|
||||
{
|
||||
if ($method->returnType instanceof UnresolvableType) {
|
||||
$message = $method->returnType->message();
|
||||
} else {
|
||||
$message = "Invalid return type `{$method->returnType->toString()}` for constructor `{$method->signature}`, it must be `$expectedClassName`.";
|
||||
}
|
||||
|
||||
parent::__construct($message, 1708104783);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidConstructorReturnType extends LogicException
|
||||
{
|
||||
public function __construct(FunctionDefinition $function)
|
||||
{
|
||||
if ($function->returnType instanceof UnresolvableType) {
|
||||
$message = $function->returnType->message();
|
||||
} else {
|
||||
$message = "Invalid return type `{$function->returnType->toString()}` for constructor `{$function->signature}`, it must be a valid class name.";
|
||||
}
|
||||
|
||||
parent::__construct($message, 1659446121);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Arguments;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidSource extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(mixed $source, Arguments $arguments)
|
||||
{
|
||||
$this->parameters = [
|
||||
'expected_type' => TypeHelper::dumpArguments($arguments),
|
||||
];
|
||||
|
||||
$this->body = $source === null
|
||||
? 'Cannot be empty and must be filled with a value matching type {expected_type}.'
|
||||
: 'Value {source_value} does not match type {expected_type}.';
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1632903281);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class MissingConstructorClassTypeParameter extends LogicException
|
||||
{
|
||||
public function __construct(FunctionDefinition $function)
|
||||
{
|
||||
parent::__construct(
|
||||
"Missing first parameter of type `class-string` for the constructor `{$function->signature}`.",
|
||||
1661516853
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectBuildersCollision extends RuntimeException
|
||||
{
|
||||
public function __construct(ObjectBuilder $builderA, ObjectBuilder $builderB)
|
||||
{
|
||||
parent::__construct(
|
||||
"A type collision was detected between the constructors `{$builderA->signature()}` and `{$builderB->signature()}`.",
|
||||
1654955787
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class PermissiveTypeNotAllowed extends LogicException
|
||||
{
|
||||
public function __construct(string $argumentSignature, Type $permissiveType)
|
||||
{
|
||||
parent::__construct(
|
||||
"The type of `$argumentSignature` contains `{$permissiveType->toString()}`, which is not " .
|
||||
"allowed in strict mode. If really needed, the `allowPermissiveTypes` setting can be used.",
|
||||
1655389255,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/** @internal */
|
||||
final class CacheObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
public function __construct(
|
||||
private ObjectBuilderFactory $delegate,
|
||||
/** @var CacheInterface<list<ObjectBuilder>> */
|
||||
private CacheInterface $cache
|
||||
) {}
|
||||
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
$signature = $class->type->toString();
|
||||
|
||||
$entry = $this->cache->get($signature);
|
||||
|
||||
if ($entry) {
|
||||
return $entry;
|
||||
}
|
||||
|
||||
$builders = $this->delegate->for($class);
|
||||
|
||||
$this->cache->set($signature, $builders);
|
||||
|
||||
return $builders;
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionObject;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Constructor;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\DynamicConstructor;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\CannotInstantiateObject;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorClassTypeParameter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodWithAttributeReturnType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorReturnType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\MissingConstructorClassTypeParameter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\MethodObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\NativeEnumObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ClassType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ObjectType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ClassStringType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\EnumType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NativeStringType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
|
||||
use function array_key_exists;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function is_a;
|
||||
|
||||
/** @internal */
|
||||
final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
/** @var list<FunctionObject> */
|
||||
private array $filteredConstructors;
|
||||
|
||||
public function __construct(
|
||||
private ObjectBuilderFactory $delegate,
|
||||
/** @var array<class-string, null> */
|
||||
private array $nativeConstructors,
|
||||
private FunctionsContainer $constructors,
|
||||
) {}
|
||||
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
$builders = $this->builders($class);
|
||||
|
||||
if (count($builders) === 0) {
|
||||
if ($class->methods->hasConstructor()) {
|
||||
throw new CannotInstantiateObject($class);
|
||||
}
|
||||
|
||||
return $this->delegate->for($class);
|
||||
}
|
||||
|
||||
return $builders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<ObjectBuilder>
|
||||
*/
|
||||
private function builders(ClassDefinition $class): array
|
||||
{
|
||||
$className = $class->name;
|
||||
$classType = $class->type;
|
||||
$methods = $class->methods;
|
||||
|
||||
$builders = [];
|
||||
|
||||
foreach ($this->filteredConstructors() as $constructor) {
|
||||
if (! $this->constructorMatches($constructor, $classType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition = $constructor->definition;
|
||||
$functionClass = $definition->class;
|
||||
|
||||
if ($functionClass && $definition->isStatic && ! $definition->isClosure) {
|
||||
$scopedClass = is_a($className, $functionClass, true) ? $className : $functionClass;
|
||||
|
||||
$builders[$definition->signature] = new MethodObjectBuilder($scopedClass, $definition->name, $definition->parameters);
|
||||
} else {
|
||||
$builders[$definition->signature] = new FunctionObjectBuilder($constructor, $classType);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($methods as $method) {
|
||||
if (! $method->isStatic) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $method->attributes->has(Constructor::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $method->returnType instanceof ClassType) {
|
||||
throw new InvalidConstructorMethodWithAttributeReturnType($className, $method);
|
||||
}
|
||||
|
||||
if (! is_a($className, $method->returnType->className(), true)) {
|
||||
throw new InvalidConstructorMethodWithAttributeReturnType($className, $method);
|
||||
}
|
||||
|
||||
if (! $class->type->matches($method->returnType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$builders[$method->signature] = new MethodObjectBuilder($className, $method->name, $method->parameters);
|
||||
}
|
||||
|
||||
if ($classType instanceof EnumType) {
|
||||
$buildersWithOneArguments = array_filter($builders, fn (ObjectBuilder $builder) => $builder->describeArguments()->count() === 1);
|
||||
|
||||
if (count($buildersWithOneArguments) === 0) {
|
||||
$builders[] = new NativeEnumObjectBuilder($classType);
|
||||
}
|
||||
} elseif ($methods->hasConstructor()
|
||||
&& $methods->constructor()->isPublic
|
||||
&& (
|
||||
count($builders) === 0
|
||||
|| $methods->constructor()->attributes->has(Constructor::class)
|
||||
|| array_key_exists($className, $this->nativeConstructors)
|
||||
)
|
||||
) {
|
||||
$builders[] = new NativeConstructorObjectBuilder($class);
|
||||
}
|
||||
|
||||
return array_values($builders);
|
||||
}
|
||||
|
||||
private function constructorMatches(FunctionObject $function, ObjectType $classType): bool
|
||||
{
|
||||
$definition = $function->definition;
|
||||
|
||||
if (! $classType->matches($definition->returnType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $definition->attributes->has(DynamicConstructor::class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($definition->parameters) === 0) {
|
||||
throw new MissingConstructorClassTypeParameter($definition);
|
||||
}
|
||||
|
||||
$parameterType = $definition->parameters->at(0)->type;
|
||||
|
||||
if ($parameterType instanceof NativeStringType) {
|
||||
$parameterType = ClassStringType::get();
|
||||
}
|
||||
|
||||
if (! $parameterType instanceof ClassStringType) {
|
||||
throw new InvalidConstructorClassTypeParameter($definition, $parameterType);
|
||||
}
|
||||
|
||||
$subType = $parameterType->subType();
|
||||
|
||||
if ($subType) {
|
||||
return $classType->matches($subType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<FunctionObject>
|
||||
*/
|
||||
private function filteredConstructors(): array
|
||||
{
|
||||
if (! isset($this->filteredConstructors)) {
|
||||
$this->filteredConstructors = [];
|
||||
|
||||
foreach ($this->constructors as $constructor) {
|
||||
$function = $constructor->definition;
|
||||
|
||||
if ($function->class
|
||||
&& Reflection::enumExists($function->class)
|
||||
&& in_array($function->name, ['from', 'tryFrom'], true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $function->returnType instanceof ObjectType) {
|
||||
throw new InvalidConstructorReturnType($function);
|
||||
}
|
||||
|
||||
$this->filteredConstructors[] = $constructor;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->filteredConstructors;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionObject;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Library\Settings;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\DateTimeFormatConstructor;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ObjectType;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
|
||||
use function array_filter;
|
||||
use function count;
|
||||
|
||||
/** @internal */
|
||||
final class DateTimeObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
public function __construct(
|
||||
private ObjectBuilderFactory $delegate,
|
||||
/** @var non-empty-list<non-empty-string> */
|
||||
private array $supportedDateFormats,
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository
|
||||
) {}
|
||||
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
$className = $class->name;
|
||||
|
||||
$builders = $this->delegate->for($class);
|
||||
|
||||
if ($className !== DateTime::class && $className !== DateTimeImmutable::class) {
|
||||
return $builders;
|
||||
}
|
||||
|
||||
// Remove `DateTime` & `DateTimeImmutable` native constructors
|
||||
$builders = array_filter($builders, fn (ObjectBuilder $builder) => ! $builder instanceof NativeConstructorObjectBuilder);
|
||||
|
||||
$buildersWithOneArgument = array_filter($builders, fn (ObjectBuilder $builder) => count($builder->describeArguments()) === 1);
|
||||
|
||||
if (count($buildersWithOneArgument) === 0 || $this->supportedDateFormats !== Settings::DEFAULT_SUPPORTED_DATETIME_FORMATS) {
|
||||
$builders[] = $this->internalDateTimeBuilder($class->type);
|
||||
}
|
||||
|
||||
/** @var non-empty-list<ObjectBuilder> */
|
||||
return $builders;
|
||||
}
|
||||
|
||||
private function internalDateTimeBuilder(ObjectType $type): FunctionObjectBuilder
|
||||
{
|
||||
$constructor = new DateTimeFormatConstructor(...$this->supportedDateFormats);
|
||||
$function = new FunctionObject($this->functionDefinitionRepository->for($constructor), $constructor);
|
||||
|
||||
return new FunctionObjectBuilder($function, $type);
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionObject;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ObjectType;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
|
||||
use function array_filter;
|
||||
use function count;
|
||||
|
||||
/** @internal */
|
||||
final class DateTimeZoneObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
private ObjectBuilderFactory $delegate;
|
||||
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository;
|
||||
|
||||
public function __construct(ObjectBuilderFactory $delegate, FunctionDefinitionRepository $functionDefinitionRepository)
|
||||
{
|
||||
$this->delegate = $delegate;
|
||||
$this->functionDefinitionRepository = $functionDefinitionRepository;
|
||||
}
|
||||
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
$builders = $this->delegate->for($class);
|
||||
|
||||
if ($class->name !== DateTimeZone::class) {
|
||||
return $builders;
|
||||
}
|
||||
|
||||
// Remove `DateTimeZone` native constructors
|
||||
$builders = array_filter($builders, fn (ObjectBuilder $builder) => ! $builder instanceof NativeConstructorObjectBuilder);
|
||||
|
||||
$useDefaultBuilder = true;
|
||||
|
||||
foreach ($builders as $builder) {
|
||||
if (count($builder->describeArguments()) === 1) {
|
||||
$useDefaultBuilder = false;
|
||||
// @infection-ignore-all
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($useDefaultBuilder) {
|
||||
// @infection-ignore-all / Ignore memoization
|
||||
$builders[] = $this->defaultBuilder($class->type);
|
||||
}
|
||||
|
||||
/** @var non-empty-list<ObjectBuilder> */
|
||||
return $builders;
|
||||
}
|
||||
|
||||
private function defaultBuilder(ObjectType $type): FunctionObjectBuilder
|
||||
{
|
||||
$constructor = function (string $timezone) {
|
||||
try {
|
||||
return new DateTimeZone($timezone);
|
||||
} catch (Exception) {
|
||||
throw MessageBuilder::newError('Value {source_value} is not a valid timezone.')->build();
|
||||
}
|
||||
};
|
||||
|
||||
$function = new FunctionObject($this->functionDefinitionRepository->for($constructor), $constructor);
|
||||
|
||||
return new FunctionObjectBuilder($function, $type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
|
||||
/** @internal */
|
||||
interface ObjectBuilderFactory
|
||||
{
|
||||
/**
|
||||
* @return non-empty-list<ObjectBuilder>
|
||||
*/
|
||||
public function for(ClassDefinition $class): array;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ReflectionObjectBuilder;
|
||||
|
||||
/** @internal */
|
||||
final class ReflectionObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
return [new ReflectionObjectBuilder($class)];
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\ObjectBuildersCollision;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ScalarType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function usort;
|
||||
|
||||
/** @internal */
|
||||
final class SortingObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
public function __construct(private ObjectBuilderFactory $delegate) {}
|
||||
|
||||
/**
|
||||
* Will properly sort the object builders, based on the number of arguments
|
||||
* they need, and on the types of the arguments.
|
||||
*
|
||||
* Builders with most parameters will be prioritized, and in the case of
|
||||
* parameters' name collision, the builder with the most specific types will
|
||||
* be prioritized.
|
||||
*
|
||||
* Type priority is as follows:
|
||||
* 1. Non-scalar type
|
||||
* 2. Integer type
|
||||
* 3. Float type
|
||||
* 4. String type
|
||||
* 5. Boolean type
|
||||
*/
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
$builders = $this->delegate->for($class);
|
||||
|
||||
$sortedByArgumentsNumber = [];
|
||||
$sortedByPriority = [];
|
||||
|
||||
foreach ($builders as $builder) {
|
||||
$sortedByArgumentsNumber[$builder->describeArguments()->count()][] = $builder;
|
||||
}
|
||||
|
||||
krsort($sortedByArgumentsNumber);
|
||||
|
||||
foreach ($sortedByArgumentsNumber as $sortedBuilders) {
|
||||
usort($sortedBuilders, $this->sortObjectBuilders(...));
|
||||
|
||||
$sortedByPriority = array_merge($sortedByPriority, $sortedBuilders);
|
||||
}
|
||||
|
||||
return $sortedByPriority;
|
||||
}
|
||||
|
||||
private function sortObjectBuilders(ObjectBuilder $builderA, ObjectBuilder $builderB): int
|
||||
{
|
||||
$argumentsA = $builderA->describeArguments()->toArray();
|
||||
$argumentsB = $builderB->describeArguments()->toArray();
|
||||
|
||||
$sharedArguments = array_keys(array_intersect_key($argumentsA, $argumentsB));
|
||||
|
||||
$winner = null;
|
||||
|
||||
foreach ($sharedArguments as $name) {
|
||||
$typeA = $argumentsA[$name]->type();
|
||||
$typeB = $argumentsB[$name]->type();
|
||||
|
||||
$score = $this->sortTypes($typeA, $typeB);
|
||||
|
||||
if ($score === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newWinner = $score === 1 ? $builderB : $builderA;
|
||||
|
||||
if ($winner && $winner !== $newWinner) {
|
||||
throw new ObjectBuildersCollision($builderA, $builderB);
|
||||
}
|
||||
|
||||
$winner = $newWinner;
|
||||
}
|
||||
|
||||
if ($winner === null && count($sharedArguments) === count($argumentsA)) {
|
||||
throw new ObjectBuildersCollision($builderA, $builderB);
|
||||
}
|
||||
|
||||
// @infection-ignore-all / Incrementing or decrementing sorting value makes no sense, so we ignore it.
|
||||
return $winner === $builderA ? -1 : 1;
|
||||
}
|
||||
|
||||
private function sortTypes(Type $typeA, Type $typeB): int
|
||||
{
|
||||
if ($typeA instanceof ScalarType && $typeB instanceof ScalarType) {
|
||||
return TypeHelper::typePriority($typeB) <=> TypeHelper::typePriority($typeA);
|
||||
}
|
||||
|
||||
if (! $typeA instanceof ScalarType) {
|
||||
// @infection-ignore-all / Decrementing sorting value makes no sense, so we ignore it.
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Argument;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\PermissiveTypeNotAllowed;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\CompositeType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\MixedType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UndefinedObjectType;
|
||||
|
||||
/** @internal */
|
||||
final class StrictTypesObjectBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
public function __construct(private ObjectBuilderFactory $delegate) {}
|
||||
|
||||
public function for(ClassDefinition $class): array
|
||||
{
|
||||
$builders = $this->delegate->for($class);
|
||||
|
||||
foreach ($builders as $builder) {
|
||||
$arguments = $builder->describeArguments();
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
$this->checkPresenceOfPermissiveType($argument, $argument->type());
|
||||
}
|
||||
}
|
||||
|
||||
return $builders;
|
||||
}
|
||||
|
||||
private function checkPresenceOfPermissiveType(Argument $argument, Type $type): void
|
||||
{
|
||||
if ($type instanceof CompositeType) {
|
||||
foreach ($type->traverse() as $subType) {
|
||||
self::checkPresenceOfPermissiveType($argument, $subType);
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof MixedType || $type instanceof UndefinedObjectType) {
|
||||
throw new PermissiveTypeNotAllowed($argument->signature(), $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionObject;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ParameterDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ObjectType;
|
||||
use Exception;
|
||||
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
|
||||
/** @internal */
|
||||
final class FunctionObjectBuilder implements ObjectBuilder
|
||||
{
|
||||
private FunctionObject $function;
|
||||
|
||||
private string $className;
|
||||
|
||||
private Arguments $arguments;
|
||||
|
||||
private bool $isDynamicConstructor;
|
||||
|
||||
public function __construct(FunctionObject $function, ObjectType $type)
|
||||
{
|
||||
$definition = $function->definition;
|
||||
|
||||
$arguments = array_map(
|
||||
fn (ParameterDefinition $parameter) => Argument::fromParameter($parameter),
|
||||
array_values([...$definition->parameters])
|
||||
);
|
||||
|
||||
$this->isDynamicConstructor = $definition->attributes->has(DynamicConstructor::class);
|
||||
|
||||
if ($this->isDynamicConstructor) {
|
||||
array_shift($arguments);
|
||||
}
|
||||
|
||||
$this->function = $function;
|
||||
$this->className = $type->className();
|
||||
$this->arguments = new Arguments(...$arguments);
|
||||
}
|
||||
|
||||
public function describeArguments(): Arguments
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function build(array $arguments): object
|
||||
{
|
||||
$parameters = $this->function->definition->parameters;
|
||||
|
||||
if ($this->isDynamicConstructor) {
|
||||
$arguments[$parameters->at(0)->name] = $this->className;
|
||||
}
|
||||
|
||||
$arguments = new MethodArguments($parameters, $arguments);
|
||||
|
||||
try {
|
||||
/** @var object */
|
||||
return ($this->function->callback)(...$arguments);
|
||||
} catch (Exception $exception) {
|
||||
throw UserlandError::from($exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function signature(): string
|
||||
{
|
||||
return $this->function->definition->signature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Parameters;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function array_values;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class MethodArguments implements IteratorAggregate
|
||||
{
|
||||
/** @var list<mixed> */
|
||||
private array $arguments = [];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $arguments
|
||||
*/
|
||||
public function __construct(Parameters $parameters, array $arguments)
|
||||
{
|
||||
foreach ($parameters as $parameter) {
|
||||
$name = $parameter->name;
|
||||
|
||||
if ($parameter->isVariadic) {
|
||||
$this->arguments = [...$this->arguments, ...array_values($arguments[$name])]; // @phpstan-ignore-line we know that the argument is iterable
|
||||
} else {
|
||||
$this->arguments[] = $arguments[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Parameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
|
||||
use Exception;
|
||||
|
||||
/** @internal */
|
||||
final class MethodObjectBuilder implements ObjectBuilder
|
||||
{
|
||||
private Arguments $arguments;
|
||||
|
||||
public function __construct(
|
||||
private string $className,
|
||||
private string $methodName,
|
||||
private Parameters $parameters
|
||||
) {}
|
||||
|
||||
public function describeArguments(): Arguments
|
||||
{
|
||||
return $this->arguments ??= Arguments::fromParameters($this->parameters);
|
||||
}
|
||||
|
||||
public function build(array $arguments): object
|
||||
{
|
||||
$methodName = $this->methodName;
|
||||
$arguments = new MethodArguments($this->parameters, $arguments);
|
||||
|
||||
try {
|
||||
return ($this->className)::$methodName(...$arguments); // @phpstan-ignore-line
|
||||
} catch (Exception $exception) {
|
||||
throw UserlandError::from($exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function signature(): string
|
||||
{
|
||||
return "$this->className::$this->methodName()";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
|
||||
use Exception;
|
||||
|
||||
/** @internal */
|
||||
final class NativeConstructorObjectBuilder implements ObjectBuilder
|
||||
{
|
||||
private Arguments $arguments;
|
||||
|
||||
public function __construct(private ClassDefinition $class) {}
|
||||
|
||||
public function describeArguments(): Arguments
|
||||
{
|
||||
return $this->arguments ??= Arguments::fromParameters($this->class->methods->constructor()->parameters);
|
||||
}
|
||||
|
||||
public function build(array $arguments): object
|
||||
{
|
||||
$className = $this->class->name;
|
||||
$arguments = new MethodArguments($this->class->methods->constructor()->parameters, $arguments);
|
||||
|
||||
try {
|
||||
return new $className(...$arguments);
|
||||
} catch (Exception $exception) {
|
||||
throw UserlandError::from($exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function signature(): string
|
||||
{
|
||||
return $this->class->methods->constructor()->signature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use BackedEnum;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\Factory\ValueTypeFactory;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\EnumType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnionType;
|
||||
|
||||
/** @internal */
|
||||
class NativeEnumObjectBuilder implements ObjectBuilder
|
||||
{
|
||||
private Arguments $arguments;
|
||||
|
||||
private EnumType $enum;
|
||||
|
||||
public function __construct(EnumType $type)
|
||||
{
|
||||
$types = [];
|
||||
|
||||
foreach ($type->cases() as $case) {
|
||||
$value = $case instanceof BackedEnum ? $case->value : $case->name;
|
||||
|
||||
$types[] = ValueTypeFactory::from($value);
|
||||
}
|
||||
|
||||
$argumentType = count($types) === 1
|
||||
? $types[0]
|
||||
: new UnionType(...$types);
|
||||
|
||||
$this->enum = $type;
|
||||
$this->arguments = new Arguments(
|
||||
new Argument('value', $type->className() . '::$value', $argumentType)
|
||||
);
|
||||
}
|
||||
|
||||
public function describeArguments(): Arguments
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function build(array $arguments): object
|
||||
{
|
||||
return $this->enum->cases()[$arguments['value']];
|
||||
}
|
||||
|
||||
public function signature(): string
|
||||
{
|
||||
return $this->enum->readableSignature();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
/** @internal */
|
||||
interface ObjectBuilder
|
||||
{
|
||||
public function describeArguments(): Arguments;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $arguments
|
||||
*/
|
||||
public function build(array $arguments): object;
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function signature(): string;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\ClassDefinition;
|
||||
|
||||
use function count;
|
||||
|
||||
/** @internal */
|
||||
final class ReflectionObjectBuilder implements ObjectBuilder
|
||||
{
|
||||
private Arguments $arguments;
|
||||
|
||||
public function __construct(private ClassDefinition $class) {}
|
||||
|
||||
public function describeArguments(): Arguments
|
||||
{
|
||||
return $this->arguments ??= Arguments::fromProperties($this->class->properties);
|
||||
}
|
||||
|
||||
public function build(array $arguments): object
|
||||
{
|
||||
$object = new ($this->class->name)();
|
||||
|
||||
if (count($arguments) > 0) {
|
||||
(function () use ($arguments): void {
|
||||
foreach ($arguments as $name => $value) {
|
||||
$this->{$name} = $value; // @phpstan-ignore-line
|
||||
}
|
||||
})->call($object);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function signature(): string
|
||||
{
|
||||
return $this->class->name . ' (properties)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class FileExtensionNotHandled extends LogicException
|
||||
{
|
||||
public function __construct(string $extension)
|
||||
{
|
||||
parent::__construct(
|
||||
"The file extension `$extension` is not handled.",
|
||||
1629991744
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use JsonException;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidJson extends RuntimeException implements InvalidSource
|
||||
{
|
||||
public function __construct(private string $source, ?JsonException $previous = null)
|
||||
{
|
||||
parent::__construct(
|
||||
'Invalid JSON source.',
|
||||
1566307185,
|
||||
$previous
|
||||
);
|
||||
}
|
||||
|
||||
public function source(): string
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/** @api */
|
||||
interface InvalidSource extends Throwable
|
||||
{
|
||||
public function source(): mixed;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidYaml extends RuntimeException implements InvalidSource
|
||||
{
|
||||
public function __construct(private string $source)
|
||||
{
|
||||
parent::__construct(
|
||||
'Invalid YAML source.',
|
||||
1629990223
|
||||
);
|
||||
}
|
||||
|
||||
public function source(): string
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class SourceNotIterable extends RuntimeException implements InvalidSource
|
||||
{
|
||||
public function __construct(private string $source)
|
||||
{
|
||||
parent::__construct(
|
||||
'Invalid source, expected an iterable.',
|
||||
1566307291
|
||||
);
|
||||
}
|
||||
|
||||
public function source(): string
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class UnableToReadFile extends LogicException
|
||||
{
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
parent::__construct(
|
||||
"Unable to read the file `$filename`.",
|
||||
1629993117
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @infection-ignore-all
|
||||
*/
|
||||
final class YamlExtensionNotEnabled extends LogicException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
"The PHP YAML extension is not enabled.",
|
||||
1629990959
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\FileExtensionNotHandled;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\UnableToReadFile;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use SplFileObject;
|
||||
use Traversable;
|
||||
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class FileSource implements IteratorAggregate, IdentifiableSource
|
||||
{
|
||||
private string $filePath;
|
||||
|
||||
/** @var Traversable<mixed> */
|
||||
private Traversable $delegate;
|
||||
|
||||
public function __construct(SplFileObject $file)
|
||||
{
|
||||
$this->filePath = $file->getPathname();
|
||||
|
||||
$content = $file->fread($file->getSize());
|
||||
|
||||
/** @infection-ignore-all */
|
||||
if ($content === false || $content === '') {
|
||||
throw new UnableToReadFile($this->filePath);
|
||||
}
|
||||
|
||||
$this->delegate = match (strtolower($file->getExtension())) {
|
||||
'json' => new JsonSource($content),
|
||||
'yaml', 'yml' => new YamlSource($content),
|
||||
default => throw new FileExtensionNotHandled($file->getExtension()),
|
||||
};
|
||||
}
|
||||
|
||||
public function sourceName(): string
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<mixed>
|
||||
*/
|
||||
public function getIterator(): Iterator
|
||||
{
|
||||
yield from $this->delegate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source;
|
||||
|
||||
/** @api */
|
||||
interface IdentifiableSource
|
||||
{
|
||||
public function sourceName(): string;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\InvalidJson;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\InvalidSource;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\SourceNotIterable;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use JsonException;
|
||||
use Traversable;
|
||||
|
||||
use function is_iterable;
|
||||
use function json_decode;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class JsonSource implements IteratorAggregate
|
||||
{
|
||||
/** @var iterable<mixed> */
|
||||
private iterable $source;
|
||||
|
||||
/**
|
||||
* @throws InvalidSource
|
||||
*/
|
||||
public function __construct(string $jsonSource)
|
||||
{
|
||||
try {
|
||||
$source = json_decode($jsonSource, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
throw new InvalidJson($jsonSource, $e);
|
||||
}
|
||||
|
||||
if (! is_iterable($source)) {
|
||||
throw new SourceNotIterable($jsonSource);
|
||||
}
|
||||
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<mixed>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Modifier;
|
||||
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function is_iterable;
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class CamelCaseKeys implements IteratorAggregate
|
||||
{
|
||||
/** @var array<mixed> */
|
||||
private array $source;
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $source
|
||||
*/
|
||||
public function __construct(iterable $source)
|
||||
{
|
||||
$this->source = $this->replace($source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $source
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function replace(iterable $source): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($source as $key => $value) {
|
||||
if (is_iterable($value)) {
|
||||
$value = $this->replace($value);
|
||||
}
|
||||
|
||||
if (! is_string($key)) {
|
||||
$result[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$camelCaseKey = $this->camelCaseKeys($key);
|
||||
|
||||
if (isset($result[$camelCaseKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$camelCaseKey] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function camelCaseKeys(string $key): string
|
||||
{
|
||||
return lcfirst(str_replace([' ', '_', '-'], '', ucwords($key, ' _-')));
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Modifier;
|
||||
|
||||
/** @internal */
|
||||
final class Mapping
|
||||
{
|
||||
/** @var array<string> */
|
||||
private array $keys;
|
||||
|
||||
private string $to;
|
||||
|
||||
private int $depth;
|
||||
|
||||
/**
|
||||
* @param array<string> $keys
|
||||
*/
|
||||
public function __construct(array $keys, string $to)
|
||||
{
|
||||
$this->keys = $keys;
|
||||
$this->to = $to;
|
||||
$this->depth = count($keys) - 1;
|
||||
}
|
||||
|
||||
public function matches(int|string $key, int $atDepth): bool
|
||||
{
|
||||
$from = $this->keys[$atDepth] ?? null;
|
||||
|
||||
return $from === (string)$key || $from === '*';
|
||||
}
|
||||
|
||||
public function findMappedKey(int|string $key, int $atDepth): ?string
|
||||
{
|
||||
if ($atDepth < $this->depth
|
||||
|| !$this->matches($key, $atDepth)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->to;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Modifier;
|
||||
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function explode;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class PathMapping implements IteratorAggregate
|
||||
{
|
||||
/** @var array<mixed> */
|
||||
private array $source;
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $source
|
||||
* @param array<string> $map
|
||||
*/
|
||||
public function __construct(iterable $source, array $map)
|
||||
{
|
||||
$this->source = $this->map($source, $this->prepareMappings($map));
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $source
|
||||
* @param array<Mapping> $mappings
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function map(iterable $source, array $mappings, int $depth = 0): array
|
||||
{
|
||||
$out = [];
|
||||
|
||||
foreach ($source as $key => $value) {
|
||||
/** @var int|string $key */
|
||||
$newMappings = array_filter($mappings, fn (Mapping $mapping) => $mapping->matches($key, $depth));
|
||||
|
||||
$newKey = $this->findMapping($newMappings, $depth, $key);
|
||||
|
||||
if (is_array($value)) {
|
||||
$out[$newKey] = $this->map($value, $newMappings, $depth + 1);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[$newKey] = $value;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $map
|
||||
* @return array<Mapping>
|
||||
*/
|
||||
private function prepareMappings(array $map): array
|
||||
{
|
||||
$mappings = [];
|
||||
|
||||
foreach ($map as $from => $to) {
|
||||
$mappings[] = new Mapping(explode('.', (string)$from), $to);
|
||||
}
|
||||
|
||||
return $mappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Mapping> $mappings
|
||||
*/
|
||||
private function findMapping(array $mappings, int $atDepth, int|string $key): int|string
|
||||
{
|
||||
foreach ($mappings as $mapping) {
|
||||
$mappedKey = $mapping->findMappedKey($key, $atDepth);
|
||||
|
||||
if (null !== $mappedKey) {
|
||||
return $mappedKey;
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\InvalidSource;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Modifier\CamelCaseKeys;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Modifier\PathMapping;
|
||||
use IteratorAggregate;
|
||||
use SplFileObject;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class Source implements IteratorAggregate
|
||||
{
|
||||
private function __construct(
|
||||
/** @var iterable<mixed> */
|
||||
private iterable $delegate
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $data
|
||||
*/
|
||||
public static function iterable(iterable $data): Source
|
||||
{
|
||||
return new Source($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
public static function array(array $data): Source
|
||||
{
|
||||
return new Source($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidSource
|
||||
*/
|
||||
public static function json(string $jsonSource): Source
|
||||
{
|
||||
return new Source(new JsonSource($jsonSource));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidSource
|
||||
*/
|
||||
public static function yaml(string $yamlSource): Source
|
||||
{
|
||||
return new Source(new YamlSource($yamlSource));
|
||||
}
|
||||
|
||||
public static function file(SplFileObject $file): Source
|
||||
{
|
||||
return new Source(new FileSource($file));
|
||||
}
|
||||
|
||||
public function camelCaseKeys(): Source
|
||||
{
|
||||
return new Source(new CamelCaseKeys($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $map
|
||||
*/
|
||||
public function map(array $map): Source
|
||||
{
|
||||
return new Source(new PathMapping($this, $map));
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->delegate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\InvalidSource;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\InvalidYaml;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\SourceNotIterable;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Exception\YamlExtensionNotEnabled;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
|
||||
use Traversable;
|
||||
|
||||
use function function_exists;
|
||||
use function is_iterable;
|
||||
use function yaml_parse;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class YamlSource implements IteratorAggregate
|
||||
{
|
||||
/** @var iterable<mixed> */
|
||||
private iterable $source;
|
||||
|
||||
/**
|
||||
* @throws InvalidSource
|
||||
*/
|
||||
public function __construct(string $yamlSource)
|
||||
{
|
||||
/** @infection-ignore-all */
|
||||
// @codeCoverageIgnoreStart
|
||||
if (! function_exists('yaml_parse')) {
|
||||
throw new YamlExtensionNotEnabled();
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$source = @yaml_parse($yamlSource);
|
||||
|
||||
if ($source === false) {
|
||||
throw new InvalidYaml($yamlSource);
|
||||
}
|
||||
|
||||
if (! is_iterable($source)) {
|
||||
throw new SourceNotIterable($yamlSource);
|
||||
}
|
||||
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<mixed>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
yield from $this->source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidIterableKeyType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidTraversableKey;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\CompositeTraversableType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ArrayType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\IterableType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NonEmptyArrayType;
|
||||
|
||||
use function assert;
|
||||
use function is_int;
|
||||
use function is_iterable;
|
||||
use function is_string;
|
||||
|
||||
/** @internal */
|
||||
final class ArrayNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
$value = $shell->value();
|
||||
|
||||
assert($type instanceof ArrayType || $type instanceof NonEmptyArrayType || $type instanceof IterableType);
|
||||
|
||||
if ($shell->allowUndefinedValues() && $value === null) {
|
||||
return TreeNode::branch($shell, [], []);
|
||||
}
|
||||
|
||||
if (! is_iterable($value)) {
|
||||
return TreeNode::error($shell, new SourceMustBeIterable($value, $type));
|
||||
}
|
||||
|
||||
$children = $this->children($type, $shell, $rootBuilder);
|
||||
$array = $this->buildArray($children);
|
||||
|
||||
return TreeNode::branch($shell, $array, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<TreeNode>
|
||||
*/
|
||||
private function children(CompositeTraversableType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
|
||||
{
|
||||
/** @var iterable<mixed> $values */
|
||||
$values = $shell->value();
|
||||
$keyType = $type->keyType();
|
||||
$subType = $type->subType();
|
||||
|
||||
$children = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (! is_string($key) && ! is_int($key)) {
|
||||
throw new InvalidIterableKeyType($key, $shell->path());
|
||||
}
|
||||
|
||||
$child = $shell->child((string)$key, $subType);
|
||||
|
||||
if (! $keyType->accepts($key)) {
|
||||
$children[$key] = TreeNode::error($child, new InvalidTraversableKey($key, $keyType));
|
||||
} else {
|
||||
$children[$key] = $rootBuilder->build($child->withValue($value));
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<TreeNode> $children
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function buildArray(array $children): ?array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($children as $key => $child) {
|
||||
if (! $child->isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$array[$key] = $child->value();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Arguments;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ArgumentsValues;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\InvalidSource;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CannotInferFinalClass;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveObjectType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InterfaceHasBothConstructorAndInfer;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\InterfaceType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NativeClassType;
|
||||
use Throwable;
|
||||
|
||||
/** @internal */
|
||||
final class InterfaceNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private NodeBuilder $delegate,
|
||||
private ObjectImplementations $implementations,
|
||||
private ClassDefinitionRepository $classDefinitionRepository,
|
||||
private FunctionsContainer $constructors,
|
||||
/** @var callable(Throwable): ErrorMessage */
|
||||
private mixed $exceptionFilter,
|
||||
) {}
|
||||
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
|
||||
if (! $type instanceof InterfaceType && ! $type instanceof NativeClassType) {
|
||||
return $this->delegate->build($shell, $rootBuilder);
|
||||
}
|
||||
|
||||
if ($type->accepts($shell->value())) {
|
||||
return TreeNode::leaf($shell, $shell->value());
|
||||
}
|
||||
|
||||
if ($this->constructorRegisteredFor($type)) {
|
||||
if ($this->implementations->has($type->className())) {
|
||||
throw new InterfaceHasBothConstructorAndInfer($type->className());
|
||||
}
|
||||
|
||||
return $this->delegate->build($shell, $rootBuilder);
|
||||
}
|
||||
|
||||
if ($shell->allowUndefinedValues() && $shell->value() === null) {
|
||||
$shell = $shell->withValue([]);
|
||||
} else {
|
||||
$shell = $shell->transformIteratorToArray();
|
||||
}
|
||||
|
||||
$className = $type->className();
|
||||
|
||||
if (! $this->implementations->has($className)) {
|
||||
if ($type instanceof InterfaceType || $this->classDefinitionRepository->for($type)->isAbstract) {
|
||||
throw new CannotResolveObjectType($className);
|
||||
}
|
||||
|
||||
return $this->delegate->build($shell, $rootBuilder);
|
||||
}
|
||||
|
||||
$function = $this->implementations->function($className);
|
||||
$arguments = Arguments::fromParameters($function->parameters);
|
||||
|
||||
if ($type instanceof NativeClassType && $this->classDefinitionRepository->for($type)->isFinal) {
|
||||
throw new CannotInferFinalClass($type, $function);
|
||||
}
|
||||
|
||||
$argumentsValues = ArgumentsValues::forInterface($arguments, $shell);
|
||||
|
||||
if ($argumentsValues->hasInvalidValue()) {
|
||||
return TreeNode::error($shell, new InvalidSource($shell->value(), $arguments));
|
||||
}
|
||||
|
||||
$children = $this->children($shell, $argumentsValues, $rootBuilder);
|
||||
|
||||
$values = [];
|
||||
|
||||
foreach ($children as $child) {
|
||||
if (! $child->isValid()) {
|
||||
return TreeNode::branch($shell, null, $children);
|
||||
}
|
||||
|
||||
$values[] = $child->value();
|
||||
}
|
||||
|
||||
try {
|
||||
$classType = $this->implementations->implementation($className, $values);
|
||||
} catch (ObjectImplementationCallbackError $exception) {
|
||||
$exception = ($this->exceptionFilter)($exception->original());
|
||||
|
||||
return TreeNode::error($shell, $exception);
|
||||
}
|
||||
|
||||
$shell = $shell->withType($classType);
|
||||
$shell = $shell->withAllowedSuperfluousKeys($arguments->names());
|
||||
|
||||
return $this->delegate->build($shell, $rootBuilder);
|
||||
}
|
||||
|
||||
private function constructorRegisteredFor(Type $type): bool
|
||||
{
|
||||
foreach ($this->constructors as $constructor) {
|
||||
if ($type->matches($constructor->definition->returnType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<TreeNode>
|
||||
*/
|
||||
private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuilder $rootBuilder): array
|
||||
{
|
||||
$children = [];
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
$name = $argument->name();
|
||||
$type = $argument->type();
|
||||
$attributes = $argument->attributes();
|
||||
|
||||
$child = $shell->child($name, $type, $attributes);
|
||||
|
||||
if ($arguments->hasValue($name)) {
|
||||
$child = $child->withValue($arguments->getValue($name));
|
||||
}
|
||||
|
||||
$children[] = $rootBuilder->build($child);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidIterableKeyType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidListKey;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\CompositeTraversableType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ListType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NonEmptyListType;
|
||||
|
||||
use function assert;
|
||||
use function is_int;
|
||||
use function is_iterable;
|
||||
use function is_string;
|
||||
|
||||
/** @internal */
|
||||
final class ListNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
$value = $shell->value();
|
||||
|
||||
assert($type instanceof ListType || $type instanceof NonEmptyListType);
|
||||
|
||||
if ($shell->allowUndefinedValues() && $value === null) {
|
||||
return TreeNode::branch($shell, [], []);
|
||||
}
|
||||
|
||||
if (! is_iterable($value)) {
|
||||
return TreeNode::error($shell, new SourceMustBeIterable($value, $type));
|
||||
}
|
||||
|
||||
$children = $this->children($type, $shell, $rootBuilder);
|
||||
$array = $this->buildArray($children);
|
||||
|
||||
return TreeNode::branch($shell, $array, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<TreeNode>
|
||||
*/
|
||||
private function children(CompositeTraversableType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
|
||||
{
|
||||
/** @var iterable<mixed> $values */
|
||||
$values = $shell->value();
|
||||
$subType = $type->subType();
|
||||
|
||||
$expected = 0;
|
||||
$children = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (! is_string($key) && ! is_int($key)) {
|
||||
throw new InvalidIterableKeyType($key, $shell->path());
|
||||
}
|
||||
|
||||
if ($shell->allowNonSequentialList() || $key === $expected) {
|
||||
$child = $shell->child((string)$expected, $subType);
|
||||
$children[$expected] = $rootBuilder->build($child->withValue($value));
|
||||
} else {
|
||||
$child = $shell->child((string)$key, $subType);
|
||||
$children[$key] = TreeNode::error($child, new InvalidListKey($key, $expected));
|
||||
}
|
||||
|
||||
$expected++;
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<TreeNode> $children
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function buildArray(array $children): ?array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($children as $key => $child) {
|
||||
if (! $child->isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$array[$key] = $child->value();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CannotMapToPermissiveType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\MixedType;
|
||||
|
||||
/** @internal */
|
||||
final class MixedNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
assert($shell->type() instanceof MixedType);
|
||||
|
||||
if (! $shell->allowPermissiveTypes()) {
|
||||
throw new CannotMapToPermissiveType($shell);
|
||||
}
|
||||
|
||||
return TreeNode::leaf($shell, $shell->value());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
|
||||
/** @internal */
|
||||
interface NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\SourceIsNotNull;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NullType;
|
||||
|
||||
use function assert;
|
||||
|
||||
/** @internal */
|
||||
final class NullNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
$value = $shell->value();
|
||||
|
||||
assert($type instanceof NullType);
|
||||
|
||||
if ($value !== null) {
|
||||
return TreeNode::error($shell, new SourceIsNotNull());
|
||||
}
|
||||
|
||||
return TreeNode::leaf($shell, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationNotRegistered;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\ResolvedImplementationIsNotAccepted;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ClassType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ClassStringType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\InterfaceType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnionType;
|
||||
use Exception;
|
||||
|
||||
use function assert;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectImplementations
|
||||
{
|
||||
/** @var array<string, non-empty-array<string, ClassType>> */
|
||||
private array $implementations = [];
|
||||
|
||||
public function __construct(
|
||||
private FunctionsContainer $functions,
|
||||
private TypeParser $typeParser
|
||||
) {}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return $this->functions->has($name);
|
||||
}
|
||||
|
||||
public function function(string $name): FunctionDefinition
|
||||
{
|
||||
return $this->functions->get($name)->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $arguments
|
||||
*/
|
||||
public function implementation(string $name, array $arguments): ClassType
|
||||
{
|
||||
/** @infection-ignore-all / We cannot test the assignment */
|
||||
$this->implementations[$name] ??= $this->implementations($name);
|
||||
|
||||
$class = $this->call($name, $arguments);
|
||||
|
||||
return $this->implementations[$name][$class]
|
||||
?? throw new ObjectImplementationNotRegistered($class, $name, $this->implementations[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $arguments
|
||||
*/
|
||||
private function call(string $name, array $arguments): string
|
||||
{
|
||||
try {
|
||||
$signature = ($this->functions->get($name)->callback)(...$arguments);
|
||||
} catch (Exception $exception) {
|
||||
throw new ObjectImplementationCallbackError($name, $exception);
|
||||
}
|
||||
|
||||
if (! is_string($signature)) {
|
||||
throw new InvalidResolvedImplementationValue($name, $signature);
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-array<string, ClassType>
|
||||
*/
|
||||
private function implementations(string $name): array
|
||||
{
|
||||
$function = $this->functions->get($name)->definition;
|
||||
|
||||
$type = $this->typeParser->parse($name);
|
||||
|
||||
/** @infection-ignore-all */
|
||||
assert($type instanceof InterfaceType || $type instanceof ClassType);
|
||||
|
||||
$classes = $this->implementationsByReturnSignature($name, $function);
|
||||
|
||||
if ($classes === []) {
|
||||
throw new MissingObjectImplementationRegistration($name, $function);
|
||||
}
|
||||
|
||||
foreach ($classes as $classType) {
|
||||
if (! $classType instanceof ClassType || ! $classType->matches($type)) {
|
||||
throw new ResolvedImplementationIsNotAccepted($name, $classType);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var non-empty-array<string, ClassType> $classes */
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Type>
|
||||
*/
|
||||
private function implementationsByReturnSignature(string $name, FunctionDefinition $function): array
|
||||
{
|
||||
$returnType = $function->returnType;
|
||||
|
||||
if (! $returnType instanceof ClassStringType && ! $returnType instanceof UnionType) {
|
||||
if (count($function->parameters) > 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$class = $this->call($name, []);
|
||||
$classType = $this->typeParser->parse($class);
|
||||
|
||||
return [$classType->toString() => $classType];
|
||||
}
|
||||
|
||||
$types = $returnType instanceof UnionType
|
||||
? $returnType->types()
|
||||
: [$returnType];
|
||||
|
||||
$classes = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
if (! $type instanceof ClassStringType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$subType = $type->subType();
|
||||
|
||||
if ($subType === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$subTypes = $subType instanceof UnionType
|
||||
? $subType->types()
|
||||
: [$subType];
|
||||
|
||||
foreach ($subTypes as $classType) {
|
||||
$classes[$classType->toString()] = $classType;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ArgumentsValues;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\CannotFindObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Exception\InvalidSource;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CircularDependencyDetected;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Message;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ObjectType;
|
||||
use Throwable;
|
||||
|
||||
use function assert;
|
||||
use function count;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private ClassDefinitionRepository $classDefinitionRepository,
|
||||
private ObjectBuilderFactory $objectBuilderFactory,
|
||||
/** @var callable(Throwable): ErrorMessage */
|
||||
private mixed $exceptionFilter,
|
||||
) {}
|
||||
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
|
||||
// @infection-ignore-all
|
||||
assert($type instanceof ObjectType);
|
||||
|
||||
if ($type->accepts($shell->value())) {
|
||||
return TreeNode::leaf($shell, $shell->value());
|
||||
}
|
||||
|
||||
if ($shell->allowUndefinedValues() && $shell->value() === null) {
|
||||
$shell = $shell->withValue([]);
|
||||
} else {
|
||||
$shell = $shell->transformIteratorToArray();
|
||||
}
|
||||
|
||||
$class = $this->classDefinitionRepository->for($type);
|
||||
$builders = $this->objectBuilderFactory->for($class);
|
||||
|
||||
foreach ($builders as $builder) {
|
||||
$argumentsValues = ArgumentsValues::forClass($builder->describeArguments(), $shell);
|
||||
|
||||
if ($argumentsValues->hasInvalidValue()) {
|
||||
if (count($builders) === 1) {
|
||||
return TreeNode::error($shell, new InvalidSource($shell->value(), $builder->describeArguments()));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$children = $this->children($shell, $argumentsValues, $rootBuilder);
|
||||
|
||||
try {
|
||||
$object = $this->buildObject($builder, $children);
|
||||
} catch (Message $exception) {
|
||||
if ($exception instanceof UserlandError) {
|
||||
$exception = ($this->exceptionFilter)($exception->previous());
|
||||
}
|
||||
|
||||
return TreeNode::error($shell, $exception);
|
||||
}
|
||||
|
||||
if ($argumentsValues->hadSingleArgument()) {
|
||||
$node = TreeNode::flattenedBranch($shell, $object, $children[0]);
|
||||
} else {
|
||||
$node = TreeNode::branch($shell, $object, $children);
|
||||
$node = $node->checkUnexpectedKeys();
|
||||
}
|
||||
|
||||
if ($node->isValid() || count($builders) === 1) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
return TreeNode::error($shell, new CannotFindObjectBuilder($builders));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<TreeNode>
|
||||
*/
|
||||
private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuilder $rootBuilder): array
|
||||
{
|
||||
$children = [];
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
$name = $argument->name();
|
||||
$type = $argument->type();
|
||||
$attributes = $argument->attributes();
|
||||
|
||||
$child = $shell->child($name, $type, $attributes);
|
||||
|
||||
if ($arguments->hasValue($name)) {
|
||||
$child = $child->withValue($arguments->getValue($name));
|
||||
}
|
||||
|
||||
// This whole block is needed to detect object circular dependencies
|
||||
// and prevent infinite loops.
|
||||
if ($rootBuilder->typeWasSeen($type)) {
|
||||
// An exception is thrown only when the type of the property is
|
||||
// literally the same as the type of the object being built.
|
||||
// Otherwise, the property type might be a union, for instance,
|
||||
// so we do not want to stop the script execution right away
|
||||
// because the value might be valid.
|
||||
if (count($arguments) === 1 && $type instanceof ObjectType) {
|
||||
throw new CircularDependencyDetected($argument);
|
||||
}
|
||||
|
||||
$children[] = TreeNode::error($shell, new InvalidNodeValue($type));
|
||||
} else {
|
||||
$childBuilder = $rootBuilder;
|
||||
|
||||
if ($type->matches($shell->type())) {
|
||||
$childBuilder = $rootBuilder->withTypeAsCurrentRoot($type);
|
||||
}
|
||||
|
||||
$children[] = $childBuilder->build($child);
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<TreeNode> $children
|
||||
*/
|
||||
private function buildObject(ObjectBuilder $builder, array $children): ?object
|
||||
{
|
||||
$arguments = [];
|
||||
|
||||
foreach ($children as $child) {
|
||||
if (! $child->isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$arguments[$child->name()] = $child->value();
|
||||
}
|
||||
|
||||
return $builder->build($arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\MissingNodeValue;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
|
||||
/** @internal */
|
||||
final class RootNodeBuilder
|
||||
{
|
||||
/**
|
||||
* This property is used to detect circular references for objects.
|
||||
*/
|
||||
public Type $currentRootType;
|
||||
|
||||
public function __construct(private NodeBuilder $root) {}
|
||||
|
||||
public function build(Shell $shell): TreeNode
|
||||
{
|
||||
if (! $shell->hasValue()) {
|
||||
if (! $shell->allowUndefinedValues()) {
|
||||
return TreeNode::error($shell, new MissingNodeValue($shell->type()));
|
||||
}
|
||||
|
||||
$shell = $shell->withValue(null);
|
||||
}
|
||||
|
||||
return $this->root->build($shell, $this);
|
||||
}
|
||||
|
||||
public function withTypeAsCurrentRoot(Type $type): self
|
||||
{
|
||||
$self = clone $this;
|
||||
$self->currentRootType = $type;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
public function typeWasSeen(Type $type): bool
|
||||
{
|
||||
return isset($this->currentRootType)
|
||||
&& $type->toString() === $this->currentRootType->toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ScalarType;
|
||||
|
||||
use function assert;
|
||||
|
||||
/** @internal */
|
||||
final class ScalarNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
$value = $shell->value();
|
||||
|
||||
assert($type instanceof ScalarType);
|
||||
|
||||
if ($type->accepts($value)) {
|
||||
return TreeNode::leaf($shell, $value);
|
||||
}
|
||||
|
||||
if (! $shell->allowScalarValueCasting() || ! $type->canCast($value)) {
|
||||
return TreeNode::error($shell, $type->errorMessage());
|
||||
}
|
||||
|
||||
return TreeNode::leaf($shell, $type->cast($value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ShapedArrayType;
|
||||
|
||||
use function array_key_exists;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
use function is_iterable;
|
||||
|
||||
/** @internal */
|
||||
final class ShapedArrayNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
$value = $shell->value();
|
||||
|
||||
assert($type instanceof ShapedArrayType);
|
||||
|
||||
if (! is_iterable($value)) {
|
||||
return TreeNode::error($shell, new SourceMustBeIterable($value, $type));
|
||||
}
|
||||
|
||||
$children = $this->children($type, $shell, $rootBuilder);
|
||||
|
||||
$array = $this->buildArray($children);
|
||||
|
||||
$node = TreeNode::branch($shell, $array, $children);
|
||||
$node = $node->checkUnexpectedKeys();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<TreeNode>
|
||||
*/
|
||||
private function children(ShapedArrayType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
|
||||
{
|
||||
/** @var iterable<mixed> $value */
|
||||
$value = $shell->value();
|
||||
$elements = $type->elements();
|
||||
$children = [];
|
||||
|
||||
if (! is_array($value)) {
|
||||
$value = iterator_to_array($value);
|
||||
}
|
||||
|
||||
foreach ($elements as $element) {
|
||||
$key = $element->key()->value();
|
||||
|
||||
$child = $shell->child((string)$key, $element->type());
|
||||
|
||||
if (array_key_exists($key, $value)) {
|
||||
$child = $child->withValue($value[$key]);
|
||||
} elseif ($element->isOptional()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$children[$key] = $rootBuilder->build($child);
|
||||
|
||||
unset($value[$key]);
|
||||
}
|
||||
|
||||
if ($type->isUnsealed()) {
|
||||
$unsealedShell = $shell->withType($type->unsealedType())->withValue($value);
|
||||
$unsealedChildren = $rootBuilder->build($unsealedShell)->children();
|
||||
|
||||
foreach ($unsealedChildren as $unsealedChild) {
|
||||
$children[$unsealedChild->name()] = $unsealedChild;
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<TreeNode> $children
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function buildArray(array $children): ?array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($children as $key => $child) {
|
||||
if (! $child->isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$array[$key] = $child->value();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\UnexpectedKeysInSource;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Message;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Node;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use Throwable;
|
||||
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
|
||||
/** @internal */
|
||||
final class TreeNode
|
||||
{
|
||||
private Shell $shell;
|
||||
|
||||
private mixed $value;
|
||||
|
||||
/** @var array<self> */
|
||||
private array $children = [];
|
||||
|
||||
/** @var array<Message> */
|
||||
private array $messages = [];
|
||||
|
||||
private bool $valid = true;
|
||||
|
||||
private function __construct(Shell $shell, mixed $value)
|
||||
{
|
||||
$this->shell = $shell;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public static function leaf(Shell $shell, mixed $value): self
|
||||
{
|
||||
$instance = new self($shell, $value);
|
||||
$instance->check();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<self> $children
|
||||
*/
|
||||
public static function branch(Shell $shell, mixed $value, array $children): self
|
||||
{
|
||||
$instance = new self($shell, $value);
|
||||
|
||||
foreach ($children as $child) {
|
||||
$instance->children[$child->name()] = $child;
|
||||
}
|
||||
|
||||
$instance->check();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public static function flattenedBranch(Shell $shell, mixed $value, self $child): self
|
||||
{
|
||||
$instance = new self($shell, $value);
|
||||
$instance->messages = $child->messages;
|
||||
$instance->children = $child->children;
|
||||
$instance->valid = $child->valid;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public static function error(Shell $shell, Throwable&Message $message): self
|
||||
{
|
||||
return (new self($shell, null))->withMessage($message);
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return $this->shell->name();
|
||||
}
|
||||
|
||||
public function type(): Type
|
||||
{
|
||||
return $this->shell->type();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<self>
|
||||
*/
|
||||
public function children(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
return $this->valid;
|
||||
}
|
||||
|
||||
public function withValue(mixed $value): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->value = $value;
|
||||
$clone->check();
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function value(): mixed
|
||||
{
|
||||
assert($this->valid, "Trying to get value of an invalid node at path `{$this->shell->path()}`.");
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function withMessage(Message $message): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->messages[] = $message;
|
||||
$clone->valid = $clone->valid && ! $message instanceof Throwable;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function node(): Node
|
||||
{
|
||||
return $this->buildNode($this);
|
||||
}
|
||||
|
||||
public function checkUnexpectedKeys(): self
|
||||
{
|
||||
$value = $this->shell->value();
|
||||
|
||||
if ($this->shell->allowSuperfluousKeys() || ! is_array($value)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$diff = array_diff(array_keys($value), array_keys($this->children), $this->shell->allowedSuperfluousKeys());
|
||||
|
||||
if ($diff !== []) {
|
||||
return $this->withMessage(new UnexpectedKeysInSource($value, $this->children));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function check(): void
|
||||
{
|
||||
foreach ($this->children as $child) {
|
||||
if (! $child->valid) {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
$type = $this->shell->type();
|
||||
|
||||
if ($this->valid && ! $type->accepts($this->value)) {
|
||||
$this->valid = false;
|
||||
$this->messages[] = new InvalidNodeValue($type);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildNode(self $self): Node
|
||||
{
|
||||
return new Node(
|
||||
$self->shell->isRoot(),
|
||||
$self->shell->name(),
|
||||
$self->shell->path(),
|
||||
$self->shell->type()->toString(),
|
||||
$self->shell->hasValue(),
|
||||
$self->shell->hasValue() ? $self->shell->value() : null,
|
||||
$self->valid ? $self->value : null,
|
||||
$self->messages,
|
||||
array_map(
|
||||
fn (self $child) => $self->buildNode($child),
|
||||
$self->children
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ArrayType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\EnumType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\InterfaceType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\IterableType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ListType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\MixedType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NativeClassType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NonEmptyArrayType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NonEmptyListType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NullType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ShapedArrayType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UndefinedObjectType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnionType;
|
||||
|
||||
/** @internal */
|
||||
final class TypeNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private ArrayNodeBuilder $arrayNodeBuilder,
|
||||
private ListNodeBuilder $listNodeBuilder,
|
||||
private ShapedArrayNodeBuilder $shapedArrayNodeBuilder,
|
||||
private ScalarNodeBuilder $scalarNodeBuilder,
|
||||
private UnionNodeBuilder $unionNodeBuilder,
|
||||
private NullNodeBuilder $nullNodeBuilder,
|
||||
private MixedNodeBuilder $mixedNodeBuilder,
|
||||
private UndefinedObjectNodeBuilder $undefinedObjectNodeBuilder,
|
||||
private ObjectNodeBuilder $objectNodeBuilder,
|
||||
) {}
|
||||
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$builder = match ($shell->type()::class) {
|
||||
// List
|
||||
ListType::class,
|
||||
NonEmptyListType::class => $this->listNodeBuilder,
|
||||
|
||||
// Array
|
||||
ArrayType::class,
|
||||
NonEmptyArrayType::class,
|
||||
IterableType::class => $this->arrayNodeBuilder,
|
||||
|
||||
// ShapedArray
|
||||
ShapedArrayType::class => $this->shapedArrayNodeBuilder,
|
||||
|
||||
// Union
|
||||
UnionType::class => $this->unionNodeBuilder,
|
||||
|
||||
// Null
|
||||
NullType::class => $this->nullNodeBuilder,
|
||||
|
||||
// Mixed
|
||||
MixedType::class => $this->mixedNodeBuilder,
|
||||
|
||||
// Undefined object
|
||||
UndefinedObjectType::class => $this->undefinedObjectNodeBuilder,
|
||||
|
||||
// Object
|
||||
NativeClassType::class,
|
||||
EnumType::class,
|
||||
InterfaceType::class => $this->objectNodeBuilder,
|
||||
|
||||
// Scalar
|
||||
default => $this->scalarNodeBuilder,
|
||||
};
|
||||
|
||||
return $builder->build($shell, $rootBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CannotMapToPermissiveType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UndefinedObjectType;
|
||||
|
||||
use function assert;
|
||||
|
||||
/** @internal */
|
||||
final class UndefinedObjectNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
assert($shell->type() instanceof UndefinedObjectType);
|
||||
|
||||
if (! $shell->allowPermissiveTypes()) {
|
||||
throw new CannotMapToPermissiveType($shell);
|
||||
}
|
||||
|
||||
return TreeNode::leaf($shell, $shell->value());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveObjectType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveTypeFromUnion;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception\TooManyResolvedTypesFromUnion;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ClassType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ScalarType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\InterfaceType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\NullType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ShapedArrayType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnionType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
|
||||
use function count;
|
||||
use function krsort;
|
||||
use function reset;
|
||||
use function usort;
|
||||
|
||||
/** @internal */
|
||||
final class UnionNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$type = $shell->type();
|
||||
|
||||
assert($type instanceof UnionType);
|
||||
|
||||
$structs = [];
|
||||
$scalars = [];
|
||||
$all = [];
|
||||
|
||||
foreach ($type->types() as $subType) {
|
||||
// @infection-ignore-all / This is a performance optimisation, so we
|
||||
// cannot easily test this behavior.
|
||||
if ($subType instanceof NullType && $shell->value() === null) {
|
||||
return TreeNode::leaf($shell, null);
|
||||
}
|
||||
|
||||
try {
|
||||
$node = $rootBuilder->build($shell->withType($subType));
|
||||
} catch (CannotResolveObjectType) {
|
||||
// We catch a special case where an interface type from the
|
||||
// union has no implementation. In this case, we just ignore the
|
||||
// exception and let the other types handle the value.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $node->isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$all[] = $node;
|
||||
|
||||
if ($subType instanceof InterfaceType || $subType instanceof ClassType || $subType instanceof ShapedArrayType) {
|
||||
$structs[] = $node;
|
||||
} elseif ($subType instanceof ScalarType) {
|
||||
$scalars[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
if ($all === []) {
|
||||
return TreeNode::error($shell, new CannotResolveTypeFromUnion($shell->value(), $type));
|
||||
}
|
||||
|
||||
if (count($all) === 1) {
|
||||
return $all[0];
|
||||
}
|
||||
|
||||
// If there is only one scalar and one struct, the scalar has priority.
|
||||
if (count($scalars) === 1 && count($structs) === 1) {
|
||||
return $scalars[0];
|
||||
}
|
||||
|
||||
if ($structs !== []) {
|
||||
// Structs can be either an interface, a class or a shaped array.
|
||||
// We prioritize the one with the most children, as it's the most
|
||||
// specific type. If there are multiple types with the same number
|
||||
// of children, we consider it as a collision.
|
||||
$childrenCount = [];
|
||||
|
||||
foreach ($structs as $node) {
|
||||
$childrenCount[count($node->children())][] = $node;
|
||||
}
|
||||
|
||||
krsort($childrenCount);
|
||||
|
||||
$first = reset($childrenCount);
|
||||
|
||||
if (count($first) === 1) {
|
||||
return $first[0];
|
||||
}
|
||||
} elseif ($scalars !== []) {
|
||||
usort(
|
||||
$scalars,
|
||||
fn (TreeNode $a, TreeNode $b): int => TypeHelper::typePriority($b->type()) <=> TypeHelper::typePriority($a->type()),
|
||||
);
|
||||
|
||||
return $scalars[0];
|
||||
}
|
||||
|
||||
return TreeNode::error($shell, new TooManyResolvedTypesFromUnion($type));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
|
||||
/** @internal */
|
||||
final class ValueAlteringNodeBuilder implements NodeBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private NodeBuilder $delegate,
|
||||
private FunctionsContainer $functions
|
||||
) {}
|
||||
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
|
||||
{
|
||||
$node = $this->delegate->build($shell, $rootBuilder);
|
||||
|
||||
if (! $node->isValid()) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$value = $node->value();
|
||||
|
||||
foreach ($this->functions as $function) {
|
||||
$parameters = $function->definition->parameters;
|
||||
|
||||
if (count($parameters) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$firstParameterType = $parameters->at(0)->type;
|
||||
|
||||
if (! $firstParameterType->accepts($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = ($function->callback)($value);
|
||||
$node = $node->withValue($value);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ClassType;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class CannotInferFinalClass extends RuntimeException
|
||||
{
|
||||
public function __construct(ClassType $class, FunctionDefinition $function)
|
||||
{
|
||||
parent::__construct(
|
||||
"Cannot infer final class `{$class->className()}` with function `$function->signature`.",
|
||||
1671468163
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class CannotMapToPermissiveType extends LogicException
|
||||
{
|
||||
public function __construct(Shell $shell)
|
||||
{
|
||||
$type = $shell->type()->toString();
|
||||
|
||||
parent::__construct(
|
||||
"Type `$type` at path `{$shell->path()}` is not allowed in strict mode. " .
|
||||
"In case `$type` is really needed, the `allowPermissiveTypes` setting can be used.",
|
||||
1736935538,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class CannotResolveObjectType extends RuntimeException
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
parent::__construct(
|
||||
"Impossible to resolve an implementation for `$name`.",
|
||||
1618049116
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnionType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_map;
|
||||
use function implode;
|
||||
|
||||
/** @internal */
|
||||
final class CannotResolveTypeFromUnion extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(mixed $source, UnionType $unionType)
|
||||
{
|
||||
$this->parameters = [
|
||||
'allowed_types' => implode(
|
||||
', ',
|
||||
array_map(TypeHelper::dump(...), $unionType->types())
|
||||
),
|
||||
];
|
||||
|
||||
if ($source === null) {
|
||||
$this->body = TypeHelper::containsObject($unionType)
|
||||
? 'Cannot be empty.'
|
||||
: 'Cannot be empty and must be filled with a value matching any of {allowed_types}.';
|
||||
} else {
|
||||
$this->body = TypeHelper::containsObject($unionType)
|
||||
? 'Invalid value {source_value}.'
|
||||
: 'Value {source_value} does not match any of {allowed_types}.';
|
||||
}
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1607027306);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Object\Argument;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class CircularDependencyDetected extends LogicException
|
||||
{
|
||||
public function __construct(Argument $argument)
|
||||
{
|
||||
parent::__construct(
|
||||
"Circular dependency detected for `{$argument->signature()}`.",
|
||||
1739903374,
|
||||
);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class InterfaceHasBothConstructorAndInfer extends LogicException
|
||||
{
|
||||
/**
|
||||
* @param interface-string $name
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
parent::__construct(
|
||||
"Interface `$name` is configured with at least one constructor but also has an infer configuration. Only one method can be used.",
|
||||
1711915749,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidAbstractObjectName extends RuntimeException
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
parent::__construct(
|
||||
"Invalid interface or class name `$name`.",
|
||||
1653990369
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
use function get_debug_type;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidIterableKeyType extends RuntimeException
|
||||
{
|
||||
public function __construct(mixed $key, string $path)
|
||||
{
|
||||
$type = get_debug_type($key);
|
||||
|
||||
parent::__construct(
|
||||
"Invalid key of type `$type` at path `$path`, only integers and strings are allowed.",
|
||||
1737104770,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\ValueDumper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidListKey extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body = 'Invalid sequential key {key}, expected {expected}.';
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(int|string $key, int $expected)
|
||||
{
|
||||
$this->parameters = [
|
||||
'key' => ValueDumper::dump($key),
|
||||
'expected' => (string)$expected,
|
||||
];
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1654273010);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidNodeHasNoMappedValue extends RuntimeException
|
||||
{
|
||||
public function __construct(string $path)
|
||||
{
|
||||
parent::__construct(
|
||||
"Cannot get mapped value for invalid node at path `$path`; use method `\$node->isValid()`.",
|
||||
1657466305
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidNodeValue extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(Type $type)
|
||||
{
|
||||
$this->parameters = [
|
||||
'expected_type' => TypeHelper::dump($type),
|
||||
];
|
||||
|
||||
$this->body = TypeHelper::containsObject($type)
|
||||
? 'Invalid value {source_value}.'
|
||||
: 'Value {source_value} does not match type {expected_type}.';
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1630678334);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\ValueDumper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidResolvedImplementationValue extends RuntimeException
|
||||
{
|
||||
public function __construct(string $name, mixed $value)
|
||||
{
|
||||
$value = ValueDumper::dump($value);
|
||||
|
||||
parent::__construct(
|
||||
"Invalid value $value, expected a subtype of `$name`.",
|
||||
1630091260
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\ArrayKeyType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\ValueDumper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class InvalidTraversableKey extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body = 'Key {key} does not match type {expected_type}.';
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(string|int $key, ArrayKeyType $type)
|
||||
{
|
||||
$this->parameters = [
|
||||
'key' => ValueDumper::dump($key),
|
||||
'expected_type' => TypeHelper::dump($type),
|
||||
];
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1630946163);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class MissingNodeValue extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body = 'Cannot be empty and must be filled with a value matching type {expected_type}.';
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(Type $type)
|
||||
{
|
||||
$this->parameters = [
|
||||
'expected_type' => TypeHelper::dump($type),
|
||||
];
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1655449641);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class MissingObjectImplementationRegistration extends RuntimeException
|
||||
{
|
||||
public function __construct(string $name, FunctionDefinition $functionDefinition)
|
||||
{
|
||||
parent::__construct(
|
||||
"No implementation of `$name` found with return type `{$functionDefinition->returnType->toString()}` of `$functionDefinition->signature`.",
|
||||
1653990549
|
||||
);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectImplementationCallbackError extends RuntimeException
|
||||
{
|
||||
public function __construct(string $name, private Exception $original)
|
||||
{
|
||||
parent::__construct(
|
||||
"Error thrown when trying to get implementation of `$name`: " . $original->getMessage(),
|
||||
1653983061,
|
||||
$original
|
||||
);
|
||||
}
|
||||
|
||||
public function original(): Exception
|
||||
{
|
||||
return $this->original;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\ClassType;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_map;
|
||||
use function implode;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectImplementationNotRegistered extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @param non-empty-array<string, ClassType> $allowed
|
||||
*/
|
||||
public function __construct(string $implementation, string $name, array $allowed)
|
||||
{
|
||||
$allowed = implode('`, `', array_map(fn (ClassType $type) => $type->toString(), $allowed));
|
||||
|
||||
parent::__construct(
|
||||
"Invalid implementation `$implementation` for `$name`, it should be one of `$allowed`.",
|
||||
1653990989
|
||||
);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class ResolvedImplementationIsNotAccepted extends RuntimeException
|
||||
{
|
||||
public function __construct(string $name, Type $incorrectType)
|
||||
{
|
||||
parent::__construct(
|
||||
"Invalid implementation type `{$incorrectType->toString()}`, expected a subtype of `$name`.",
|
||||
1618049487
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class SourceIsNotNull extends RuntimeException implements ErrorMessage
|
||||
{
|
||||
private string $body;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->body = 'Value {source_value} is not null.';
|
||||
|
||||
parent::__construct($this->body, 1710263908);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class SourceMustBeIterable extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(mixed $value, Type $type)
|
||||
{
|
||||
$this->parameters = [
|
||||
'expected_type' => TypeHelper::dump($type),
|
||||
];
|
||||
|
||||
if ($value === null) {
|
||||
$this->body = TypeHelper::containsObject($type)
|
||||
? 'Cannot be empty.'
|
||||
: 'Cannot be empty and must be filled with a value matching type {expected_type}.';
|
||||
} else {
|
||||
$this->body = TypeHelper::containsObject($type)
|
||||
? 'Invalid value {source_value}.'
|
||||
: 'Value {source_value} does not match type {expected_type}.';
|
||||
}
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1618739163);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class SourceValueWasNotFilled extends RuntimeException
|
||||
{
|
||||
public function __construct(string $path)
|
||||
{
|
||||
parent::__construct(
|
||||
"Source was not filled at path `$path`; use method `\$node->sourceFilled()`.",
|
||||
1657466107
|
||||
);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnionType;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\TypeHelper;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_map;
|
||||
use function implode;
|
||||
|
||||
/** @internal */
|
||||
final class TooManyResolvedTypesFromUnion extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(UnionType $unionType)
|
||||
{
|
||||
$this->parameters = [
|
||||
'allowed_types' => implode(
|
||||
', ',
|
||||
array_map(TypeHelper::dump(...), $unionType->types())
|
||||
),
|
||||
];
|
||||
|
||||
$this->body = TypeHelper::containsObject($unionType)
|
||||
? 'Invalid value {source_value}, it matches two or more types from union: cannot take a decision.'
|
||||
: 'Invalid value {source_value}, it matches two or more types from {allowed_types}: cannot take a decision.';
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1710262975);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Builder\TreeNode;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Utility\String\StringFormatter;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_filter;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
|
||||
/** @internal */
|
||||
final class UnexpectedKeysInSource extends RuntimeException implements ErrorMessage, HasParameters
|
||||
{
|
||||
private string $body = 'Unexpected key(s) {keys}, expected {expected_keys}.';
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $parameters;
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
* @param array<TreeNode> $children
|
||||
*/
|
||||
public function __construct(array $value, array $children)
|
||||
{
|
||||
$expected = array_map(fn (TreeNode $child) => $child->name(), $children);
|
||||
$superfluous = array_filter(
|
||||
array_keys($value),
|
||||
fn (string $key) => ! in_array($key, $expected, true)
|
||||
);
|
||||
|
||||
$this->parameters = [
|
||||
'keys' => '`' . implode('`, `', $superfluous) . '`',
|
||||
'expected_keys' => '`' . implode('`, `', $expected) . '`',
|
||||
];
|
||||
|
||||
parent::__construct(StringFormatter::for($this), 1655117782);
|
||||
}
|
||||
|
||||
public function body(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Exception;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use LogicException;
|
||||
|
||||
/** @internal */
|
||||
final class UnresolvableShellType extends LogicException
|
||||
{
|
||||
public function __construct(UnresolvableType $type)
|
||||
{
|
||||
parent::__construct($type->message());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message;
|
||||
|
||||
/** @internal */
|
||||
interface DefaultMessage
|
||||
{
|
||||
public const TRANSLATIONS = [
|
||||
'Value {source_value} does not match any of {allowed_values}.' => [
|
||||
'en' => 'Value {source_value} does not match any of {allowed_values}.',
|
||||
],
|
||||
'Value {source_value} does not match any of {allowed_types}.' => [
|
||||
'en' => 'Value {source_value} does not match any of {allowed_types}.',
|
||||
],
|
||||
'Cannot be empty and must be filled with a value matching any of {allowed_types}.' => [
|
||||
'en' => 'Cannot be empty and must be filled with a value matching any of {allowed_types}.',
|
||||
],
|
||||
'Value {source_value} does not match type {expected_type}.' => [
|
||||
'en' => 'Value {source_value} does not match type {expected_type}.',
|
||||
],
|
||||
'Value {source_value} does not match {expected_value}.' => [
|
||||
'en' => 'Value {source_value} does not match {expected_value}.',
|
||||
],
|
||||
'Value {source_value} does not match boolean value {expected_value}.' => [
|
||||
'en' => 'Value {source_value} does not match boolean value {expected_value}.',
|
||||
],
|
||||
'Value {source_value} does not match float value {expected_value}.' => [
|
||||
'en' => 'Value {source_value} does not match float value {expected_value}.',
|
||||
],
|
||||
'Value {source_value} does not match integer value {expected_value}.' => [
|
||||
'en' => 'Value {source_value} does not match integer value {expected_value}.',
|
||||
],
|
||||
'Value {source_value} does not match string value {expected_value}.' => [
|
||||
'en' => 'Value {source_value} does not match string value {expected_value}.',
|
||||
],
|
||||
'Value {source_value} is not null.' => [
|
||||
'en' => 'Value {source_value} is not null.',
|
||||
],
|
||||
'Value {source_value} is not a valid boolean.' => [
|
||||
'en' => 'Value {source_value} is not a valid boolean.',
|
||||
],
|
||||
'Value {source_value} is not a valid float.' => [
|
||||
'en' => 'Value {source_value} is not a valid float.',
|
||||
],
|
||||
'Value {source_value} is not a valid integer.' => [
|
||||
'en' => 'Value {source_value} is not a valid integer.',
|
||||
],
|
||||
'Value {source_value} is not a valid string.' => [
|
||||
'en' => 'Value {source_value} is not a valid string.',
|
||||
],
|
||||
'Value {source_value} is not a valid negative integer.' => [
|
||||
'en' => 'Value {source_value} is not a valid negative integer.',
|
||||
],
|
||||
'Value {source_value} is not a valid positive integer.' => [
|
||||
'en' => 'Value {source_value} is not a valid positive integer.',
|
||||
],
|
||||
'Value {source_value} is not a valid non-empty string.' => [
|
||||
'en' => 'Value {source_value} is not a valid non-empty string.',
|
||||
],
|
||||
'Value {source_value} is not a valid numeric string.' => [
|
||||
'en' => 'Value {source_value} is not a valid numeric string.',
|
||||
],
|
||||
'Value {source_value} is not a valid integer between {min} and {max}.' => [
|
||||
'en' => 'Value {source_value} is not a valid integer between {min} and {max}.',
|
||||
],
|
||||
'Value {source_value} is not a valid timezone.' => [
|
||||
'en' => 'Value {source_value} is not a valid timezone.',
|
||||
],
|
||||
'Value {source_value} is not a valid class string.' => [
|
||||
'en' => 'Value {source_value} is not a valid class string.',
|
||||
],
|
||||
'Value {source_value} is not a valid class string of `{expected_class_type}`.' => [
|
||||
'en' => 'Value {source_value} is not a valid class string of `{expected_class_type}`.',
|
||||
],
|
||||
'Invalid value {source_value}.' => [
|
||||
'en' => 'Invalid value {source_value}.',
|
||||
],
|
||||
'Invalid value {source_value}, it matches two or more types from union: cannot take a decision.' => [
|
||||
'en' => 'Invalid value {source_value}, it matches two or more types from union: cannot take a decision.',
|
||||
],
|
||||
'Invalid value {source_value}, it matches two or more types from {allowed_types}: cannot take a decision.' => [
|
||||
'en' => 'Invalid value {source_value}, it matches two or more types from {allowed_types}: cannot take a decision.',
|
||||
],
|
||||
'Invalid sequential key {key}, expected {expected}.' => [
|
||||
'en' => 'Invalid sequential key {key}, expected {expected}.',
|
||||
],
|
||||
'Cannot be empty.' => [
|
||||
'en' => 'Cannot be empty.',
|
||||
],
|
||||
'Cannot be empty and must be filled with a value matching type {expected_type}.' => [
|
||||
'en' => 'Cannot be empty and must be filled with a value matching type {expected_type}.',
|
||||
],
|
||||
'Key {key} does not match type {expected_type}.' => [
|
||||
'en' => 'Key {key} does not match type {expected_type}.',
|
||||
],
|
||||
'Value {source_value} does not match a valid date format.' => [
|
||||
'en' => 'Value {source_value} does not match a valid date format.',
|
||||
],
|
||||
'Value {source_value} does not match any of the following formats: {formats}.' => [
|
||||
'en' => 'Value {source_value} does not match any of the following formats: {formats}.',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/** @api */
|
||||
interface ErrorMessage extends Message, Throwable {}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
/** @api */
|
||||
final class AggregateMessageFormatter implements MessageFormatter
|
||||
{
|
||||
/** @var MessageFormatter[] */
|
||||
private array $formatters;
|
||||
|
||||
public function __construct(MessageFormatter ...$formatters)
|
||||
{
|
||||
$this->formatters = $formatters;
|
||||
}
|
||||
|
||||
public function format(NodeMessage $message): NodeMessage
|
||||
{
|
||||
foreach ($this->formatters as $formatter) {
|
||||
$message = $formatter->format($message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
/**
|
||||
* Can be used to easily customize a message with the given callback.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* // Customize the body of messages that have a certain code.
|
||||
* $formatter = new CallbackMessageFormatter(
|
||||
* fn (NodeMessage $message) => match ($message->code()) {
|
||||
* 'some_code_a',
|
||||
* 'some_code_b',
|
||||
* 'some_code_c' => $message->withBody('some new message body'),
|
||||
* default => $message
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* $message = $formatter->format($message);
|
||||
* ```
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
final class CallbackMessageFormatter implements MessageFormatter
|
||||
{
|
||||
/** @var callable(NodeMessage): NodeMessage */
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* @param callable(NodeMessage): NodeMessage $callback
|
||||
*/
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
public function format(NodeMessage $message): NodeMessage
|
||||
{
|
||||
return ($this->callback)($message);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
/** @api */
|
||||
final class LocaleMessageFormatter implements MessageFormatter
|
||||
{
|
||||
public function __construct(private string $locale) {}
|
||||
|
||||
public function format(NodeMessage $message): NodeMessage
|
||||
{
|
||||
return $message->withLocale($this->locale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
/** @api */
|
||||
interface MessageFormatter
|
||||
{
|
||||
public function format(NodeMessage $message): NodeMessage;
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Can be used to customize the content of messages added during a mapping.
|
||||
*
|
||||
* The constructor parameter is an array where each key represents either:
|
||||
* - The code of the message to be replaced
|
||||
* - The body of the message to be replaced
|
||||
* - The class name of the message to be replaced
|
||||
*
|
||||
* If none of those is found, the content of the message will stay unchanged
|
||||
* unless a default one is given to this class.
|
||||
*
|
||||
* If one of these keys is found, the array entry will be used to replace the
|
||||
* content of the message. This entry can be either a plain text or a callable
|
||||
* that takes the message as a parameter and returns a string; it is for
|
||||
* instance advised to use a callable in cases where a custom translation
|
||||
* service is used — to avoid useless greedy operations.
|
||||
*
|
||||
* See usage examples below:
|
||||
*
|
||||
* ```php
|
||||
* $formatter = (new MessageMapFormatter([
|
||||
* // Will match if the given message has this exact code
|
||||
* 'some_code' => 'New content / code: {message_code}',
|
||||
*
|
||||
* // Will match if the given message has this exact content
|
||||
* 'Some message content' => 'New content / previous: {original_message}',
|
||||
*
|
||||
* // Will match if the given message is an instance of `SomeError`
|
||||
* SomeError::class => 'New content / value: {source_value}',
|
||||
*
|
||||
* // A callback can be used to get access to the message instance
|
||||
* OtherError::class => function (NodeMessage $message): string {
|
||||
* if ($message->path() === 'foo.bar') {
|
||||
* return 'Some custom message';
|
||||
* }
|
||||
*
|
||||
* return $message->body();
|
||||
* },
|
||||
*
|
||||
* // For greedy operation, it is advised to use a lazy-callback
|
||||
* 'foo' => fn () => $this->translator->translate('foo.bar'),
|
||||
* ]))
|
||||
* ->defaultsTo('some default message')
|
||||
* // …or…
|
||||
* ->defaultsTo(fn () => $this->translator->translate('default_message'));
|
||||
*
|
||||
* $message = $formatter->format($message);
|
||||
* ```
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
final class MessageMapFormatter implements MessageFormatter
|
||||
{
|
||||
/** @var null|string|callable(NodeMessage): string */
|
||||
private $default;
|
||||
|
||||
public function __construct(
|
||||
/** @var array<string|callable(NodeMessage): string> */
|
||||
private array $map
|
||||
) {}
|
||||
|
||||
public function format(NodeMessage $message): NodeMessage
|
||||
{
|
||||
$target = $this->target($message);
|
||||
|
||||
if ($target) {
|
||||
return $message->withBody(is_string($target) ? $target : $target($message));
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|callable(NodeMessage): string $default
|
||||
*/
|
||||
public function defaultsTo(string|callable $default): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->default = $default;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|string|callable(NodeMessage): string
|
||||
*/
|
||||
private function target(NodeMessage $message): false|string|callable
|
||||
{
|
||||
return $this->map[$message->code()]
|
||||
?? $this->map[$message->body()]
|
||||
?? $this->map[$message->originalMessage()::class]
|
||||
?? $this->default
|
||||
?? false;
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\DefaultMessage;
|
||||
use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
/** @api */
|
||||
final class TranslationMessageFormatter implements MessageFormatter
|
||||
{
|
||||
/** @var array<string, array<string, string>> */
|
||||
private array $translations = [];
|
||||
|
||||
/**
|
||||
* Returns an instance of the class with the default translations provided
|
||||
* by the library.
|
||||
*/
|
||||
public static function default(): self
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->translations = DefaultMessage::TRANSLATIONS;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or overrides a single translation.
|
||||
*
|
||||
* ```php
|
||||
* (TranslationMessageFormatter::default())->withTranslation(
|
||||
* 'fr',
|
||||
* 'Invalid value {source_value}.',
|
||||
* 'Valeur invalide {source_value}.',
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
public function withTranslation(string $locale, string $original, string $translation): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->translations[$original][$locale] = $translation;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or overrides a list of translations.
|
||||
*
|
||||
* The given array consists of messages to be translated and for each one a
|
||||
* list of locales with their associated translations.
|
||||
*
|
||||
* ```php
|
||||
* $formatter = (TranslationMessageFormatter::default())->withTranslations([
|
||||
* 'Invalid value {source_value}.' => [
|
||||
* 'fr' => 'Valeur invalide {source_value}.',
|
||||
* 'es' => 'Valor inválido {source_value}.',
|
||||
* ],
|
||||
* 'Some custom message' => [
|
||||
* // …
|
||||
* ],
|
||||
* ]);
|
||||
*
|
||||
* $message = $formatter->format($message);
|
||||
* ```
|
||||
*
|
||||
* @param array<string, array<string, string>> $translations
|
||||
*/
|
||||
public function withTranslations(array $translations): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
// @phpstan-ignore assign.propertyType (PHPStan does not properly infer the return type of the function)
|
||||
$clone->translations = array_replace_recursive($this->translations, $translations);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function format(NodeMessage $message): NodeMessage
|
||||
{
|
||||
$body = $this->translations[$message->body()][$message->locale()] ?? null;
|
||||
|
||||
if ($body) {
|
||||
return $message->withBody($body);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user