feat: Take @SerializableField properties into account when generating Query.parseRow (#98)

This commit is contained in:
Ben Vercammen 2023-03-21 20:42:45 +01:00
parent fd5f726e2f
commit 95f425021a
6 changed files with 110 additions and 13 deletions

View file

@ -3,6 +3,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/type.dart';
import 'package:angel3_orm/angel3_orm.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:angel3_serialize_generator/angel3_serialize_generator.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart' hide LibraryBuilder; import 'package:code_builder/code_builder.dart' hide LibraryBuilder;
@ -277,11 +278,8 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
* EnumType.values[(row[3] as int)] : null, * EnumType.values[(row[3] as int)] : null,
*/ */
var isNull = expr.equalTo(literalNull); var isNull = expr.equalTo(literalNull);
final parseExpression = _deserializeEnumExpression(field, expr);
Reference enumType = expr = isNull.conditional(literalNull, parseExpression);
convertTypeReference(fType, ignoreNullabilityCheck: true);
expr = isNull.conditional(literalNull,
enumType.property('values').index(expr.asA(refer('int'))));
} else if (fType.isDartCoreBool) { } else if (fType.isDartCoreBool) {
// Generated Code: mapToBool(row[i]) // Generated Code: mapToBool(row[i])
expr = refer('mapToBool').call([expr]); expr = refer('mapToBool').call([expr]);
@ -892,9 +890,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
var value = refer('values').index(literalString(name!)); var value = refer('values').index(literalString(name!));
if (fType is InterfaceType && fType.element is EnumElement) { if (fType is InterfaceType && fType.element is EnumElement) {
var asInt = value.asA(refer('int')); value = _deserializeEnumExpression(field, value);
var t = convertTypeReference(fType, ignoreNullabilityCheck: true);
value = t.property('values').index(asInt);
} else if (const TypeChecker.fromRuntime(List) } else if (const TypeChecker.fromRuntime(List)
.isAssignableFromType(fType)) { .isAssignableFromType(fType)) {
value = refer('json') value = refer('json')
@ -926,7 +922,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
Expression value = refer('value'); Expression value = refer('value');
if (fType is InterfaceType && fType.element is EnumElement) { if (fType is InterfaceType && fType.element is EnumElement) {
value = CodeExpression(Code('value?.index')); value = _serializeEnumExpression(field, value);
} else if (const TypeChecker.fromRuntime(List) } else if (const TypeChecker.fromRuntime(List)
.isAssignableFromType(fType)) { .isAssignableFromType(fType)) {
value = refer('json').property('encode').call([value]); value = refer('json').property('encode').call([value]);
@ -1005,4 +1001,45 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
})); }));
}); });
} }
/// 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'));
}
} }

View file

@ -1,6 +1,7 @@
CREATE TABLE IF NOT EXISTS has_cars ( CREATE TABLE IF NOT EXISTS has_cars (
id serial PRIMARY KEY, id serial PRIMARY KEY,
type int not null, type int not null,
color varchar(1),
created_at datetime, created_at datetime,
updated_at datetime updated_at datetime
); );

View file

@ -1,6 +1,7 @@
CREATE TEMPORARY TABLE "has_cars" ( CREATE TEMPORARY TABLE "has_cars" (
id serial PRIMARY KEY, id serial PRIMARY KEY,
type int not null, type int not null,
color varchar(1),
created_at timestamp, created_at timestamp,
updated_at timestamp updated_at timestamp
); );

View file

@ -13,11 +13,14 @@ void enumAndNestedTests(FutureOr<QueryExecutor> Function() createExecutor,
}); });
test('insert', () async { 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)); var resultOpt = await (query.insert(executor));
expect(resultOpt.isPresent, true); expect(resultOpt.isPresent, true);
resultOpt.ifPresent((result) { resultOpt.ifPresent((result) {
expect(result.type, CarType.sedan); expect(result.type, CarType.sedan);
expect(result.color, Color.red);
}); });
}); });

View file

@ -11,6 +11,20 @@ part 'has_car.g.dart';
enum CarType { sedan, suv, atv } 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 @orm
@serializable @serializable
abstract class _HasCar extends Model { abstract class _HasCar extends Model {
@ -21,4 +35,12 @@ abstract class _HasCar extends Model {
@SerializableField(isNullable: false, defaultValue: CarType.sedan) @SerializableField(isNullable: false, defaultValue: CarType.sedan)
CarType? get type; CarType? get type;
@SerializableField(
serializesTo: String,
serializer: #colorToCode,
deserializer: #codeToColor,
)
@Column(type: ColumnType.varChar, length: 1)
Color? color;
} }

View file

@ -15,6 +15,7 @@ class HasCarMigration extends Migration {
table.serial('id').primaryKey(); table.serial('id').primaryKey();
table.timeStamp('created_at'); table.timeStamp('created_at');
table.timeStamp('updated_at'); table.timeStamp('updated_at');
table.varChar('color');
table.integer('type').defaultsTo(0); table.integer('type').defaultsTo(0);
}, },
); );
@ -63,6 +64,7 @@ class HasCarQuery extends Query<HasCar, HasCarQueryWhere> {
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
'color',
'type', 'type',
]; ];
return _selectedFields.isEmpty return _selectedFields.isEmpty
@ -95,10 +97,15 @@ class HasCarQuery extends Query<HasCar, HasCarQueryWhere> {
fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null, fields.contains('created_at') ? mapToNullableDateTime(row[1]) : null,
updatedAt: updatedAt:
fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null, fields.contains('updated_at') ? mapToNullableDateTime(row[2]) : null,
type: fields.contains('type') color: fields.contains('color')
? row[3] == null ? row[3] == null
? 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, : null,
); );
return Optional.of(model); return Optional.of(model);
@ -124,6 +131,10 @@ class HasCarQueryWhere extends QueryWhere {
query, query,
'updated_at', 'updated_at',
), ),
color = StringSqlExpressionBuilder(
query,
'color',
),
type = EnumSqlExpressionBuilder<CarType?>( type = EnumSqlExpressionBuilder<CarType?>(
query, query,
'type', 'type',
@ -136,6 +147,8 @@ class HasCarQueryWhere extends QueryWhere {
final DateTimeSqlExpressionBuilder updatedAt; final DateTimeSqlExpressionBuilder updatedAt;
final StringSqlExpressionBuilder color;
final EnumSqlExpressionBuilder<CarType?> type; final EnumSqlExpressionBuilder<CarType?> type;
@override @override
@ -144,6 +157,7 @@ class HasCarQueryWhere extends QueryWhere {
id, id,
createdAt, createdAt,
updatedAt, updatedAt,
color,
type, type,
]; ];
} }
@ -170,6 +184,11 @@ class HasCarQueryValues extends MapQueryValues {
} }
set updatedAt(DateTime? value) => values['updated_at'] = value; 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 { CarType? get type {
return CarType.values[(values['type'] as int)]; return CarType.values[(values['type'] as int)];
} }
@ -178,6 +197,7 @@ class HasCarQueryValues extends MapQueryValues {
void copyFrom(HasCar model) { void copyFrom(HasCar model) {
createdAt = model.createdAt; createdAt = model.createdAt;
updatedAt = model.updatedAt; updatedAt = model.updatedAt;
color = model.color;
type = model.type; type = model.type;
} }
} }
@ -192,6 +212,7 @@ class HasCar extends _HasCar {
this.id, this.id,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.color,
this.type = CarType.sedan, this.type = CarType.sedan,
}); });
@ -207,6 +228,9 @@ class HasCar extends _HasCar {
@override @override
DateTime? updatedAt; DateTime? updatedAt;
@override
Color? color;
@override @override
CarType? type; CarType? type;
@ -214,12 +238,14 @@ class HasCar extends _HasCar {
String? id, String? id,
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
Color? color,
CarType? type, CarType? type,
}) { }) {
return HasCar( return HasCar(
id: id ?? this.id, id: id ?? this.id,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
color: color ?? this.color,
type: type ?? this.type); type: type ?? this.type);
} }
@ -229,6 +255,7 @@ class HasCar extends _HasCar {
other.id == id && other.id == id &&
other.createdAt == createdAt && other.createdAt == createdAt &&
other.updatedAt == updatedAt && other.updatedAt == updatedAt &&
other.color == color &&
other.type == type; other.type == type;
} }
@ -238,13 +265,14 @@ class HasCar extends _HasCar {
id, id,
createdAt, createdAt,
updatedAt, updatedAt,
color,
type, type,
]); ]);
} }
@override @override
String toString() { 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<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -296,6 +324,7 @@ class HasCarSerializer extends Codec<HasCar, Map> {
? (map['updated_at'] as DateTime) ? (map['updated_at'] as DateTime)
: DateTime.parse(map['updated_at'].toString())) : DateTime.parse(map['updated_at'].toString()))
: null, : null,
color: codeToColor(map['color']),
type: map['type'] as CarType? ?? CarType.sedan); type: map['type'] as CarType? ?? CarType.sedan);
} }
@ -307,6 +336,7 @@ class HasCarSerializer extends Codec<HasCar, Map> {
'id': model.id, 'id': model.id,
'created_at': model.createdAt?.toIso8601String(), 'created_at': model.createdAt?.toIso8601String(),
'updated_at': model.updatedAt?.toIso8601String(), 'updated_at': model.updatedAt?.toIso8601String(),
'color': colorToCode(model.color),
'type': model.type 'type': model.type
}; };
} }
@ -317,6 +347,7 @@ abstract class HasCarFields {
id, id,
createdAt, createdAt,
updatedAt, updatedAt,
color,
type, type,
]; ];
@ -326,5 +357,7 @@ abstract class HasCarFields {
static const String updatedAt = 'updated_at'; static const String updatedAt = 'updated_at';
static const String color = 'color';
static const String type = 'type'; static const String type = 'type';
} }