This commit is contained in:
Tobe O 2019-03-28 21:10:16 -04:00
parent edd19d3d12
commit d575b0f75e
9 changed files with 130 additions and 52 deletions

1
CHANGELOG.md Normal file
View file

@ -0,0 +1 @@
# 1.0.0

View file

@ -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<String, Todo>(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}');
}
```

4
analysis_options.yaml Normal file
View file

@ -0,0 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

5
example/README.md Normal file
View file

@ -0,0 +1,5 @@
Command:
```bash
curl -X POST -d '@example/data.json' -H 'content-type: application/json' http://localhost:3000/api/todos; echo
```

1
example/data.json Normal file
View file

@ -0,0 +1 @@
{ "id": 3, "completed": false, "text": "clean your room" }

34
example/main.dart Normal file
View file

@ -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<String, Todo>(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);
}

View file

@ -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<T> extends Service {
final Service inner;
/// An Angel service that uses reflection to (de)serialize Dart objects.
class TypedService<Id, T> extends Service<Id, dynamic> {
/// The inner service.
final Service<Id, Map> 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<T> 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<T> 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<List<T>> index([Map<String, dynamic> params]) =>
inner.index(params).then((it) => it.map(deserialize).toList());
@override
Future create(data, [Map params]) =>
Future<T> create(data, [Map<String, dynamic> params]) =>
inner.create(serialize(data), params).then(deserialize);
@override
Future read(id, [Map params]) => inner.read(id, params).then(deserialize);
Future<T> read(Id id, [Map<String, dynamic> params]) =>
inner.read(id, params).then(deserialize);
@override
Future modify(id, data, [Map params]) =>
Future<T> modify(Id id, data, [Map<String, dynamic> params]) =>
inner.modify(id, serialize(data), params).then(deserialize);
@override
Future update(id, data, [Map params]) =>
Future<T> update(Id id, data, [Map<String, dynamic> params]) =>
inner.update(id, serialize(data), params).then(deserialize);
@override
Future remove(id, [Map params]) => inner.remove(id, params).then(deserialize);
Future<T> remove(Id id, [Map<String, dynamic> params]) =>
inner.remove(id, params).then(deserialize);
}

14
pubspec.yaml Normal file
View file

@ -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 <thosakwe@gmail.com>
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

View file

@ -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<Todo>(new MapService());
var svc = TypedService<String, Todo>(MapService());
test('force model', () {
expect(() => new TypedService<String>(null), throwsException);
expect(() => TypedService<String, int>(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<Todo>());
var t = m as Todo;
expect(t.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch);
expect(m, const TypeMatcher<Todo>());
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<Todo>());
var t = m as Todo;
expect(t.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch);
expect(m, const TypeMatcher<Todo>());
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);
}