Skip to content

Commit

Permalink
feat: Mapping carbon types and allowing partial failure of deserializ…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
autaut03 committed Oct 17, 2023
1 parent 7da8da8 commit c8c231c
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 37 deletions.
2 changes: 2 additions & 0 deletions src/GoodPhp/Serialization/SerializerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use GoodPhp\Serialization\TypeAdapter\Primitive\BuiltIn\DateTimeMapper;
use GoodPhp\Serialization\TypeAdapter\Primitive\BuiltIn\Nullable\NullableTypeAdapterFactory;
use GoodPhp\Serialization\TypeAdapter\Primitive\BuiltIn\ScalarMapper;
use GoodPhp\Serialization\TypeAdapter\Primitive\Carbon\CarbonMapper;
use GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\ClassPropertiesPrimitiveTypeAdapterFactory;
use GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\Naming\BuiltInNamingStrategy;
use GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\Naming\NamingStrategy;
Expand Down Expand Up @@ -148,6 +149,7 @@ public function build(): Serializer
->addMapperLast(new ArrayMapper())
->addMapperLast(new CollectionMapper())
->addMapperLast(new DateTimeMapper())
->addMapperLast(new CarbonMapper())
->addFactoryLast(new ClassPropertiesPrimitiveTypeAdapterFactory(
new SerializedNameAttributeNamingStrategy($this->namingStrategy ?? BuiltInNamingStrategy::PRESERVING),
$this->hydrator ?? new ConstructorHydrator(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use RuntimeException;
use Throwable;

class UnexpectedEnumValueException extends RuntimeException
class UnexpectedEnumValueException extends RuntimeException implements UnexpectedValueException
{
public function __construct(
public readonly string|int $value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use RuntimeException;
use Throwable;

class UnexpectedValueTypeException extends RuntimeException
class UnexpectedTypeException extends RuntimeException
{
public function __construct(
public readonly mixed $value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace GoodPhp\Serialization\TypeAdapter\Exception;

interface UnexpectedValueException extends \Throwable
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace GoodPhp\Serialization\TypeAdapter\Primitive\Carbon;

use Carbon\Carbon;
use Carbon\CarbonImmutable;
use GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\MapFrom;
use GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\MapTo;
use GoodPhp\Serialization\TypeAdapter\Primitive\PrimitiveTypeAdapter;

class CarbonMapper
{
#[MapTo(PrimitiveTypeAdapter::class)]
public function toCarbon(Carbon $value): string
{
return $value->toISOString();
}

#[MapFrom(PrimitiveTypeAdapter::class)]
public function fromCarbon(string $value): Carbon
{
return new Carbon($value);
}

#[MapTo(PrimitiveTypeAdapter::class)]
public function toCarbonImmutable(CarbonImmutable $value): string
{
return $value->toISOString();
}

#[MapFrom(PrimitiveTypeAdapter::class)]
public function fromCarbonImmutable(string $value): CarbonImmutable
{
return new CarbonImmutable($value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

use GoodPhp\Reflection\Reflection\PropertyReflection;
use GoodPhp\Serialization\MissingValue;
use GoodPhp\Serialization\TypeAdapter\Exception\UnexpectedEnumValueException;
use GoodPhp\Serialization\TypeAdapter\Exception\UnexpectedValueException;
use GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\MissingValueException;
use GoodPhp\Serialization\TypeAdapter\TypeAdapter;
use Illuminate\Support\Arr;
use Webmozart\Assert\Assert;

/**
* This handles the built-in default mechanism of binding properties. Specifically, it handles:
Expand All @@ -29,8 +32,12 @@ public function __construct(
private readonly bool $optional,
private readonly bool $hasDefaultValue,
private readonly bool $nullable,
private readonly bool $useDefaultForUnexpected,
)
{
if ($this->useDefaultForUnexpected) {
Assert::true($this->hasDefaultValue, "When using #[UseDefaultForUnexpected], the property must have a default value.");
}
}

public function serializedName(): string
Expand Down Expand Up @@ -76,8 +83,16 @@ public function deserialize(array $data): array
throw new MissingValueException();
}

return [
$this->property->name() => $this->typeAdapter->deserialize($data[$this->serializedName])
];
try {
return [
$this->property->name() => $this->typeAdapter->deserialize($data[$this->serializedName])
];
} catch (UnexpectedValueException $e) {
if ($this->useDefaultForUnexpected) {
return [];
}

throw $e;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function create(
optional: $optional,
hasDefaultValue: $this->hasDefaultValue($property),
nullable: $type instanceof NullableType,
useDefaultForUnexpected: $property->attributes()->has(UseDefaultForUnexpected::class),
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\Property;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class UseDefaultForUnexpected
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use GoodPhp\Reflection\Type\NamedType;
use GoodPhp\Reflection\Type\Type;
use GoodPhp\Serialization\Serializer;
use GoodPhp\Serialization\TypeAdapter\Exception\UnexpectedValueTypeException;
use GoodPhp\Serialization\TypeAdapter\Exception\UnexpectedTypeException;
use GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\Acceptance\AcceptanceStrategy;
use GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\TypeAdapter\MapperMethodsPrimitiveTypeAdapterFactory;
use TypeError;
Expand All @@ -29,12 +29,13 @@ public function accepts(NamedType $type, Attributes $attributes, Serializer $ser
return $this->acceptanceStrategy->accepts($this->methodValueType, $type, $serializer);
}

public function invoke(mixed $value, Type $type, Serializer $serializer, MapperMethodsPrimitiveTypeAdapterFactory $skipPast): mixed
public function invoke(mixed $value, Type $type, Attributes $attributes, Serializer $serializer, MapperMethodsPrimitiveTypeAdapterFactory $skipPast): mixed
{
$map = [
MapperMethodsPrimitiveTypeAdapterFactory::class => $skipPast,
Serializer::class => $serializer,
Type::class => $type,
Attributes::class => $attributes,
];

try {
Expand All @@ -58,7 +59,7 @@ public function invoke(mixed $value, Type $type, Serializer $serializer, MapperM
throw $e;
}

throw new UnexpectedValueTypeException($value, $this->method->parameters()->first()->type());
throw new UnexpectedTypeException($value, $this->method->parameters()->first()->type());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ interface MapperMethod
{
public function accepts(NamedType $type, Attributes $attributes, Serializer $serializer): bool;

public function invoke(mixed $value, Type $type, Serializer $serializer, MapperMethodsPrimitiveTypeAdapterFactory $skipPast): mixed;
public function invoke(mixed $value, Type $type, Attributes $attributes, Serializer $serializer, MapperMethodsPrimitiveTypeAdapterFactory $skipPast): mixed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\TypeAdapter;

use GoodPhp\Reflection\Reflection\Attributes\Attributes;
use GoodPhp\Reflection\Type\Type;
use GoodPhp\Serialization\Serializer;
use GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\MapperMethod\MapperMethod;
Expand All @@ -15,6 +16,7 @@ public function __construct(
private readonly ?MapperMethod $fromMapper,
private readonly ?PrimitiveTypeAdapter $fallbackDelegate,
private readonly Type $type,
private readonly Attributes $attributes,
private readonly Serializer $serializer,
private readonly MapperMethodsPrimitiveTypeAdapterFactory $skipPast,
) {
Expand All @@ -29,7 +31,7 @@ public function __construct(
public function serialize(mixed $value): mixed
{
return $this->toMapper ?
$this->toMapper->invoke($value, $this->type, $this->serializer, $this->skipPast) :
$this->toMapper->invoke($value, $this->type, $this->attributes, $this->serializer, $this->skipPast) :
$this->fallbackDelegate->serialize($value);
}

Expand All @@ -39,7 +41,7 @@ public function serialize(mixed $value): mixed
public function deserialize(mixed $value): mixed
{
return $this->fromMapper ?
$this->fromMapper->invoke($value, $this->type, $this->serializer, $this->skipPast) :
$this->fromMapper->invoke($value, $this->type, $this->attributes, $this->serializer, $this->skipPast) :
$this->fallbackDelegate->deserialize($value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function create(string $typeAdapterType, Type $type, Attributes $attribut
fromMapper: $fromMapper,
fallbackDelegate: $fallbackDelegate,
type: $type,
attributes: $attributes,
serializer: $serializer,
skipPast: $this,
);
Expand Down
Loading

0 comments on commit c8c231c

Please sign in to comment.