Skip to content

Commit f2d5af4

Browse files
committed
Casting Lists/Maps/Structures
1 parent 40b210c commit f2d5af4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+467
-94
lines changed

src/adapter/etl-adapter-json/tests/Flow/ETL/Adapter/JSON/Tests/Integration/JSONMachine/JsonExtractorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public function test_extracting_json_from_local_file_stream_with_schema() : void
9898
<<<'SCHEMA'
9999
schema
100100
|-- timezones: list<string>
101-
|-- latlng: array<mixed>
101+
|-- latlng: list<float>
102102
|-- name: string
103103
|-- country_code: string
104104
|-- capital: ?string

src/core/etl/src/Flow/ETL/DSL/functions.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ function struct_entry(string $name, array $value, StructureType $type) : Row\Ent
358358
return new Row\Entry\StructureEntry($name, $value, $type);
359359
}
360360

361+
function structure_entry(string $name, array $value, StructureType $type) : Row\Entry\StructureEntry
362+
{
363+
return new Row\Entry\StructureEntry($name, $value, $type);
364+
}
365+
361366
/**
362367
* @param array<string, StructureElement> $elements
363368
*/
@@ -366,11 +371,21 @@ function struct_type(array $elements, bool $nullable = false) : StructureType
366371
return new StructureType($elements, $nullable);
367372
}
368373

374+
function structure_type(array $elements, bool $nullable = false) : StructureType
375+
{
376+
return new StructureType($elements, $nullable);
377+
}
378+
369379
function struct_element(string $name, Type $type) : StructureElement
370380
{
371381
return new StructureElement($name, $type);
372382
}
373383

384+
function structure_element(string $name, Type $type) : StructureElement
385+
{
386+
return new StructureElement($name, $type);
387+
}
388+
374389
function list_entry(string $name, array $value, ListType $type) : Row\Entry\ListEntry
375390
{
376391
return new Row\Entry\ListEntry($name, $value, $type);

src/core/etl/src/Flow/ETL/Exception/CastingException.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99
final class CastingException extends RuntimeException
1010
{
11-
public function __construct(public readonly mixed $value, public readonly Type $type)
11+
public function __construct(public readonly mixed $value, public readonly Type $type, ?\Throwable $previous = null)
1212
{
13-
parent::__construct(\sprintf("Can't cast \"%s\" into \"%s\" type", \gettype($value), $type->toString()));
13+
parent::__construct(
14+
\sprintf("Can't cast \"%s\" into \"%s\" type", \gettype($value), $type->toString()),
15+
0,
16+
$previous
17+
);
1418
}
1519
}

src/core/etl/src/Flow/ETL/PHP/Type/ArrayContentDetector.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ final class ArrayContentDetector
1515

1616
private readonly ?Type $firstValueType;
1717

18-
private readonly int $uniqueKeysCount;
18+
private readonly int $uniqueKeysTypeCount;
1919

20-
private readonly int $uniqueValuesCount;
20+
private readonly int $uniqueValuesTypeCount;
2121

2222
public function __construct(Types $uniqueKeysType, Types $uniqueValuesType)
2323
{
2424
$this->firstKeyType = $uniqueKeysType->first();
2525
$this->firstValueType = $uniqueValuesType->first();
26-
$this->uniqueKeysCount = $uniqueKeysType->count();
27-
$this->uniqueValuesCount = $uniqueValuesType->without(type_array(true), type_null())->count();
26+
$this->uniqueKeysTypeCount = $uniqueKeysType->count();
27+
$this->uniqueValuesTypeCount = $uniqueValuesType->without(type_array(true), type_null())->count();
2828
}
2929

3030
public function firstKeyType() : ?ScalarType
@@ -43,12 +43,12 @@ public function firstValueType() : ?Type
4343

4444
public function isList() : bool
4545
{
46-
return 1 === $this->uniqueValuesCount && $this->firstKeyType()?->isInteger();
46+
return 1 === $this->uniqueValuesTypeCount && $this->firstKeyType()?->isInteger();
4747
}
4848

4949
public function isMap() : bool
5050
{
51-
if (1 === $this->uniqueValuesCount && 1 === $this->uniqueKeysCount) {
51+
if (1 === $this->uniqueValuesTypeCount && 1 === $this->uniqueKeysTypeCount) {
5252
return !$this->firstKeyType()?->isInteger();
5353
}
5454

@@ -61,8 +61,8 @@ public function isStructure() : bool
6161
return false;
6262
}
6363

64-
return 0 !== $this->uniqueValuesCount
65-
&& 1 === $this->uniqueKeysCount
64+
return 0 !== $this->uniqueValuesTypeCount
65+
&& 1 === $this->uniqueKeysTypeCount
6666
&& $this->firstKeyType()?->isString();
6767
}
6868
}

src/core/etl/src/Flow/ETL/PHP/Type/AutoCaster.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Flow\ETL\PHP\Type;
66

7+
use function Flow\ETL\DSL\get_type;
78
use function Flow\ETL\DSL\type_boolean;
89
use function Flow\ETL\DSL\type_datetime;
910
use function Flow\ETL\DSL\type_float;
1011
use function Flow\ETL\DSL\type_integer;
1112
use function Flow\ETL\DSL\type_json;
1213
use function Flow\ETL\DSL\type_uuid;
13-
use Flow\ETL\PHP\Type\Caster\StringTypeChecker;
14+
use Flow\ETL\PHP\Type\Caster\StringCastingHandler\StringTypeChecker;
1415

1516
final class AutoCaster
1617
{
@@ -20,10 +21,44 @@ public function __construct(private readonly Caster $caster)
2021

2122
public function cast(mixed $value) : mixed
2223
{
23-
if (!\is_string($value)) {
24-
return $value;
24+
if (\is_string($value)) {
25+
return $this->castToString($value);
2526
}
2627

28+
if (\is_array($value)) {
29+
return $this->castArray($value);
30+
}
31+
32+
return $value;
33+
}
34+
35+
private function castArray(array $value) : array
36+
{
37+
$keyTypes = [];
38+
$valueTypes = [];
39+
40+
foreach ($value as $key => $item) {
41+
$keyType = get_type($key);
42+
$valueType = get_type($item);
43+
$keyTypes[$keyType->toString()] = $keyType;
44+
$valueTypes[$valueType->toString()] = $valueType;
45+
}
46+
47+
if (isset($valueTypes['integer'], $valueTypes['float']) && \count($valueTypes) === 2) {
48+
$castedArray = [];
49+
50+
foreach ($value as $key => $item) {
51+
$castedArray[$key] = $this->caster->to(type_float())->value($item);
52+
}
53+
54+
return $castedArray;
55+
}
56+
57+
return $value;
58+
}
59+
60+
private function castToString(string $value) : mixed
61+
{
2762
$typeChecker = new StringTypeChecker($value);
2863

2964
if ($typeChecker->isNull()) {

src/core/etl/src/Flow/ETL/PHP/Type/Caster.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public static function default() : self
5656
type_json()->toString() => new JsonCastingHandler(),
5757
type_array()->toString() => new ArrayCastingHandler(),
5858
'list' => new ListCastingHandler(),
59-
'map', new MapCastingHandler(),
60-
'structure', new StructureCastingHandler(),
59+
'map' => new MapCastingHandler(),
60+
'structure' => new StructureCastingHandler(),
6161
type_null()->toString() => new NullCastingHandler(),
6262
'enum' => new EnumCastingHandler(),
6363
]);
@@ -66,12 +66,12 @@ public static function default() : self
6666
public function to(Type $type) : CastingContext
6767
{
6868
if (\array_key_exists($type->toString(), $this->handlers)) {
69-
return new CastingContext($this->handlers[$type->toString()], $type);
69+
return new CastingContext($this->handlers[$type->toString()], $type, $this);
7070
}
7171

7272
foreach ($this->handlers as $handler) {
7373
if ($handler->supports($type)) {
74-
return new CastingContext($handler, $type);
74+
return new CastingContext($handler, $type, $this);
7575
}
7676
}
7777

src/core/etl/src/Flow/ETL/PHP/Type/Caster/ArrayCastingHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Flow\ETL\PHP\Type\Caster;
66

77
use Flow\ETL\Exception\CastingException;
8+
use Flow\ETL\PHP\Type\Caster;
89
use Flow\ETL\PHP\Type\Caster\XML\XMLConverter;
910
use Flow\ETL\PHP\Type\Native\ArrayType;
1011
use Flow\ETL\PHP\Type\Type;
@@ -16,7 +17,7 @@ public function supports(Type $type) : bool
1617
return $type instanceof ArrayType;
1718
}
1819

19-
public function value(mixed $value, Type $type) : mixed
20+
public function value(mixed $value, Type $type, Caster $caster) : mixed
2021
{
2122
try {
2223
if (\is_string($value) && (\str_starts_with($value, '{') || \str_starts_with($value, '['))) {

src/core/etl/src/Flow/ETL/PHP/Type/Caster/BooleanCastingHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Flow\ETL\PHP\Type\Caster;
66

77
use Flow\ETL\Exception\CastingException;
8+
use Flow\ETL\PHP\Type\Caster;
89
use Flow\ETL\PHP\Type\Native\ScalarType;
910
use Flow\ETL\PHP\Type\Type;
1011

@@ -15,7 +16,7 @@ public function supports(Type $type) : bool
1516
return $type instanceof ScalarType && $type->isBoolean();
1617
}
1718

18-
public function value(mixed $value, Type $type) : mixed
19+
public function value(mixed $value, Type $type, Caster $caster) : mixed
1920
{
2021
if (\is_string($value)) {
2122
if (\in_array(\mb_strtolower($value), ['true', '1', 'yes', 'on'], true)) {

src/core/etl/src/Flow/ETL/PHP/Type/Caster/CastingContext.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,29 @@
44

55
namespace Flow\ETL\PHP\Type\Caster;
66

7+
use Flow\ETL\Exception\CastingException;
8+
use Flow\ETL\PHP\Type\Caster;
79
use Flow\ETL\PHP\Type\Type;
810

911
final class CastingContext
1012
{
11-
public function __construct(private readonly CastingHandler $handler, private readonly Type $type)
12-
{
13+
public function __construct(
14+
private readonly CastingHandler $handler,
15+
private readonly Type $type,
16+
private readonly Caster $caster
17+
) {
1318
}
1419

1520
public function value(mixed $value) : mixed
1621
{
17-
return $this->handler->value($value, $this->type);
22+
if ($value === null && $this->type->nullable()) {
23+
return null;
24+
}
25+
26+
if ($value === null && !$this->type->nullable()) {
27+
throw new CastingException($value, $this->type);
28+
}
29+
30+
return $this->handler->value($value, $this->type, $this->caster);
1831
}
1932
}

src/core/etl/src/Flow/ETL/PHP/Type/Caster/CastingHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
namespace Flow\ETL\PHP\Type\Caster;
66

7+
use Flow\ETL\PHP\Type\Caster;
78
use Flow\ETL\PHP\Type\Type;
89

910
interface CastingHandler
1011
{
1112
public function supports(Type $type) : bool;
1213

13-
public function value(mixed $value, Type $type) : mixed;
14+
public function value(mixed $value, Type $type, Caster $caster) : mixed;
1415
}

0 commit comments

Comments
 (0)