From f0cc3bb5136f17e4146771b84062eb03749be054 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 8 Apr 2019 11:00:04 -0400 Subject: [PATCH] gen@2.4.3 --- README.md | 6 ++ angel_serialize/CHANGELOG.md | 3 + angel_serialize/lib/angel_serialize.dart | 1 + angel_serialize/pubspec.yaml | 2 +- angel_serialize_generator/CHANGELOG.md | 5 + angel_serialize_generator/example/main.g.dart | 29 +++++- angel_serialize_generator/lib/model.dart | 26 +++++ angel_serialize_generator/lib/serialize.dart | 49 +++++++++- angel_serialize_generator/pubspec.yaml | 8 +- angel_serialize_generator/test/book_test.dart | 12 +-- angel_serialize_generator/test/enum_test.dart | 2 +- .../test/models/book.dart | 4 + .../test/models/book.g.dart | 94 ++++++++++++++++++- .../test/models/game_pad_button.g.dart | 59 +++++++++++- .../test/models/goat.g.dart | 29 +++++- .../test/models/has_map.g.dart | 29 +++++- .../test/models/with_enum.g.dart | 29 +++++- 17 files changed, 363 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4f703525..4edf099c 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,12 @@ Producing these classes: - `Book`: Extends or implements `_Book`; may be `const`-enabled. - `BookSerializer`: static functionality for serializing `Book` models. - `BookFields`: The names of all fields from the `Book` model, statically-available. +- `BookEncoder`: Allows `BookSerializer` to extend `Codec`. +- `BookDecoder`: Also allows `BookSerializer` to extend `Codec`. + +And the following other features: +- `bookSerializer`: A top-level, `const` instance of `BookSerializer`. +- `Book.toString`: Prints out all of a `Book` instance's fields. # Serialization diff --git a/angel_serialize/CHANGELOG.md b/angel_serialize/CHANGELOG.md index 71c8895f..6cc6bd9e 100644 --- a/angel_serialize/CHANGELOG.md +++ b/angel_serialize/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.2.3+1 +* Export `json`, `Codec`, and `Converter` from `dart:convert`. + # 2.2.3 * `isNullable` defaults to `true`, and will not change. * Deprecate `@nullable`. diff --git a/angel_serialize/lib/angel_serialize.dart b/angel_serialize/lib/angel_serialize.dart index a9a31415..56fef3d6 100644 --- a/angel_serialize/lib/angel_serialize.dart +++ b/angel_serialize/lib/angel_serialize.dart @@ -1,3 +1,4 @@ +export 'dart:convert' show json, Codec, Converter; export 'package:angel_model/angel_model.dart'; export 'package:collection/collection.dart'; export 'package:meta/meta.dart' show required, Required; diff --git a/angel_serialize/pubspec.yaml b/angel_serialize/pubspec.yaml index 7a36f746..a9f2d61d 100644 --- a/angel_serialize/pubspec.yaml +++ b/angel_serialize/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_serialize -version: 2.2.3 +version: 2.2.3+1 description: Static annotations powering Angel model serialization. Combine with angel_serialize_generator for flexible modeling. author: Tobe O homepage: https://github.com/angel-dart/serialize diff --git a/angel_serialize_generator/CHANGELOG.md b/angel_serialize_generator/CHANGELOG.md index 50427a19..9e8077d5 100644 --- a/angel_serialize_generator/CHANGELOG.md +++ b/angel_serialize_generator/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.4.3 +* Generate `Codec` and `Converter` classes. +* Generate `toString` methods. +* Include original documentation comments from the model. + # 2.4.2 * Fix bug where enums didn't support default values. * Stop emitting `@required` on items with default values. diff --git a/angel_serialize_generator/example/main.g.dart b/angel_serialize_generator/example/main.g.dart index d30ff1cb..25b200df 100644 --- a/angel_serialize_generator/example/main.g.dart +++ b/angel_serialize_generator/example/main.g.dart @@ -30,6 +30,11 @@ class Todo extends _Todo { return hashObjects([text, completed]); } + @override + String toString() { + return "Todo(text=$text, completed=$completed)"; + } + Map toJson() { return TodoSerializer.toMap(this); } @@ -39,7 +44,29 @@ class Todo extends _Todo { // SerializerGenerator // ************************************************************************** -abstract class TodoSerializer { +const TodoSerializer todoSerializer = const TodoSerializer(); + +class TodoEncoder extends Converter { + const TodoEncoder(); + + @override + Map convert(Todo model) => TodoSerializer.toMap(model); +} + +class TodoDecoder extends Converter { + const TodoDecoder(); + + @override + Todo convert(Map map) => TodoSerializer.fromMap(map); +} + +class TodoSerializer extends Codec { + const TodoSerializer(); + + @override + get encoder => const TodoEncoder(); + @override + get decoder => const TodoDecoder(); static Todo fromMap(Map map) { return new Todo( text: map['text'] as String, completed: map['completed'] as bool); diff --git a/angel_serialize_generator/lib/model.dart b/angel_serialize_generator/lib/model.dart index 9b7fe1a1..93b9dec7 100644 --- a/angel_serialize_generator/lib/model.dart +++ b/angel_serialize_generator/lib/model.dart @@ -48,6 +48,12 @@ class JsonModelGenerator extends GeneratorForAnnotation { ..modifier = FieldModifier.final$ ..annotations.add(new CodeExpression(new Code('override'))) ..type = convertTypeReference(field.type); + + for (var el in [field.getter, field]) { + if (el?.documentationComment != null) { + b.docs.addAll(el.documentationComment.split('\n')); + } + } })); } @@ -55,6 +61,7 @@ class JsonModelGenerator extends GeneratorForAnnotation { generateCopyWithMethod(ctx, clazz, file); generateEqualsOperator(ctx, clazz, file); generateHashCode(ctx, clazz); + generateToString(ctx, clazz); // Generate toJson() method if necessary var serializers = annotation.peek('serializers')?.listValue ?? []; @@ -235,6 +242,25 @@ class JsonModelGenerator extends GeneratorForAnnotation { })); } + void generateToString(BuildContext ctx, ClassBuilder clazz) { + clazz.methods.add(Method((b) { + b + ..name = 'toString' + ..returns = refer('String') + ..annotations.add(refer('override')) + ..body = Block((b) { + var buf = StringBuffer('\"${ctx.modelClassName}('); + var i = 0; + for (var field in ctx.fields) { + if (i++ > 0) buf.write(', '); + buf.write('${field.name}=\$${field.name}'); + } + buf.write(')\"'); + b.addExpression(CodeExpression(Code(buf.toString())).returned); + }); + })); + } + void generateEqualsOperator( BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) { clazz.methods.add(new Method((method) { diff --git a/angel_serialize_generator/lib/serialize.dart b/angel_serialize_generator/lib/serialize.dart index 40baa45b..2cc45d00 100644 --- a/angel_serialize_generator/lib/serialize.dart +++ b/angel_serialize_generator/lib/serialize.dart @@ -35,10 +35,53 @@ class SerializerGenerator extends GeneratorForAnnotation { /// Generate a serializer class. void generateClass( List serializers, BuildContext ctx, LibraryBuilder file) { + // Generate canonical codecs, etc. + var pascal = ctx.modelClassNameRecase.pascalCase, + camel = ctx.modelClassNameRecase.camelCase; + + if (ctx.constructorParameters.isEmpty) { + file.body.add(new Code(''' +const ${pascal}Serializer ${camel}Serializer = const ${pascal}Serializer(); + +class ${pascal}Encoder extends Converter<${pascal}, Map> { + const ${pascal}Encoder(); + + @override + Map convert(${pascal} model) => ${pascal}Serializer.toMap(model); +} + +class ${pascal}Decoder extends Converter { + const ${pascal}Decoder(); + + @override + ${pascal} convert(Map map) => ${pascal}Serializer.fromMap(map); +} + ''')); + } + file.body.add(new Class((clazz) { - clazz - ..name = '${ctx.modelClassNameRecase.pascalCase}Serializer' - ..abstract = true; + clazz..name = '${pascal}Serializer'; + if (ctx.constructorParameters.isEmpty) { + clazz + ..extend = TypeReference((b) => b + ..symbol = 'Codec' + ..types.addAll([ctx.modelClassType, refer('Map')])); + + // Add constructor, Codec impl, etc. + clazz.constructors.add(Constructor((b) => b..constant = true)); + clazz.methods.add(Method((b) => b + ..name = 'encoder' + ..type = MethodType.getter + ..annotations.add(refer('override')) + ..body = refer('${pascal}Encoder').constInstance([]).code)); + clazz.methods.add(Method((b) => b + ..name = 'decoder' + ..type = MethodType.getter + ..annotations.add(refer('override')) + ..body = refer('${pascal}Decoder').constInstance([]).code)); + } else { + clazz.abstract = true; + } if (serializers.contains(Serializers.map)) { generateFromMapMethod(clazz, ctx, file); diff --git a/angel_serialize_generator/pubspec.yaml b/angel_serialize_generator/pubspec.yaml index 8bed20d0..0aff1f3c 100644 --- a/angel_serialize_generator/pubspec.yaml +++ b/angel_serialize_generator/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_serialize_generator -version: 2.4.2 +version: 2.4.3 description: Model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling. author: Tobe O homepage: https://github.com/angel-dart/serialize @@ -22,6 +22,6 @@ dev_dependencies: build_runner: ^1.0.0 collection: ^1.0.0 test: ^1.0.0 -# dependency_overrides: -# angel_serialize: -# path: ../angel_serialize \ No newline at end of file +dependency_overrides: + angel_serialize: + path: ../angel_serialize \ No newline at end of file diff --git a/angel_serialize_generator/test/book_test.dart b/angel_serialize_generator/test/book_test.dart index b3b169e8..d4304e4d 100644 --- a/angel_serialize_generator/test/book_test.dart +++ b/angel_serialize_generator/test/book_test.dart @@ -13,7 +13,7 @@ main() { notModels: [1.0, 3.0], updatedAt: new DateTime.now()); var serializedDeathlyHallows = deathlyHallows.toJson(); - print('Deathly Hallows: $serializedDeathlyHallows'); + print('Deathly Hallows: $deathlyHallows'); var jkRowling = new Author( id: '1', @@ -21,13 +21,13 @@ main() { age: 51, books: [deathlyHallows], newestBook: deathlyHallows); - Map serializedJkRowling = AuthorSerializer.toMap(jkRowling); - Map deathlyHallowsMap = BookSerializer.toMap(deathlyHallows); - print('J.K. Rowling: $serializedJkRowling'); + var serializedJkRowling = authorSerializer.encode(jkRowling); + var deathlyHallowsMap = bookSerializer.encode(deathlyHallows); + print('J.K. Rowling: $jkRowling'); var library = new Library(collection: {deathlyHallowsIsbn: deathlyHallows}); var serializedLibrary = LibrarySerializer.toMap(library); - print('Library: $serializedLibrary'); + print('Library: $library'); group('serialization', () { test('serialization sets proper fields', () { @@ -56,7 +56,7 @@ main() { test('heeds canDeserialize', () { var map = new Map.from(serializedJkRowling)..['obscured'] = 'foo'; - var author = AuthorSerializer.fromMap(map); + var author = authorSerializer.decode(map); expect(author.obscured, 'foo'); }); diff --git a/angel_serialize_generator/test/enum_test.dart b/angel_serialize_generator/test/enum_test.dart index 39b14d5d..3a96b366 100644 --- a/angel_serialize_generator/test/enum_test.dart +++ b/angel_serialize_generator/test/enum_test.dart @@ -53,7 +53,7 @@ void main() { imageBytes: new Uint8List.fromList(new List.generate(1000, (i) => i))); var eeMap = ee.toJson(); - print(eeMap); + print(ee); var ef = WithEnumSerializer.fromMap(eeMap); expect(ee.copyWith(), ee); expect(ef, ee); diff --git a/angel_serialize_generator/test/models/book.dart b/angel_serialize_generator/test/models/book.dart index fc09731b..1f0ff5ba 100644 --- a/angel_serialize_generator/test/models/book.dart +++ b/angel_serialize_generator/test/models/book.dart @@ -15,7 +15,10 @@ part 'book.g.dart'; ) abstract class _Book extends Model { String author, title, description; + + /// The number of pages the book has. int pageCount; + List notModels; @SerializableField(alias: 'camelCase', isNullable: true) @@ -35,6 +38,7 @@ abstract class _Author extends Model { List<_Book> get books; + /// The newest book. _Book get newestBook; @SerializableField(exclude: true, isNullable: true) diff --git a/angel_serialize_generator/test/models/book.g.dart b/angel_serialize_generator/test/models/book.g.dart index a5163786..40222169 100644 --- a/angel_serialize_generator/test/models/book.g.dart +++ b/angel_serialize_generator/test/models/book.g.dart @@ -34,6 +34,7 @@ class Book extends _Book { @override final String description; + /// The number of pages the book has. @override final int pageCount; @@ -100,6 +101,11 @@ class Book extends _Book { ]); } + @override + String toString() { + return "Book(id=$id, author=$author, title=$title, description=$description, pageCount=$pageCount, notModels=$notModels, camelCaseString=$camelCaseString, createdAt=$createdAt, updatedAt=$updatedAt)"; + } + Map toJson() { return BookSerializer.toMap(this); } @@ -131,6 +137,7 @@ class Author extends _Author { @override final List<_Book> books; + /// The newest book. @override final _Book newestBook; @@ -197,6 +204,11 @@ class Author extends _Author { ]); } + @override + String toString() { + return "Author(id=$id, name=$name, age=$age, books=$books, newestBook=$newestBook, secret=$secret, obscured=$obscured, createdAt=$createdAt, updatedAt=$updatedAt)"; + } + Map toJson() { return AuthorSerializer.toMap(this); } @@ -248,6 +260,11 @@ class Library extends _Library { return hashObjects([id, collection, createdAt, updatedAt]); } + @override + String toString() { + return "Library(id=$id, collection=$collection, createdAt=$createdAt, updatedAt=$updatedAt)"; + } + Map toJson() { return LibrarySerializer.toMap(this); } @@ -315,6 +332,11 @@ class Bookmark extends _Bookmark { return hashObjects([id, history, page, comment, createdAt, updatedAt]); } + @override + String toString() { + return "Bookmark(id=$id, history=$history, page=$page, comment=$comment, createdAt=$createdAt, updatedAt=$updatedAt)"; + } + Map toJson() { return BookmarkSerializer.toMap(this); } @@ -324,7 +346,29 @@ class Bookmark extends _Bookmark { // SerializerGenerator // ************************************************************************** -abstract class BookSerializer { +const BookSerializer bookSerializer = const BookSerializer(); + +class BookEncoder extends Converter { + const BookEncoder(); + + @override + Map convert(Book model) => BookSerializer.toMap(model); +} + +class BookDecoder extends Converter { + const BookDecoder(); + + @override + Book convert(Map map) => BookSerializer.fromMap(map); +} + +class BookSerializer extends Codec { + const BookSerializer(); + + @override + get encoder => const BookEncoder(); + @override + get decoder => const BookDecoder(); static Book fromMap(Map map) { return new Book( id: map['id'] as String, @@ -398,7 +442,29 @@ abstract class BookFields { static const String updatedAt = 'updated_at'; } -abstract class AuthorSerializer { +const AuthorSerializer authorSerializer = const AuthorSerializer(); + +class AuthorEncoder extends Converter { + const AuthorEncoder(); + + @override + Map convert(Author model) => AuthorSerializer.toMap(model); +} + +class AuthorDecoder extends Converter { + const AuthorDecoder(); + + @override + Author convert(Map map) => AuthorSerializer.fromMap(map); +} + +class AuthorSerializer extends Codec { + const AuthorSerializer(); + + @override + get encoder => const AuthorEncoder(); + @override + get decoder => const AuthorDecoder(); static Author fromMap(Map map) { if (map['name'] == null) { throw new FormatException("Missing required field 'name' on Author."); @@ -490,7 +556,29 @@ abstract class AuthorFields { static const String updatedAt = 'updated_at'; } -abstract class LibrarySerializer { +const LibrarySerializer librarySerializer = const LibrarySerializer(); + +class LibraryEncoder extends Converter { + const LibraryEncoder(); + + @override + Map convert(Library model) => LibrarySerializer.toMap(model); +} + +class LibraryDecoder extends Converter { + const LibraryDecoder(); + + @override + Library convert(Map map) => LibrarySerializer.fromMap(map); +} + +class LibrarySerializer extends Codec { + const LibrarySerializer(); + + @override + get encoder => const LibraryEncoder(); + @override + get decoder => const LibraryDecoder(); static Library fromMap(Map map) { return new Library( id: map['id'] as String, diff --git a/angel_serialize_generator/test/models/game_pad_button.g.dart b/angel_serialize_generator/test/models/game_pad_button.g.dart index 4ebb3e48..4fda54ed 100644 --- a/angel_serialize_generator/test/models/game_pad_button.g.dart +++ b/angel_serialize_generator/test/models/game_pad_button.g.dart @@ -32,6 +32,11 @@ class GamepadButton implements _GamepadButton { return hashObjects([name, radius]); } + @override + String toString() { + return "GamepadButton(name=$name, radius=$radius)"; + } + Map toJson() { return GamepadButtonSerializer.toMap(this); } @@ -72,6 +77,11 @@ class Gamepad extends _Gamepad { return hashObjects([buttons, dynamicMap]); } + @override + String toString() { + return "Gamepad(buttons=$buttons, dynamicMap=$dynamicMap)"; + } + Map toJson() { return GamepadSerializer.toMap(this); } @@ -81,7 +91,30 @@ class Gamepad extends _Gamepad { // SerializerGenerator // ************************************************************************** -abstract class GamepadButtonSerializer { +const GamepadButtonSerializer gamepadButtonSerializer = + const GamepadButtonSerializer(); + +class GamepadButtonEncoder extends Converter { + const GamepadButtonEncoder(); + + @override + Map convert(GamepadButton model) => GamepadButtonSerializer.toMap(model); +} + +class GamepadButtonDecoder extends Converter { + const GamepadButtonDecoder(); + + @override + GamepadButton convert(Map map) => GamepadButtonSerializer.fromMap(map); +} + +class GamepadButtonSerializer extends Codec { + const GamepadButtonSerializer(); + + @override + get encoder => const GamepadButtonEncoder(); + @override + get decoder => const GamepadButtonDecoder(); static GamepadButton fromMap(Map map) { return new GamepadButton( name: map['name'] as String, radius: map['radius'] as int); @@ -103,7 +136,29 @@ abstract class GamepadButtonFields { static const String radius = 'radius'; } -abstract class GamepadSerializer { +const GamepadSerializer gamepadSerializer = const GamepadSerializer(); + +class GamepadEncoder extends Converter { + const GamepadEncoder(); + + @override + Map convert(Gamepad model) => GamepadSerializer.toMap(model); +} + +class GamepadDecoder extends Converter { + const GamepadDecoder(); + + @override + Gamepad convert(Map map) => GamepadSerializer.fromMap(map); +} + +class GamepadSerializer extends Codec { + const GamepadSerializer(); + + @override + get encoder => const GamepadEncoder(); + @override + get decoder => const GamepadDecoder(); static Gamepad fromMap(Map map) { return new Gamepad( buttons: map['buttons'] is Iterable diff --git a/angel_serialize_generator/test/models/goat.g.dart b/angel_serialize_generator/test/models/goat.g.dart index 985261bc..d670c921 100644 --- a/angel_serialize_generator/test/models/goat.g.dart +++ b/angel_serialize_generator/test/models/goat.g.dart @@ -32,6 +32,11 @@ class Goat implements _Goat { return hashObjects([integer, list]); } + @override + String toString() { + return "Goat(integer=$integer, list=$list)"; + } + Map toJson() { return GoatSerializer.toMap(this); } @@ -41,7 +46,29 @@ class Goat implements _Goat { // SerializerGenerator // ************************************************************************** -abstract class GoatSerializer { +const GoatSerializer goatSerializer = const GoatSerializer(); + +class GoatEncoder extends Converter { + const GoatEncoder(); + + @override + Map convert(Goat model) => GoatSerializer.toMap(model); +} + +class GoatDecoder extends Converter { + const GoatDecoder(); + + @override + Goat convert(Map map) => GoatSerializer.fromMap(map); +} + +class GoatSerializer extends Codec { + const GoatSerializer(); + + @override + get encoder => const GoatEncoder(); + @override + get decoder => const GoatDecoder(); static Goat fromMap(Map map) { return new Goat( integer: map['integer'] as int ?? 34, diff --git a/angel_serialize_generator/test/models/has_map.g.dart b/angel_serialize_generator/test/models/has_map.g.dart index 5ed90558..9d3a39d3 100644 --- a/angel_serialize_generator/test/models/has_map.g.dart +++ b/angel_serialize_generator/test/models/has_map.g.dart @@ -29,6 +29,11 @@ class HasMap implements _HasMap { return hashObjects([value]); } + @override + String toString() { + return "HasMap(value=$value)"; + } + Map toJson() { return HasMapSerializer.toMap(this); } @@ -38,7 +43,29 @@ class HasMap implements _HasMap { // SerializerGenerator // ************************************************************************** -abstract class HasMapSerializer { +const HasMapSerializer hasMapSerializer = const HasMapSerializer(); + +class HasMapEncoder extends Converter { + const HasMapEncoder(); + + @override + Map convert(HasMap model) => HasMapSerializer.toMap(model); +} + +class HasMapDecoder extends Converter { + const HasMapDecoder(); + + @override + HasMap convert(Map map) => HasMapSerializer.fromMap(map); +} + +class HasMapSerializer extends Codec { + const HasMapSerializer(); + + @override + get encoder => const HasMapEncoder(); + @override + get decoder => const HasMapDecoder(); static HasMap fromMap(Map map) { if (map['value'] == null) { throw new FormatException("Missing required field 'value' on HasMap."); diff --git a/angel_serialize_generator/test/models/with_enum.g.dart b/angel_serialize_generator/test/models/with_enum.g.dart index b522fb37..e745d66e 100644 --- a/angel_serialize_generator/test/models/with_enum.g.dart +++ b/angel_serialize_generator/test/models/with_enum.g.dart @@ -41,6 +41,11 @@ class WithEnum implements _WithEnum { return hashObjects([type, finalList, imageBytes]); } + @override + String toString() { + return "WithEnum(type=$type, finalList=$finalList, imageBytes=$imageBytes)"; + } + Map toJson() { return WithEnumSerializer.toMap(this); } @@ -50,7 +55,29 @@ class WithEnum implements _WithEnum { // SerializerGenerator // ************************************************************************** -abstract class WithEnumSerializer { +const WithEnumSerializer withEnumSerializer = const WithEnumSerializer(); + +class WithEnumEncoder extends Converter { + const WithEnumEncoder(); + + @override + Map convert(WithEnum model) => WithEnumSerializer.toMap(model); +} + +class WithEnumDecoder extends Converter { + const WithEnumDecoder(); + + @override + WithEnum convert(Map map) => WithEnumSerializer.fromMap(map); +} + +class WithEnumSerializer extends Codec { + const WithEnumSerializer(); + + @override + get encoder => const WithEnumEncoder(); + @override + get decoder => const WithEnumDecoder(); static WithEnum fromMap(Map map) { return new WithEnum( type: map['type'] is WithEnumType