1.0.0
This commit is contained in:
parent
edd19d3d12
commit
d575b0f75e
9 changed files with 130 additions and 52 deletions
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
|
@ -0,0 +1 @@
|
|||
# 1.0.0
|
27
README.md
27
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<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
4
analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
5
example/README.md
Normal file
5
example/README.md
Normal 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
1
example/data.json
Normal file
|
@ -0,0 +1 @@
|
|||
{ "id": 3, "completed": false, "text": "clean your room" }
|
34
example/main.dart
Normal file
34
example/main.dart
Normal 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);
|
||||
}
|
|
@ -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
14
pubspec.yaml
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue