From bdb7b30bc4fae6ab38b7a81e149ad99bccc259b4 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 27 Jun 2018 01:36:57 -0400 Subject: [PATCH] 2.0.9 - add enums --- .../tests_in_enum_test_dart.xml | 7 +++ README.md | 11 +++++ angel_serialize_generator/CHANGELOG.md | 5 ++ angel_serialize_generator/build.yaml | 1 + .../lib/angel_serialize_generator.dart | 11 +++++ angel_serialize_generator/lib/model.dart | 20 ++++++-- angel_serialize_generator/lib/serialize.dart | 26 ++++++++-- angel_serialize_generator/pubspec.yaml | 2 +- angel_serialize_generator/test/enum_test.dart | 48 +++++++++++++++++++ .../test/models/author.serializer.g.dart | 40 +++++++++------- .../test/models/book.serializer.g.dart | 18 +++---- .../test/models/with_enum.dart | 11 +++++ .../test/models/with_enum.g.dart | 26 ++++++++++ .../test/models/with_enum.serializer.g.dart | 27 +++++++++++ 14 files changed, 217 insertions(+), 36 deletions(-) create mode 100644 .idea/runConfigurations/tests_in_enum_test_dart.xml create mode 100644 angel_serialize_generator/test/enum_test.dart create mode 100644 angel_serialize_generator/test/models/with_enum.dart create mode 100644 angel_serialize_generator/test/models/with_enum.g.dart create mode 100644 angel_serialize_generator/test/models/with_enum.serializer.g.dart diff --git a/.idea/runConfigurations/tests_in_enum_test_dart.xml b/.idea/runConfigurations/tests_in_enum_test_dart.xml new file mode 100644 index 00000000..9a4a3357 --- /dev/null +++ b/.idea/runConfigurations/tests_in_enum_test_dart.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index de5d50ea..65405b21 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,20 @@ part 'book.g.dart'; @serializable abstract class _Book extends Model { String get author; + String get title; + String get description; + int get pageCount; + + BookType get type; +} + +/// It even supports enums! +enum BookType { + fiction, + nonFiction } ``` diff --git a/angel_serialize_generator/CHANGELOG.md b/angel_serialize_generator/CHANGELOG.md index dd455c0f..6f2f8bd8 100644 --- a/angel_serialize_generator/CHANGELOG.md +++ b/angel_serialize_generator/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.0.9 +* Now supports de/serialization of `enum` types. +* Generate `const` constructors when possible. +* Remove `whereType`, perform manual coercion. + # 2.0.8 * Generate a `fromMap` with typecasting, for Dart 2's sake. diff --git a/angel_serialize_generator/build.yaml b/angel_serialize_generator/build.yaml index 842854c9..85a0a683 100644 --- a/angel_serialize_generator/build.yaml +++ b/angel_serialize_generator/build.yaml @@ -23,6 +23,7 @@ targets: _book: sources: - "test/models/book.dart" + - "test/models/with_enum.dart" _typescript_definition: sources: - "lib/*.dart" diff --git a/angel_serialize_generator/lib/angel_serialize_generator.dart b/angel_serialize_generator/lib/angel_serialize_generator.dart index 486fd071..c361f677 100644 --- a/angel_serialize_generator/lib/angel_serialize_generator.dart +++ b/angel_serialize_generator/lib/angel_serialize_generator.dart @@ -3,6 +3,7 @@ library angel_serialize_generator; import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; +import 'package:angel_model/angel_model.dart'; import 'package:angel_serialize/angel_serialize.dart'; import 'package:build/build.dart'; import 'package:code_buffer/code_buffer.dart'; @@ -66,6 +67,14 @@ bool isListOrMapType(DartType t) { const TypeChecker.fromRuntime(Map).isAssignableFromType(t); } +bool isEnumType(DartType t) { + if (t is InterfaceType) { + return t.element.isEnum; + } + + return false; +} + /// Determines if a [DartType] is a `List` with the first type argument being a `Model`. bool isListModelType(InterfaceType t) { return const TypeChecker.fromRuntime(List).isAssignableFromType(t) && @@ -80,6 +89,8 @@ bool isMapToModelType(InterfaceType t) { isModelClass(t.typeArguments[1]); } +bool isAssignableToModel(DartType type) => const TypeChecker.fromRuntime(Model).isAssignableFromType(type); + /// Compute a [String] representation of a [type]. String typeToString(DartType type) { if (type is InterfaceType) { diff --git a/angel_serialize_generator/lib/model.dart b/angel_serialize_generator/lib/model.dart index 90a81513..1a767c1b 100644 --- a/angel_serialize_generator/lib/model.dart +++ b/angel_serialize_generator/lib/model.dart @@ -26,9 +26,13 @@ class JsonModelGenerator extends GeneratorForAnnotation { void generateClass( BuildContext ctx, LibraryBuilder file, ConstantReader annotation) { file.body.add(new Class((clazz) { - clazz - ..name = ctx.modelClassNameRecase.pascalCase - ..extend = new Reference(ctx.originalClassName); + clazz..name = ctx.modelClassNameRecase.pascalCase; + + if (shouldBeConstant(ctx)) { + clazz.implements.add(new Reference(ctx.originalClassName)); + } else { + clazz.extend = new Reference(ctx.originalClassName); + } //if (ctx.importsPackageMeta) // clazz.annotations.add(new CodeExpression(new Code('immutable'))); @@ -61,11 +65,19 @@ class JsonModelGenerator extends GeneratorForAnnotation { })); } + bool shouldBeConstant(BuildContext ctx) { + // Check if all fields are without a getter + return !isAssignableToModel(ctx.clazz.type) && + ctx.clazz.fields.every((f) => f.getter == null || f.setter == null); + } + /// Generate a constructor with named parameters. void generateConstructor( BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) { clazz.constructors.add(new Constructor((constructor) { // Add all `super` params + constructor.constant = ctx.clazz.unnamedConstructor?.isConst == true || + shouldBeConstant(ctx); for (var param in ctx.constructorParameters) { constructor.requiredParameters.add(new Parameter((b) => b @@ -156,7 +168,7 @@ class JsonModelGenerator extends GeneratorForAnnotation { } static String generateEquality(DartType type, [bool nullable = false]) { - //if (type is! InterfaceType) return 'const DefaultEquality()'; +//if (type is! InterfaceType) return 'const DefaultEquality()'; var it = type as InterfaceType; if (const TypeChecker.fromRuntime(List).isAssignableFromType(type)) { if (it.typeParameters.length == 1) { diff --git a/angel_serialize_generator/lib/serialize.dart b/angel_serialize_generator/lib/serialize.dart index cbd1b229..b544acd7 100644 --- a/angel_serialize_generator/lib/serialize.dart +++ b/angel_serialize_generator/lib/serialize.dart @@ -112,6 +112,12 @@ class SerializerGenerator extends GeneratorForAnnotation { return map..[key] = ${rc.pascalCase}Serializer.toMap(model.${field .name}[key]); })'''; + } else if (t.element.isEnum) { + serializedRepresentation = ''' + model.${field.name} == null ? + null + : ${t.name}.values.indexOf(model.${field.name}) + '''; } } @@ -193,19 +199,31 @@ class SerializerGenerator extends GeneratorForAnnotation { if (isListModelType(t)) { var rc = new ReCase(t.typeArguments[0].name); deserializedRepresentation = "map['$alias'] is Iterable" - " ? new List.unmodifiable((map['$alias'] as Iterable).whereType().map(${rc - .pascalCase}Serializer.fromMap))" + " ? new List.unmodifiable(((map['$alias'] as Iterable)" + ".where((x) => x is Map) as Iterable)" + ".map(${rc.pascalCase}Serializer.fromMap))" " : null"; } else if (isMapToModelType(t)) { var rc = new ReCase(t.typeArguments[1].name); deserializedRepresentation = ''' map['$alias'] is Map ? new Map.unmodifiable((map['$alias'] as Map).keys.fold({}, (out, key) { - return out..[key] = ${rc - .pascalCase}Serializer.fromMap((map['$alias'] as Map)[key]); + return out..[key] = ${rc.pascalCase}Serializer + .fromMap((map['$alias'] as Map)[key]); })) : null '''; + } else if (t.element.isEnum) { + deserializedRepresentation = ''' + map['$alias'] is ${t.name} + ? map['$alias'] + : + ( + map['$alias'] is int + ? ${t.name}.values[map['$alias']] + : null + ) + '''; } } diff --git a/angel_serialize_generator/pubspec.yaml b/angel_serialize_generator/pubspec.yaml index e337d180..df87d144 100644 --- a/angel_serialize_generator/pubspec.yaml +++ b/angel_serialize_generator/pubspec.yaml @@ -16,5 +16,5 @@ dependencies: recase: ^1.0.0 source_gen: ^0.7.0 dev_dependencies: - build_runner: ^0.9.0 + build_runner: ^0.8.0 test: ^1.0.0 \ No newline at end of file diff --git a/angel_serialize_generator/test/enum_test.dart b/angel_serialize_generator/test/enum_test.dart new file mode 100644 index 00000000..15d1dd7e --- /dev/null +++ b/angel_serialize_generator/test/enum_test.dart @@ -0,0 +1,48 @@ +import 'package:test/test.dart'; +import 'models/with_enum.dart'; + +const WithEnum aWithEnum = const WithEnum(type: WithEnumType.a); +const WithEnum aWithEnum2 = const WithEnum(type: WithEnumType.a); + +void main() { + test('enum serializes to int', () { + var w = new WithEnum(type: WithEnumType.b).toJson(); + expect(w[WithEnumFields.type], WithEnumType.values.indexOf(WithEnumType.b)); + }); + + test('enum serializes null if null', () { + var w = new WithEnum(type: null).toJson(); + expect(w[WithEnumFields.type], null); + }); + + test('enum deserializes to null from null', () { + var map = {WithEnumFields.type: null}; + var w = WithEnumSerializer.fromMap(map); + expect(w.type, isNull); + }); + + test('enum deserializes from int', () { + var map = { + WithEnumFields.type: WithEnumType.values.indexOf(WithEnumType.b) + }; + var w = WithEnumSerializer.fromMap(map); + expect(w.type, WithEnumType.b); + }); + + test('enum deserializes from value', () { + var map = {WithEnumFields.type: WithEnumType.c}; + var w = WithEnumSerializer.fromMap(map); + expect(w.type, WithEnumType.c); + }); + + test('equality', () { + expect( + new WithEnum(type: WithEnumType.a), new WithEnum(type: WithEnumType.a)); + expect(new WithEnum(type: WithEnumType.a), + isNot(new WithEnum(type: WithEnumType.b))); + }); + + test('const', () { + expect(identical(aWithEnum, aWithEnum2), true); + }); +} diff --git a/angel_serialize_generator/test/models/author.serializer.g.dart b/angel_serialize_generator/test/models/author.serializer.g.dart index 6db29c22..5f0c5a52 100644 --- a/angel_serialize_generator/test/models/author.serializer.g.dart +++ b/angel_serialize_generator/test/models/author.serializer.g.dart @@ -17,24 +17,26 @@ abstract class AuthorSerializer { } return new Author( - id: map['id'], - name: map['name'], - age: map['age'], + id: map['id'] as String, + name: map['name'] as String, + age: map['age'] as int, books: map['books'] is Iterable - ? new List.unmodifiable(map['books'].map(BookSerializer.fromMap)) + ? new List.unmodifiable(((map['books'] as Iterable) + .where((x) => x is Map) as Iterable) + .map(BookSerializer.fromMap)) : null, newestBook: map['newest_book'] != null ? BookSerializer.fromMap(map['newest_book']) : null, - obscured: map['obscured'], + obscured: map['obscured'] as String, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime - ? map['created_at'] + ? (map['created_at'] as DateTime) : DateTime.parse(map['created_at'])) : null, updatedAt: map['updated_at'] != null ? (map['updated_at'] is DateTime - ? map['updated_at'] + ? (map['updated_at'] as DateTime) : DateTime.parse(map['updated_at'])) : null); } @@ -83,21 +85,23 @@ abstract class AuthorFields { abstract class LibrarySerializer { static Library fromMap(Map map) { return new Library( - id: map['id'], + id: map['id'] as String, collection: map['collection'] is Map - ? new Map.unmodifiable(map['collection'].keys.fold({}, (out, key) { + ? new Map.unmodifiable( + (map['collection'] as Map).keys.fold({}, (out, key) { return out - ..[key] = BookSerializer.fromMap(map['collection'][key]); + ..[key] = + BookSerializer.fromMap((map['collection'] as Map)[key]); })) : null, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime - ? map['created_at'] + ? (map['created_at'] as DateTime) : DateTime.parse(map['created_at'])) : null, updatedAt: map['updated_at'] != null ? (map['updated_at'] is DateTime - ? map['updated_at'] + ? (map['updated_at'] as DateTime) : DateTime.parse(map['updated_at'])) : null); } @@ -131,18 +135,18 @@ abstract class BookmarkSerializer { } return new Bookmark(book, - id: map['id'], - history: map['history'], - page: map['page'], - comment: map['comment'], + id: map['id'] as String, + history: map['history'] as List, + page: map['page'] as int, + comment: map['comment'] as String, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime - ? map['created_at'] + ? (map['created_at'] as DateTime) : DateTime.parse(map['created_at'])) : null, updatedAt: map['updated_at'] != null ? (map['updated_at'] is DateTime - ? map['updated_at'] + ? (map['updated_at'] as DateTime) : DateTime.parse(map['updated_at'])) : null); } diff --git a/angel_serialize_generator/test/models/book.serializer.g.dart b/angel_serialize_generator/test/models/book.serializer.g.dart index 9f6d00fc..44b49b73 100644 --- a/angel_serialize_generator/test/models/book.serializer.g.dart +++ b/angel_serialize_generator/test/models/book.serializer.g.dart @@ -9,21 +9,21 @@ part of angel_serialize.test.models.book; abstract class BookSerializer { static Book fromMap(Map map) { return new Book( - id: map['id'], - author: map['author'], - title: map['title'], - description: map['description'], - pageCount: map['page_count'], - notModels: map['not_models'], - camelCaseString: map['camelCase'], + id: map['id'] as String, + author: map['author'] as String, + title: map['title'] as String, + description: map['description'] as String, + pageCount: map['page_count'] as int, + notModels: map['not_models'] as List, + camelCaseString: map['camelCase'] as String, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime - ? map['created_at'] + ? (map['created_at'] as DateTime) : DateTime.parse(map['created_at'])) : null, updatedAt: map['updated_at'] != null ? (map['updated_at'] is DateTime - ? map['updated_at'] + ? (map['updated_at'] as DateTime) : DateTime.parse(map['updated_at'])) : null); } diff --git a/angel_serialize_generator/test/models/with_enum.dart b/angel_serialize_generator/test/models/with_enum.dart new file mode 100644 index 00000000..91d4811a --- /dev/null +++ b/angel_serialize_generator/test/models/with_enum.dart @@ -0,0 +1,11 @@ +import 'package:angel_model/angel_model.dart'; +import 'package:angel_serialize/angel_serialize.dart'; +part 'with_enum.g.dart'; +part 'with_enum.serializer.g.dart'; + +@Serializable(autoIdAndDateFields: false) +abstract class _WithEnum { + WithEnumType get type; +} + +enum WithEnumType { a, b, c } diff --git a/angel_serialize_generator/test/models/with_enum.g.dart b/angel_serialize_generator/test/models/with_enum.g.dart new file mode 100644 index 00000000..f5f08a08 --- /dev/null +++ b/angel_serialize_generator/test/models/with_enum.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'with_enum.dart'; + +// ************************************************************************** +// Generator: JsonModelGenerator +// ************************************************************************** + +class WithEnum implements _WithEnum { + const WithEnum({this.type}); + + @override + final WithEnumType type; + + WithEnum copyWith({WithEnumType type}) { + return new WithEnum(type: type ?? this.type); + } + + bool operator ==(other) { + return other is _WithEnum && other.type == type; + } + + Map toJson() { + return WithEnumSerializer.toMap(this); + } +} diff --git a/angel_serialize_generator/test/models/with_enum.serializer.g.dart b/angel_serialize_generator/test/models/with_enum.serializer.g.dart new file mode 100644 index 00000000..b7c19328 --- /dev/null +++ b/angel_serialize_generator/test/models/with_enum.serializer.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'with_enum.dart'; + +// ************************************************************************** +// Generator: SerializerGenerator +// ************************************************************************** + +abstract class WithEnumSerializer { + static WithEnum fromMap(Map map) { + return new WithEnum( + type: map['type'] is WithEnumType + ? map['type'] + : (map['type'] is int ? WithEnumType.values[map['type']] : null)); + } + + static Map toMap(WithEnum model) { + return { + 'type': + model.type == null ? null : WithEnumType.values.indexOf(model.type) + }; + } +} + +abstract class WithEnumFields { + static const String type = 'type'; +}