Skip to content

Commit c3e6e36

Browse files
authored
Merge pull request #2586 from flow-php/types-structure-compatibility
Types structure compatibility
2 parents b5819f3 + faaf404 commit c3e6e36

89 files changed

Lines changed: 1971 additions & 383 deletions

File tree

Some content is hidden

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

documentation/upgrading.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,21 @@ flow_telemetry:
152152
- 'cache.http_client.pool'
153153
```
154154
155+
### 9) `flow-php/types` - a `Type` implementation's generic parameter is the value it represents
156+
157+
| Before | After |
158+
|----------------------------------|------------------------------------------|
159+
| `ListType<string>` | `ListType<list<string>>` |
160+
| `MapType<string, int>` | `MapType<array<string, int>>` |
161+
| `StructureType<mixed>` | `StructureType<array<array-key, mixed>>` |
162+
| `ClassStringType<Foo>` | `ClassStringType<class-string<Foo>>` |
163+
| `ListType::element(): Type<T>` | `Type<value-of<T>>` |
164+
| `MapType::key(): Type<TKey>` | `Type<key-of<T>>` |
165+
| `MapType::value(): Type<TValue>` | `Type<value-of<T>>` |
166+
| `type_string(): Type` | `type_string(): StringType` |
167+
168+
Update `ListType`, `MapType`, `StructureType` and `ClassStringType` parameters in your own docblocks.
169+
155170
---
156171

157172
## Upgrading from 0.41.x to 0.42.x

src/adapter/etl-adapter-json/src/Flow/ETL/Adapter/JSON/SchemaConverter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ private function typeToJsonSchema(Type $type): array
10201020
}
10211021

10221022
/**
1023-
* @param MapType<array-key, mixed> $type
1023+
* @param MapType<array<array-key, mixed>> $type
10241024
*
10251025
* @return array<string, mixed>
10261026
*/
@@ -1041,7 +1041,7 @@ private function mapToJsonSchema(MapType $type): array
10411041
}
10421042

10431043
/**
1044-
* @param StructureType<mixed> $type
1044+
* @param StructureType<array<array-key, mixed>> $type
10451045
*
10461046
* @return array<string, mixed>
10471047
*/

src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/SchemaConverter.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ public function toParquet(Schema $schema): ParquetSchema
9696
*/
9797
private function flowToParquet(string $name, Type $type, bool $nullable): Column
9898
{
99+
if ($type instanceof StructureType && count($type->optionalElements())) {
100+
throw new RuntimeException(sprintf(
101+
'Parquet schema does not support structure optional elements, given: %s',
102+
$type->toString(),
103+
));
104+
}
105+
99106
$repetition = $nullable ? ParquetSchema\Repetition::OPTIONAL : ParquetSchema\Repetition::REQUIRED;
100107

101108
return match ($type::class) {

src/adapter/etl-adapter-parquet/tests/Flow/ETL/Adapter/Parquet/Tests/Unit/FlowToParquetSchemaTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Flow\ETL\Adapter\Parquet\Tests\Unit;
66

77
use Flow\ETL\Adapter\Parquet\SchemaConverter;
8+
use Flow\ETL\Exception\RuntimeException;
89
use Flow\ETL\Tests\FlowTestCase;
910
use Flow\Parquet\ParquetFile\Schema as ParquetSchema;
1011
use Flow\Parquet\ParquetFile\Schema\FlatColumn;
@@ -82,4 +83,28 @@ public function test_convert_etl_entries_to_parquet_fields(): void
8283
)),
8384
);
8485
}
86+
87+
public function test_converting_structure_with_optional_elements_throws(): void
88+
{
89+
$this->expectException(RuntimeException::class);
90+
$this->expectExceptionMessage(
91+
'Parquet schema does not support structure optional elements, given: structure{a: string, b?: string}',
92+
);
93+
94+
(new SchemaConverter())->toParquet(schema(structure_schema('structure', type_structure(['a' => type_string()], [
95+
'b' => type_string(),
96+
]))));
97+
}
98+
99+
public function test_converting_nested_structure_with_optional_elements_throws(): void
100+
{
101+
$this->expectException(RuntimeException::class);
102+
$this->expectExceptionMessage(
103+
'Parquet schema does not support structure optional elements, given: structure{b: string, c?: string}',
104+
);
105+
106+
(new SchemaConverter())->toParquet(schema(structure_schema('structure', type_structure([
107+
'a' => type_structure(['b' => type_string()], ['c' => type_string()]),
108+
]))));
109+
}
85110
}

src/adapter/etl-adapter-seal/src/Flow/ETL/Adapter/Seal/SchemaConverter.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ private function flag(Metadata $metadata, SealMetadata $key, bool $default): boo
102102
*/
103103
private function flowToSealField(string $name, Type $type, bool $multiple, Metadata $metadata): AbstractField
104104
{
105+
if ($type instanceof StructureType && count($type->optionalElements())) {
106+
throw new RuntimeException(sprintf(
107+
'Seal schema does not support structure optional elements, given: %s',
108+
$type->toString(),
109+
));
110+
}
111+
105112
return match ($type::class) {
106113
EnumType::class,
107114
HTMLElementType::class,

src/adapter/etl-adapter-seal/tests/Flow/ETL/Adapter/Seal/Tests/Unit/SchemaConverterTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,22 @@ public function test_throws_when_no_identifier_is_provided(): void
101101
to_seal_schema(schema(str_schema('name')), 'index');
102102
}
103103

104+
public function test_throws_when_structure_has_optional_elements(): void
105+
{
106+
$this->expectException(RuntimeException::class);
107+
$this->expectExceptionMessage(
108+
'Seal schema does not support structure optional elements, given: structure{name: string, nickname?: string}',
109+
);
110+
111+
to_seal_schema(
112+
schema(
113+
str_schema('id'),
114+
structure_schema('author', type_structure(['name' => type_string()], ['nickname' => type_string()])),
115+
),
116+
'index',
117+
);
118+
}
119+
104120
public function test_using_metadata_to_override_default_field_flags(): void
105121
{
106122
$sealSchema = to_seal_schema(

src/adapter/etl-adapter-xml/src/Flow/ETL/Adapter/XML/XMLEncoder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ public function encode(array $batch): array
105105
*/
106106
private function normalize(string $name, Type $type, mixed $value): XMLNode|XMLAttribute
107107
{
108+
if ($type instanceof StructureType && count($type->optionalElements())) {
109+
throw new RuntimeException(sprintf(
110+
'XML encoder does not support structure optional elements, given: %s',
111+
$type->toString(),
112+
));
113+
}
114+
108115
if (str_starts_with($name, $this->attributePrefix)) {
109116
return new XMLAttribute(substr($name, strlen($this->attributePrefix)), type_string()->cast($value));
110117
}

src/adapter/etl-adapter-xml/tests/Flow/ETL/Adapter/XML/Tests/Unit/XMLEncoderTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use DateTimeImmutable;
99
use Flow\ETL\Adapter\XML\XMLEncoder;
1010
use Flow\ETL\Adapter\XML\XMLWriter\DOMDocumentWriter;
11+
use Flow\ETL\Exception\RuntimeException;
1112
use Flow\ETL\Row\TypedRowValues;
1213
use Flow\ETL\Tests\Fixtures\Enum\BackedIntEnum;
1314
use Flow\ETL\Tests\FlowTestCase;
@@ -156,6 +157,20 @@ public function test_encodes_structure_into_nested_nodes(): void
156157
);
157158
}
158159

160+
public function test_encoding_structure_with_optional_elements_throws(): void
161+
{
162+
$encoder = new XMLEncoder(new DOMDocumentWriter());
163+
164+
$this->expectException(RuntimeException::class);
165+
$this->expectExceptionMessage(
166+
'XML encoder does not support structure optional elements, given: structure{city: string, zip?: string}',
167+
);
168+
169+
$encoder->encode([new TypedRowValues(['address' => ['city' => 'Krakow']], ['address' => type_structure([
170+
'city' => type_string(),
171+
], ['zip' => type_string()])])]);
172+
}
173+
159174
public function test_encodes_null_scalar_as_an_empty_node(): void
160175
{
161176
$encoder = new XMLEncoder(new DOMDocumentWriter());

src/bridge/monolog/http/tests/Flow/Bridge/Monolog/Http/Tests/Unit/Config/RequestConfigTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function test_constructor_throws_exception_when_mask_sanitizer_has_invali
4848
{
4949
$this->expectException(InvalidTypeException::class);
5050
$this->expectExceptionMessage(
51-
'Expected type "structure{type: \'mask\', character?: string, offset?: integer}", got "map<string, string>"',
51+
'Expected type "structure{type: \'mask\', character?: string, offset?: integer}", got "structure{type: string, character: string, offset: string}"',
5252
);
5353

5454
new RequestConfig(sanitizers: [

src/bridge/monolog/http/tests/Flow/Bridge/Monolog/Http/Tests/Unit/Config/ResponseConfigTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function test_constructor_throws_exception_when_mask_sanitizer_has_invali
4848
{
4949
$this->expectException(InvalidTypeException::class);
5050
$this->expectExceptionMessage(
51-
'Expected type "structure{type: \'mask\', character?: string, offset?: integer}", got "map<string, string>"',
51+
'Expected type "structure{type: \'mask\', character?: string, offset?: integer}", got "structure{type: string, character: string, offset: string}"',
5252
);
5353

5454
new ResponseConfig(sanitizers: [

0 commit comments

Comments
 (0)