From eff6efb16c60081b99caa72b3d342bee80755910 Mon Sep 17 00:00:00 2001 From: Francisco Edno Date: Wed, 11 Nov 2020 18:43:31 -0300 Subject: [PATCH] Allow referencing previously defined named types Once a named type is defined, in order to reuse that same type it is needed to reference that type by the name (as in the primitive types). Add support for NamedTypes in both the schema builder and the schema generator, which is a way of referencing previously defined named types. Add support for adding extra attributes for records in field's definition. --- src/Objects/Schema.php | 6 ++ .../Schema/Generation/SchemaGenerator.php | 81 ++++--------------- src/Objects/Schema/Generation/TypeMapper.php | 70 ++++++++++++++++ src/Objects/Schema/NamedType.php | 25 ++++++ .../Fixture/RecordWithRecordType.php | 12 ++- .../Schema/Generation/SchemaGeneratorTest.php | 16 +++- 6 files changed, 141 insertions(+), 69 deletions(-) create mode 100644 src/Objects/Schema/Generation/TypeMapper.php create mode 100644 src/Objects/Schema/NamedType.php diff --git a/src/Objects/Schema.php b/src/Objects/Schema.php index 43a7eef..a78d2a6 100644 --- a/src/Objects/Schema.php +++ b/src/Objects/Schema.php @@ -19,6 +19,7 @@ use FlixTech\AvroSerializer\Objects\Schema\LocalTimestampMillisType; use FlixTech\AvroSerializer\Objects\Schema\LongType; use FlixTech\AvroSerializer\Objects\Schema\MapType; +use FlixTech\AvroSerializer\Objects\Schema\NamedType; use FlixTech\AvroSerializer\Objects\Schema\NullType; use FlixTech\AvroSerializer\Objects\Schema\RecordType; use FlixTech\AvroSerializer\Objects\Schema\StringType; @@ -71,6 +72,11 @@ public static function string(): StringType return new StringType(); } + public static function named(string $name): NamedType + { + return new NamedType($name); + } + public static function record(): RecordType { return new RecordType(); diff --git a/src/Objects/Schema/Generation/SchemaGenerator.php b/src/Objects/Schema/Generation/SchemaGenerator.php index 5ec6264..46d0219 100644 --- a/src/Objects/Schema/Generation/SchemaGenerator.php +++ b/src/Objects/Schema/Generation/SchemaGenerator.php @@ -18,9 +18,15 @@ class SchemaGenerator */ private $reader; + /** + * @var TypeMapper + */ + private $typeMapper; + public function __construct(SchemaAttributeReader $reader) { $this->reader = $reader; + $this->typeMapper = new TypeMapper($this); } /** @@ -39,7 +45,7 @@ public function generate(string $className): Schema */ private function generateFromClass(ReflectionClass $class, Type $type): Schema { - $schema = $this->schemaFromTypes($type); + $schema = $this->schemaFromType($type); if (!$schema instanceof Schema\RecordType) { return $schema; @@ -56,75 +62,20 @@ private function generateFromClass(ReflectionClass $class, Type $type): Schema private function schemaFromTypes(Type ...$types): Schema { if (\count($types) > 1) { - $unionSchemas = \array_map(function (Type $type) { - return $this->schemaFromTypes($type); - }, $types); + $unionSchemas = \array_map([$this, 'schemaFromType'], $types); return Schema::union(...$unionSchemas); } - $type = $types[0]; - $attributes = $type->getAttributes(); - - switch ($type->getTypeName()) { - case TypeName::RECORD: - if ($attributes->has(AttributeName::TARGET_CLASS)) { - return $this->generate($attributes->get(AttributeName::TARGET_CLASS)); - } - $schema = Schema::record(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::NULL: - $schema = Schema::null(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::BOOLEAN: - $schema = Schema::boolean(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::INT: - $schema = Schema::int(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::LONG: - $schema = Schema::long(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::FLOAT: - $schema = Schema::float(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::DOUBLE: - $schema = Schema::double(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::BYTES: - $schema = Schema::bytes(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::STRING: - $schema = Schema::string(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::ARRAY: - $schema = Schema::array(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::MAP: - $schema = Schema::map(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::ENUM: - $schema = Schema::enum(); - - return $this->applyAttributes($schema, $attributes); - case TypeName::FIXED: - $schema = Schema::fixed(); + return $this->schemaFromType($types[0]); + } - return $this->applyAttributes($schema, $attributes); - default: - throw new \InvalidArgumentException('$type is not a valid avro type'); - } + private function schemaFromType(Type $type): Schema + { + return $this->applyAttributes( + $this->typeMapper->toSchema($type), + $type->getAttributes() + ); } private function parseField(ReflectionProperty $property, Schema\RecordType $rootSchema): Schema diff --git a/src/Objects/Schema/Generation/TypeMapper.php b/src/Objects/Schema/Generation/TypeMapper.php new file mode 100644 index 0000000..3a8c5bf --- /dev/null +++ b/src/Objects/Schema/Generation/TypeMapper.php @@ -0,0 +1,70 @@ + + */ + private $mappers; + + public function __construct(SchemaGenerator $generator) + { + $this->mappers = [ + TypeName::RECORD => $this->recordType($generator), + TypeName::NULL => $this->simpleType(Schema::null()), + TypeName::BOOLEAN => $this->simpleType(Schema::boolean()), + TypeName::INT => $this->simpleType(Schema::int()), + TypeName::LONG => $this->simpleType(Schema::long()), + TypeName::FLOAT => $this->simpleType(Schema::float()), + TypeName::DOUBLE => $this->simpleType(Schema::double()), + TypeName::BYTES => $this->simpleType(Schema::bytes()), + TypeName::STRING => $this->simpleType(Schema::string()), + TypeName::ARRAY => $this->simpleType(Schema::array()), + TypeName::MAP => $this->simpleType(Schema::map()), + TypeName::ENUM => $this->simpleType(Schema::enum()), + TypeName::FIXED => $this->simpleType(Schema::fixed()), + ]; + } + + public function toSchema(Type $type): Schema + { + $mapper = $this->mappers[$type->getTypeName()] ?? $this->namedType(); + + return $mapper($type); + } + + private function simpleType(Schema $schema): callable + { + return function () use ($schema): Schema { + return $schema; + }; + } + + private function recordType(SchemaGenerator $generator): callable + { + return function (Type $type) use ($generator): Schema { + $attributes = $type->getAttributes(); + + if ($attributes->has(AttributeName::TARGET_CLASS)) { + return $generator->generate($attributes->get(AttributeName::TARGET_CLASS)); + } + + return Schema::record(); + }; + } + + private function namedType(): callable + { + return function (Type $type): Schema { + return Schema::named($type->getTypeName()); + }; + } +} diff --git a/src/Objects/Schema/NamedType.php b/src/Objects/Schema/NamedType.php new file mode 100644 index 0000000..8a4a14b --- /dev/null +++ b/src/Objects/Schema/NamedType.php @@ -0,0 +1,25 @@ +name = $name; + } + + public function serialize(): string + { + return $this->name; + } +} diff --git a/test/Objects/Schema/Generation/Fixture/RecordWithRecordType.php b/test/Objects/Schema/Generation/Fixture/RecordWithRecordType.php index 7ee9b90..4a9a08c 100644 --- a/test/Objects/Schema/Generation/Fixture/RecordWithRecordType.php +++ b/test/Objects/Schema/Generation/Fixture/RecordWithRecordType.php @@ -13,10 +13,18 @@ class RecordWithRecordType { /** - * @SerDe\AvroName("simple") + * @SerDe\AvroName("simpleField") * @SerDe\AvroType("record", attributes={ - * @SerDe\AvroTargetClass("\FlixTech\AvroSerializer\Test\Objects\Schema\Generation\Fixture\SimpleRecord") + * @SerDe\AvroTargetClass("\FlixTech\AvroSerializer\Test\Objects\Schema\Generation\Fixture\SimpleRecord"), + * @SerDe\AvroDoc("This a simple record for testing purposes") * }) */ private $simpleRecord; + + /** + * @SerDe\AvroName("unionField") + * @SerDe\AvroType("null") + * @SerDe\AvroType("org.acme.SimpleRecord") + */ + private $unionRecord; } diff --git a/test/Objects/Schema/Generation/SchemaGeneratorTest.php b/test/Objects/Schema/Generation/SchemaGeneratorTest.php index 08f8e6a..05fc440 100644 --- a/test/Objects/Schema/Generation/SchemaGeneratorTest.php +++ b/test/Objects/Schema/Generation/SchemaGeneratorTest.php @@ -151,11 +151,23 @@ public function it_should_generate_records_containing_records() $expected = Schema::record() ->name('RecordWithRecordType') ->field( - 'simple', + 'simpleField', Schema::record() ->name('SimpleRecord') ->namespace('org.acme') - ->field('intType', Schema::int(), Schema\Record\FieldOption::default(42)) + ->doc('This a simple record for testing purposes') + ->field( + 'intType', + Schema::int(), + Schema\Record\FieldOption::default(42) + ), + ) + ->field( + 'unionField', + Schema::union( + Schema::null(), + Schema::named('org.acme.SimpleRecord') + ) ); $this->assertEquals($expected, $schema);