Force unmodifiable collections

This commit is contained in:
Tobe O 2018-05-15 15:01:13 -04:00
parent d4a58cfcef
commit 46d4c24f69
8 changed files with 64 additions and 28 deletions

View file

@ -53,6 +53,7 @@ library angel_serialize.test.models.book;
import 'package:angel_framework/common.dart'; import 'package:angel_framework/common.dart';
import 'package:angel_serialize/angel_serialize.dart'; import 'package:angel_serialize/angel_serialize.dart';
import 'package:collection/collection.dart';
part 'book.g.dart'; part 'book.g.dart';
@serializable @serializable

View file

@ -1,3 +1,6 @@
# 2.0.7
* Create unmodifiable Lists and Maps.
# 2.0.6 # 2.0.6
* Support for using `abstract` to create immutable model classes. * Support for using `abstract` to create immutable model classes.
* Add support for custom constructor parameters. * Add support for custom constructor parameters.

View file

@ -60,16 +60,21 @@ bool isModelClass(DartType t) {
} }
} }
bool isListOrMapType(DartType t) {
return const TypeChecker.fromRuntime(List).isAssignableFromType(t) ||
const TypeChecker.fromRuntime(Map).isAssignableFromType(t);
}
/// Determines if a [DartType] is a `List` with the first type argument being a `Model`. /// Determines if a [DartType] is a `List` with the first type argument being a `Model`.
bool isListModelType(InterfaceType t) { bool isListModelType(InterfaceType t) {
return t.name == 'List' && return const TypeChecker.fromRuntime(List).isAssignableFromType(t) &&
t.typeArguments.length == 1 && t.typeArguments.length == 1 &&
isModelClass(t.typeArguments[0]); isModelClass(t.typeArguments[0]);
} }
/// Determines if a [DartType] is a `Map` with the second type argument being a `Model`. /// Determines if a [DartType] is a `Map` with the second type argument being a `Model`.
bool isMapToModelType(InterfaceType t) { bool isMapToModelType(InterfaceType t) {
return t.name == 'Map' && return const TypeChecker.fromRuntime(Map).isAssignableFromType(t) &&
t.typeArguments.length == 2 && t.typeArguments.length == 2 &&
isModelClass(t.typeArguments[1]); isModelClass(t.typeArguments[1]);
} }

View file

@ -63,23 +63,43 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) { BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) {
clazz.constructors.add(new Constructor((constructor) { clazz.constructors.add(new Constructor((constructor) {
// Add all `super` params // Add all `super` params
if (ctx.constructorParameters.isNotEmpty) {
for (var param in ctx.constructorParameters) {
constructor.requiredParameters.add(new Parameter((b) => b
..name = param.name
..type = convertTypeReference(param.type)));
}
constructor.initializers.add(new Code( for (var param in ctx.constructorParameters) {
'super(${ctx.constructorParameters.map((p) => p.name).join(',')})')); constructor.requiredParameters.add(new Parameter((b) => b
..name = param.name
..type = convertTypeReference(param.type)));
}
for (var field in ctx.fields) {
if (isListOrMapType(field.type)) {
String typeName = const TypeChecker.fromRuntime(List)
.isAssignableFromType(field.type)
? 'List'
: 'Map';
var defaultValue = typeName == 'List' ? '[]' : '{}';
constructor.initializers.add(new Code('''
this.${field.name} =
new $typeName.unmodifiable(${field.name} ?? $defaultValue)'''));
}
}
if (ctx.constructorParameters.isNotEmpty) {
constructor.initializers.add(
new Code('super(${ctx.constructorParameters.map((p) => p.name).join(
',')})'));
} }
for (var field in ctx.fields) { for (var field in ctx.fields) {
constructor.optionalParameters.add(new Parameter((b) { constructor.optionalParameters.add(new Parameter((b) {
b b
..name = field.name ..name = field.name
..named = true ..named = true;
..toThis = true;
if (!isListOrMapType(field.type))
b.toThis = true;
else {
b.type = convertTypeReference(field.type);
}
})); }));
} }
})); }));
@ -141,9 +161,11 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
if (it.typeParameters.length == 2) { if (it.typeParameters.length == 2) {
var keq = generateEquality(it.typeArguments[0]), var keq = generateEquality(it.typeArguments[0]),
veq = generateEquality(it.typeArguments[1]); veq = generateEquality(it.typeArguments[1]);
return 'const MapEquality<${it.typeArguments[0].name}, ${it.typeArguments[1].name}>(keys: $keq, values: $veq)'; return 'const MapEquality<${it.typeArguments[0].name}, ${it
.typeArguments[1].name}>(keys: $keq, values: $veq)';
} else } else
return 'const MapEquality()<${it.typeArguments[0].name}, ${it.typeArguments[1].name}>'; return 'const MapEquality()<${it.typeArguments[0].name}, ${it
.typeArguments[1].name}>';
} }
return nullable ? null : 'const DefaultEquality<${type.name}>()'; return nullable ? null : 'const DefaultEquality<${type.name}>()';

View file

@ -169,17 +169,17 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
if (isListModelType(t)) { if (isListModelType(t)) {
var rc = new ReCase(t.typeArguments[0].name); var rc = new ReCase(t.typeArguments[0].name);
deserializedRepresentation = "map['$alias'] is Iterable" deserializedRepresentation = "map['$alias'] is Iterable"
" ? map['$alias'].map(${rc " ? new List.unmodifiable(map['$alias'].map(${rc
.pascalCase}Serializer.fromMap).toList()" .pascalCase}Serializer.fromMap))"
" : null"; " : null";
} else if (isMapToModelType(t)) { } else if (isMapToModelType(t)) {
var rc = new ReCase(t.typeArguments[1].name); var rc = new ReCase(t.typeArguments[1].name);
deserializedRepresentation = ''' deserializedRepresentation = '''
map['$alias'] is Map map['$alias'] is Map
? map['$alias'].keys.fold({}, (out, key) { ? new Map.unmodifiable(map['$alias'].keys.fold({}, (out, key) {
return out..[key] = ${rc return out..[key] = ${rc
.pascalCase}Serializer.fromMap(map['$alias'][key]); .pascalCase}Serializer.fromMap(map['$alias'][key]);
}) }))
: null : null
'''; ''';
} }

View file

@ -11,12 +11,13 @@ class Author extends _Author {
{this.id, {this.id,
this.name, this.name,
this.age, this.age,
this.books, List<Book> books,
this.newestBook, this.newestBook,
this.secret, this.secret,
this.obscured, this.obscured,
this.createdAt, this.createdAt,
this.updatedAt}); this.updatedAt})
: this.books = new List.unmodifiable(books ?? []);
@override @override
final String id; final String id;
@ -87,7 +88,9 @@ class Author extends _Author {
} }
class Library extends _Library { class Library extends _Library {
Library({this.id, this.collection, this.createdAt, this.updatedAt}); Library(
{this.id, Map<String, Book> collection, this.createdAt, this.updatedAt})
: this.collection = new Map.unmodifiable(collection ?? {});
@override @override
final String id; final String id;
@ -132,12 +135,13 @@ class Library extends _Library {
class Bookmark extends _Bookmark { class Bookmark extends _Bookmark {
Bookmark(Book book, Bookmark(Book book,
{this.id, {this.id,
this.history, List<int> history,
this.page, this.page,
this.comment, this.comment,
this.createdAt, this.createdAt,
this.updatedAt}) this.updatedAt})
: super(book); : this.history = new List.unmodifiable(history ?? []),
super(book);
@override @override
final String id; final String id;

View file

@ -13,7 +13,7 @@ abstract class AuthorSerializer {
name: map['name'], name: map['name'],
age: map['age'], age: map['age'],
books: map['books'] is Iterable books: map['books'] is Iterable
? map['books'].map(BookSerializer.fromMap).toList() ? new List.unmodifiable(map['books'].map(BookSerializer.fromMap))
: null, : null,
newestBook: map['newest_book'] != null newestBook: map['newest_book'] != null
? BookSerializer.fromMap(map['newest_book']) ? BookSerializer.fromMap(map['newest_book'])
@ -69,10 +69,10 @@ abstract class LibrarySerializer {
return new Library( return new Library(
id: map['id'], id: map['id'],
collection: map['collection'] is Map collection: map['collection'] is Map
? map['collection'].keys.fold({}, (out, key) { ? new Map.unmodifiable(map['collection'].keys.fold({}, (out, key) {
return out return out
..[key] = BookSerializer.fromMap(map['collection'][key]); ..[key] = BookSerializer.fromMap(map['collection'][key]);
}) }))
: null, : null,
createdAt: map['created_at'] != null createdAt: map['created_at'] != null
? (map['created_at'] is DateTime ? (map['created_at'] is DateTime

View file

@ -13,10 +13,11 @@ class Book extends _Book {
this.title, this.title,
this.description, this.description,
this.pageCount, this.pageCount,
this.notModels, List<double> notModels,
this.camelCaseString, this.camelCaseString,
this.createdAt, this.createdAt,
this.updatedAt}); this.updatedAt})
: this.notModels = new List.unmodifiable(notModels ?? []);
@override @override
final String id; final String id;