Skip to content

Commit

Permalink
Add discriminator map default type support
Browse files Browse the repository at this point in the history
  • Loading branch information
SerafimArts committed Nov 20, 2024
1 parent e06cc5c commit 03d159b
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 8 deletions.
20 changes: 20 additions & 0 deletions src/Mapping/DiscriminatorMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace TypeLang\Mapper\Mapping;

use JetBrains\PhpStorm\Language;

/**
* ```
* #[DiscriminatorMap(field: 'type', map: [
Expand All @@ -24,12 +26,30 @@ class DiscriminatorMap
{
public function __construct(
/**
* The property holding the type discriminator
*
* @var non-empty-string
*/
public readonly string $field,
/**
* The mapping between field value and types, i.e.
*
* ```
* [
* 'admin_user' => AdminUser::class,
* 'admin_users' => 'list<AdminUser>',
* ]
*
* @var non-empty-array<non-empty-string, non-empty-string>
*/
public readonly array $map,
/**
* Default type if the discriminator field ({@see $field}) is missing
* or does not match the mapping rules ({@see $map})
*
* @var non-empty-string|null
*/
#[Language('PHP')]
public readonly ?string $otherwise = null,
) {}
}
11 changes: 11 additions & 0 deletions src/Mapping/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ protected function load(
$attribute = $this->findClassAttribute($reflection, DiscriminatorMap::class);
if ($attribute !== null) {
$mapping = [];
$default = null;

foreach ($attribute->map as $mappedValue => $mappedType) {
$mapping[$mappedValue] = $this->createDiscriminatorType(
Expand All @@ -155,9 +156,19 @@ class: $reflection,
);
}

if ($attribute->otherwise !== null) {
$default = $this->createDiscriminatorType(
type: $attribute->otherwise,
class: $reflection,
types: $types,
parser: $parser,
);
}

$class->setDiscriminator(new DiscriminatorMapMetadata(
field: $attribute->field,
map: $mapping,
default: $default,
));
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/Mapping/Metadata/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ public function __construct(
*
* Required to print type information in exceptions.
*
* @api
*
* @codeCoverageIgnore
*/
public function getTypeStatement(Context $context): TypeStatement
Expand Down
24 changes: 22 additions & 2 deletions src/Mapping/Metadata/DiscriminatorMapMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(
* @var non-empty-array<non-empty-string, TypeMetadata>
*/
private readonly array $map,
private ?TypeMetadata $default = null,
?int $createdAt = null,
) {
parent::__construct($createdAt);
Expand Down Expand Up @@ -73,11 +74,30 @@ public function getField(): string
}

/**
* Dynamically creates AST discriminator representation.
* Returns default mapping type for transformations that do not comply
* with the specified mapping rules defined in {@see getMapping()}.
*
* Required to print type information in exceptions.
* @api
*/
public function getDefaultType(): ?TypeMetadata
{
return $this->default;
}

/**
* Updates default mapping type.
*
* @api
*/
public function setDefaultType(?TypeMetadata $type): void
{
$this->default = $type;
}

/**
* Dynamically creates AST discriminator representation.
*
* Required to print type information in exceptions.
*
* @codeCoverageIgnore
*/
Expand Down
7 changes: 3 additions & 4 deletions src/Mapping/NormalizeAsArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ final class NormalizeAsArray
{
public function __construct(
/**
* Enables normalization of an object value as an
* associative {@see array} if {@see $enabled} is set
* to {@see true} or use anonymous {@see object} in case of
* parameter is set to {@see false}.
* Enables normalization of an object value as an associative
* {@see array} if {@see $enabled} is set to {@see true} or use
* anonymous {@see object} in case of parameter is set to {@see false}.
*/
public readonly bool $enabled = true,
) {}
Expand Down
19 changes: 19 additions & 0 deletions src/Type/ClassType/ClassTypeDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,19 @@ public function cast(mixed $value, Context $context): mixed
*/
private function castOverDiscriminator(DiscriminatorMapMetadata $map, array $value, Context $context): mixed
{
// Default mapping type
$default = $map->getDefaultType()
?->getType();

$field = $map->getField();

// In case of discriminator field is missing
if (!\array_key_exists($field, $value)) {
// In case of default type is present
if ($default !== null) {
return $default->cast($value, $context);
}

throw MissingRequiredObjectFieldException::createFromContext(
field: $field,
expected: $map->getTypeStatement(),
Expand All @@ -98,6 +107,11 @@ private function castOverDiscriminator(DiscriminatorMapMetadata $map, array $val

// In case of discriminator field is not a string
if (!\is_string($element)) {
// In case of default type is present
if ($default !== null) {
return $default->cast($value, $context);
}

throw InvalidObjectValueException::createFromContext(
element: $element,
field: $field,
Expand All @@ -111,6 +125,11 @@ private function castOverDiscriminator(DiscriminatorMapMetadata $map, array $val

// In case of discriminator value is not found
if ($mapping === null) {
// In case of default type is present
if ($default !== null) {
return $default->cast($value, $context);
}

throw InvalidObjectValueException::createFromContext(
element: $element,
field: $field,
Expand Down

0 comments on commit 03d159b

Please sign in to comment.