From d575b0f75e7333a98d9bceb667f82db8de30c335 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 28 Mar 2019 21:10:16 -0400 Subject: [PATCH] 1.0.0 --- CHANGELOG.md | 1 + README.md | 27 ++++++++++++++++++ analysis_options.yaml | 4 +++ example/README.md | 5 ++++ example/data.json | 1 + example/main.dart | 34 ++++++++++++++++++++++ lib/angel_typed_service.dart | 55 ++++++++++++++++++------------------ pubspec.yaml | 14 +++++++++ test/typed_service_test.dart | 41 +++++++++++---------------- 9 files changed, 130 insertions(+), 52 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 analysis_options.yaml create mode 100644 example/README.md create mode 100644 example/data.json create mode 100644 example/main.dart create mode 100644 pubspec.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f96ab092 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# 1.0.0 \ No newline at end of file diff --git a/README.md b/README.md index e0876220..87220849 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ # typed_service Angel services that use reflection (via mirrors or codegen) to (de)serialize PODO's. +Useful for quick prototypes. + +Typically, [`package:angel_serialize`](https://github.com/angel-dart/serialize) +is recommended. + +## Brief Example +```dart +main() async { + var app = Angel(); + var http = AngelHttp(app); + var service = TypedService(MapService()); + hierarchicalLoggingEnabled = true; + app.use('/api/todos', service); + + app + ..serializer = god.serialize + ..logger = Logger.detached('typed_service') + ..logger.onRecord.listen((rec) { + print(rec); + if (rec.error != null) print(rec.error); + if (rec.stackTrace != null) print(rec.stackTrace); + }); + + await http.startServer('127.0.0.1', 3000); + print('Listening at ${http.uri}'); +} +``` \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..c230cee7 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:pedantic/analysis_options.yaml +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..4e35b7db --- /dev/null +++ b/example/README.md @@ -0,0 +1,5 @@ +Command: + +```bash +curl -X POST -d '@example/data.json' -H 'content-type: application/json' http://localhost:3000/api/todos; echo +``` \ No newline at end of file diff --git a/example/data.json b/example/data.json new file mode 100644 index 00000000..f1312318 --- /dev/null +++ b/example/data.json @@ -0,0 +1 @@ +{ "id": 3, "completed": false, "text": "clean your room" } diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 00000000..e12491ca --- /dev/null +++ b/example/main.dart @@ -0,0 +1,34 @@ +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; +import 'package:angel_typed_service/angel_typed_service.dart'; +import 'package:json_god/json_god.dart' as god; +import 'package:logging/logging.dart'; + +main() async { + var app = Angel(); + var http = AngelHttp(app); + var service = TypedService(MapService()); + hierarchicalLoggingEnabled = true; + app.use('/api/todos', service); + + app + ..serializer = god.serialize + ..logger = Logger.detached('typed_service') + ..logger.onRecord.listen((rec) { + print(rec); + if (rec.error != null) print(rec.error); + if (rec.stackTrace != null) print(rec.stackTrace); + }); + + await http.startServer('127.0.0.1', 3000); + print('Listening at ${http.uri}'); +} + +class Todo extends Model { + String text; + bool completed; + @override + DateTime createdAt, updatedAt; + Todo({String id, this.text, this.completed, this.createdAt, this.updatedAt}) + : super(id: id); +} diff --git a/lib/angel_typed_service.dart b/lib/angel_typed_service.dart index 208fec29..46e59657 100644 --- a/lib/angel_typed_service.dart +++ b/lib/angel_typed_service.dart @@ -1,23 +1,24 @@ import 'dart:async'; import 'dart:mirrors'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:json_god/json_god.dart' as god; -import 'service.dart'; - -class TypedService extends Service { - final Service inner; +/// An Angel service that uses reflection to (de)serialize Dart objects. +class TypedService extends Service { + /// The inner service. + final Service inner; TypedService(this.inner) : super() { if (!reflectType(T).isAssignableTo(reflectType(Model))) - throw new Exception( + throw Exception( "If you specify a type for TypedService, it must extend Model."); } - deserialize(x) { + /// Attempts to deserialize [x] into an instance of [T]. + T deserialize(x) { // print('DESERIALIZE: $x (${x.runtimeType})'); - if (x is Type || x is T) + if (x is T) return x; - else if (x is Iterable) - return x.map(deserialize).toList(); else if (x is Map) { Map data = x.keys.fold({}, (map, key) { var value = x[key]; @@ -33,7 +34,7 @@ class TypedService extends Service { } }); - Model result = god.deserializeDatum(data, outputType: T); + var result = god.deserializeDatum(data, outputType: T); if (data['createdAt'] is DateTime) { result.createdAt = data['createdAt'] as DateTime; @@ -48,42 +49,42 @@ class TypedService extends Service { } // print('x: $x\nresult: $result'); - return result; + return result as T; } else - return x; + throw ArgumentError('Cannot convert $x to $T'); } - serialize(x) { - // TODO: Custom serializer, i.e. json_god? -// if (x is Model) -// return json.encodeObject(x); -// else - if (x is Map) + /// Serializes [x] into a [Map]. + Map serialize(x) { + if (x is Model) + return god.serializeObject(x) as Map; + else if (x is Map) return x; - else if (x is Iterable) - return x.map(serialize).toList(); else - throw new ArgumentError('Cannot serialize ${x.runtimeType}'); + throw ArgumentError('Cannot serialize ${x.runtimeType}'); } @override - Future index([Map params]) => inner.index(params).then(deserialize); + Future> index([Map params]) => + inner.index(params).then((it) => it.map(deserialize).toList()); @override - Future create(data, [Map params]) => + Future create(data, [Map params]) => inner.create(serialize(data), params).then(deserialize); @override - Future read(id, [Map params]) => inner.read(id, params).then(deserialize); + Future read(Id id, [Map params]) => + inner.read(id, params).then(deserialize); @override - Future modify(id, data, [Map params]) => + Future modify(Id id, data, [Map params]) => inner.modify(id, serialize(data), params).then(deserialize); @override - Future update(id, data, [Map params]) => + Future update(Id id, data, [Map params]) => inner.update(id, serialize(data), params).then(deserialize); @override - Future remove(id, [Map params]) => inner.remove(id, params).then(deserialize); + Future remove(Id id, [Map params]) => + inner.remove(id, params).then(deserialize); } diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..3eb3f83f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: angel_typed_service +version: 1.0.0 +description: Angel services that use reflection to (de)serialize Dart objects. +homepage: https://github.com/angel-dart/typed_service +author: Tobe O +environment: + sdk: ">=2.0.0-dev <3.0.0" +dependencies: + angel_framework: ^2.0.0-alpha + json_god: ^2.0.0-beta +dev_dependencies: + logging: ^0.11.0 + pedantic: ^1.0.0 + test: ^1.0.0 \ No newline at end of file diff --git a/test/typed_service_test.dart b/test/typed_service_test.dart index b5b3a3c9..41703958 100644 --- a/test/typed_service_test.dart +++ b/test/typed_service_test.dart @@ -1,32 +1,24 @@ import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/common.dart'; +import 'package:angel_typed_service/angel_typed_service.dart'; import 'package:test/test.dart'; -import 'common.dart'; main() { - var svc = new TypedService(new MapService()); + var svc = TypedService(MapService()); test('force model', () { - expect(() => new TypedService(null), throwsException); + expect(() => TypedService(MapService()), throwsException); }); test('serialize', () { expect(svc.serialize({'foo': 'bar'}), {'foo': 'bar'}); - expect( - svc.serialize([ - {'foo': 'bar'} - ]), - [ - {'foo': 'bar'} - ]); expect(() => svc.serialize(2), throwsArgumentError); - var now = new DateTime.now(); - var t = - new Todo(text: 'a', completed: false, createdAt: now, updatedAt: now); + var now = DateTime.now(); + var t = Todo( + id: '3', text: 'a', completed: false, createdAt: now, updatedAt: now); var m = svc.serialize(t); print(m); - expect(m..remove('_identityHashCode'), { - 'id': null, + expect(m..remove('_identityHashCode')..remove('idAsInt'), { + 'id': '3', 'createdAt': now.toIso8601String(), 'updatedAt': now.toIso8601String(), 'text': 'a', @@ -35,25 +27,23 @@ main() { }); test('deserialize date', () { - var now = new DateTime.now(); + var now = DateTime.now(); var m = svc.deserialize({ 'createdAt': now.toIso8601String(), 'updatedAt': now.toIso8601String() }); - expect(m, const IsInstanceOf()); - var t = m as Todo; - expect(t.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch); + expect(m, const TypeMatcher()); + expect(m.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch); }); test('deserialize date w/ underscore', () { - var now = new DateTime.now(); + var now = DateTime.now(); var m = svc.deserialize({ 'created_at': now.toIso8601String(), 'updated_at': now.toIso8601String() }); - expect(m, const IsInstanceOf()); - var t = m as Todo; - expect(t.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch); + expect(m, const TypeMatcher()); + expect(m.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch); }); } @@ -62,5 +52,6 @@ class Todo extends Model { bool completed; @override DateTime createdAt, updatedAt; - Todo({this.text, this.completed, this.createdAt, this.updatedAt}); + Todo({String id, this.text, this.completed, this.createdAt, this.updatedAt}) + : super(id: id); }