From 95f425021a1cf1f407a844bd71a8f5ff7e45c903 Mon Sep 17 00:00:00 2001 From: Ben Vercammen Date: Tue, 21 Mar 2023 20:42:45 +0100 Subject: [PATCH] feat: Take @SerializableField properties into account when generating `Query.parseRow` (#98) --- .../lib/src/orm_generator.dart | 55 ++++++++++++++++--- .../test/migrations/has_car.sql | 1 + .../test/migrations/has_car.sql | 1 + .../lib/src/enum_and_nested_test.dart | 5 +- .../lib/src/models/has_car.dart | 22 ++++++++ .../lib/src/models/has_car.g.dart | 39 ++++++++++++- 6 files changed, 110 insertions(+), 13 deletions(-) diff --git a/packages/orm/angel_orm_generator/lib/src/orm_generator.dart b/packages/orm/angel_orm_generator/lib/src/orm_generator.dart index 69e607e9..e83507e5 100644 --- a/packages/orm/angel_orm_generator/lib/src/orm_generator.dart +++ b/packages/orm/angel_orm_generator/lib/src/orm_generator.dart @@ -3,6 +3,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:angel3_orm/angel3_orm.dart'; +import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_serialize_generator/angel3_serialize_generator.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart' hide LibraryBuilder; @@ -277,11 +278,8 @@ class OrmGenerator extends GeneratorForAnnotation { * EnumType.values[(row[3] as int)] : null, */ var isNull = expr.equalTo(literalNull); - - Reference enumType = - convertTypeReference(fType, ignoreNullabilityCheck: true); - expr = isNull.conditional(literalNull, - enumType.property('values').index(expr.asA(refer('int')))); + final parseExpression = _deserializeEnumExpression(field, expr); + expr = isNull.conditional(literalNull, parseExpression); } else if (fType.isDartCoreBool) { // Generated Code: mapToBool(row[i]) expr = refer('mapToBool').call([expr]); @@ -892,9 +890,7 @@ class OrmGenerator extends GeneratorForAnnotation { var value = refer('values').index(literalString(name!)); if (fType is InterfaceType && fType.element is EnumElement) { - var asInt = value.asA(refer('int')); - var t = convertTypeReference(fType, ignoreNullabilityCheck: true); - value = t.property('values').index(asInt); + value = _deserializeEnumExpression(field, value); } else if (const TypeChecker.fromRuntime(List) .isAssignableFromType(fType)) { value = refer('json') @@ -926,7 +922,7 @@ class OrmGenerator extends GeneratorForAnnotation { Expression value = refer('value'); if (fType is InterfaceType && fType.element is EnumElement) { - value = CodeExpression(Code('value?.index')); + value = _serializeEnumExpression(field, value); } else if (const TypeChecker.fromRuntime(List) .isAssignableFromType(fType)) { value = refer('json').property('encode').call([value]); @@ -1005,4 +1001,45 @@ class OrmGenerator extends GeneratorForAnnotation { })); }); } + + /// Retrieve the [Expression] to parse a serialized enumeration field. + /// Takes into account the [SerializableField] properties. + /// Defaults to `enum.values[index as int]` + Expression _deserializeEnumExpression(FieldElement field, Expression expr) { + Reference enumType = + convertTypeReference(field.type, ignoreNullabilityCheck: true); + const TypeChecker serializableFieldTypeChecker = + TypeChecker.fromRuntime(SerializableField); + final annotation = serializableFieldTypeChecker.firstAnnotationOf(field); + Expression? parseExpr; + if (null != annotation) { + final deserializer = annotation.getField('deserializer')?.toSymbolValue(); + if (null != deserializer) { + var type = 'int'; + final serializesTo = annotation.getField('serializesTo')?.toTypeValue(); + if (null != serializesTo) { + type = serializesTo.element!.displayName; + } + parseExpr = Reference(deserializer).expression([expr.asA(refer(type))]); + } + } + return parseExpr ?? + enumType.property('values').index(expr.asA(refer('int'))); + } + + /// Retrieve the [Expression] to serialize the enumeration field. + /// Takes into account the [SerializableField] properties. + Expression _serializeEnumExpression(FieldElement field, Expression expr) { + const TypeChecker serializableFieldTypeChecker = + TypeChecker.fromRuntime(SerializableField); + final annotation = serializableFieldTypeChecker.firstAnnotationOf(field); + Expression? parseExpr; + if (null != annotation) { + final serializer = annotation.getField('serializer')?.toSymbolValue(); + if (null != serializer) { + parseExpr = Reference(serializer).expression([expr]); + } + } + return parseExpr ?? CodeExpression(Code('value?.index')); + } } diff --git a/packages/orm/angel_orm_mysql/test/migrations/has_car.sql b/packages/orm/angel_orm_mysql/test/migrations/has_car.sql index 82ff3644..b67e0d00 100644 --- a/packages/orm/angel_orm_mysql/test/migrations/has_car.sql +++ b/packages/orm/angel_orm_mysql/test/migrations/has_car.sql @@ -1,6 +1,7 @@ CREATE TABLE IF NOT EXISTS has_cars ( id serial PRIMARY KEY, type int not null, + color varchar(1), created_at datetime, updated_at datetime ); \ No newline at end of file diff --git a/packages/orm/angel_orm_postgres/test/migrations/has_car.sql b/packages/orm/angel_orm_postgres/test/migrations/has_car.sql index 67794aed..dce87d3f 100644 --- a/packages/orm/angel_orm_postgres/test/migrations/has_car.sql +++ b/packages/orm/angel_orm_postgres/test/migrations/has_car.sql @@ -1,6 +1,7 @@ CREATE TEMPORARY TABLE "has_cars" ( id serial PRIMARY KEY, type int not null, + color varchar(1), created_at timestamp, updated_at timestamp ); \ No newline at end of file diff --git a/packages/orm/angel_orm_test/lib/src/enum_and_nested_test.dart b/packages/orm/angel_orm_test/lib/src/enum_and_nested_test.dart index 1fdf2530..6dfa85b4 100644 --- a/packages/orm/angel_orm_test/lib/src/enum_and_nested_test.dart +++ b/packages/orm/angel_orm_test/lib/src/enum_and_nested_test.dart @@ -13,11 +13,14 @@ void enumAndNestedTests(FutureOr Function() createExecutor, }); test('insert', () async { - var query = HasCarQuery()..values.type = CarType.sedan; + var query = HasCarQuery() + ..values.type = CarType.sedan + ..values.color = Color.red; var resultOpt = await (query.insert(executor)); expect(resultOpt.isPresent, true); resultOpt.ifPresent((result) { expect(result.type, CarType.sedan); + expect(result.color, Color.red); }); }); diff --git a/packages/orm/angel_orm_test/lib/src/models/has_car.dart b/packages/orm/angel_orm_test/lib/src/models/has_car.dart index 7fef8b81..39a6222f 100644 --- a/packages/orm/angel_orm_test/lib/src/models/has_car.dart +++ b/packages/orm/angel_orm_test/lib/src/models/has_car.dart @@ -11,6 +11,20 @@ part 'has_car.g.dart'; enum CarType { sedan, suv, atv } +Color? codeToColor(String? code) => code == null + ? null + : Color.values.firstWhere((color) => color.code == code); + +String? colorToCode(Color? color) => color?.code; + +enum Color { + red('R'), green('G'), blue('B'); + + const Color(this.code); + + final String code; +} + @orm @serializable abstract class _HasCar extends Model { @@ -21,4 +35,12 @@ abstract class _HasCar extends Model { @SerializableField(isNullable: false, defaultValue: CarType.sedan) CarType? get type; + + @SerializableField( + serializesTo: String, + serializer: #colorToCode, + deserializer: #codeToColor, + ) + @Column(type: ColumnType.varChar, length: 1) + Color? color; } diff --git a/packages/orm/angel_orm_test/lib/src/models/has_car.g.dart b/packages/orm/angel_orm_test/lib/src/models/has_car.g.dart index 2cc6fb15..f2b10004 100644 --- a/packages/orm/angel_orm_test/lib/src/models/has_car.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/has_car.g.dart @@ -15,6 +15,7 @@ class HasCarMigration extends Migration { table.serial('id').primaryKey(); table.timeStamp('created_at'); table.timeStamp('updated_at'); + table.varChar('color'); table.integer('type').defaultsTo(0); }, ); @@ -63,6 +64,7 @@ class HasCarQuery extends Query { 'id', 'created_at', 'updated_at', + 'color', 'type', ]; return _selectedFields.isEmpty @@ -95,10 +97,15 @@ class HasCarQuery extends Query { fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, updatedAt: fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, - type: fields.contains('type') + color: fields.contains('color') ? row[3] == null ? null - : CarType.values[(row[3] as int)] + : codeToColor((row[3] as String)) + : null, + type: fields.contains('type') + ? row[4] == null + ? null + : CarType.values[(row[4] as int)] : null, ); return Optional.of(model); @@ -124,6 +131,10 @@ class HasCarQueryWhere extends QueryWhere { query, 'updated_at', ), + color = StringSqlExpressionBuilder( + query, + 'color', + ), type = EnumSqlExpressionBuilder( query, 'type', @@ -136,6 +147,8 @@ class HasCarQueryWhere extends QueryWhere { final DateTimeSqlExpressionBuilder updatedAt; + final StringSqlExpressionBuilder color; + final EnumSqlExpressionBuilder type; @override @@ -144,6 +157,7 @@ class HasCarQueryWhere extends QueryWhere { id, createdAt, updatedAt, + color, type, ]; } @@ -170,6 +184,11 @@ class HasCarQueryValues extends MapQueryValues { } set updatedAt(DateTime? value) => values['updated_at'] = value; + Color? get color { + return codeToColor((values['color'] as String)); + } + + set color(Color? value) => values['color'] = colorToCode(value); CarType? get type { return CarType.values[(values['type'] as int)]; } @@ -178,6 +197,7 @@ class HasCarQueryValues extends MapQueryValues { void copyFrom(HasCar model) { createdAt = model.createdAt; updatedAt = model.updatedAt; + color = model.color; type = model.type; } } @@ -192,6 +212,7 @@ class HasCar extends _HasCar { this.id, this.createdAt, this.updatedAt, + this.color, this.type = CarType.sedan, }); @@ -207,6 +228,9 @@ class HasCar extends _HasCar { @override DateTime? updatedAt; + @override + Color? color; + @override CarType? type; @@ -214,12 +238,14 @@ class HasCar extends _HasCar { String? id, DateTime? createdAt, DateTime? updatedAt, + Color? color, CarType? type, }) { return HasCar( id: id ?? this.id, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, + color: color ?? this.color, type: type ?? this.type); } @@ -229,6 +255,7 @@ class HasCar extends _HasCar { other.id == id && other.createdAt == createdAt && other.updatedAt == updatedAt && + other.color == color && other.type == type; } @@ -238,13 +265,14 @@ class HasCar extends _HasCar { id, createdAt, updatedAt, + color, type, ]); } @override String toString() { - return 'HasCar(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, type=$type)'; + return 'HasCar(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, color=$color, type=$type)'; } Map toJson() { @@ -296,6 +324,7 @@ class HasCarSerializer extends Codec { ? (map['updated_at'] as DateTime) : DateTime.parse(map['updated_at'].toString())) : null, + color: codeToColor(map['color']), type: map['type'] as CarType? ?? CarType.sedan); } @@ -307,6 +336,7 @@ class HasCarSerializer extends Codec { 'id': model.id, 'created_at': model.createdAt?.toIso8601String(), 'updated_at': model.updatedAt?.toIso8601String(), + 'color': colorToCode(model.color), 'type': model.type }; } @@ -317,6 +347,7 @@ abstract class HasCarFields { id, createdAt, updatedAt, + color, type, ]; @@ -326,5 +357,7 @@ abstract class HasCarFields { static const String updatedAt = 'updated_at'; + static const String color = 'color'; + static const String type = 'type'; }