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

Теперь 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:
2026-07-06 22:20:16 +00:00
parent 9dad445d11
commit 49dd055a58
439 changed files with 24952 additions and 0 deletions
+16
View File
@@ -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;
}
+42
View File
@@ -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
View File
@@ -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
View File
@@ -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();
}
}
+86
View File
@@ -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;
}
}
+45
View File
@@ -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;
}
}
@@ -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
);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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)];
}
}
@@ -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;
}
}
@@ -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();
}
}
+21
View File
@@ -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
);
}
}
+58
View File
@@ -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;
}
+55
View File
@@ -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;
}
}
+80
View File
@@ -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;
}
}
+62
View File
@@ -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;
}
}
+184
View File
@@ -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,
);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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
);
}
}
@@ -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;
}
}
@@ -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
);
}
}
@@ -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
);
}
}
@@ -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 {}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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;
}
}
@@ -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