Add 'packages/typed_service/' from commit '3324193e15e82fe8ec049a92b0c1011690d0a38f'
git-subtree-dir: packages/typed_service git-subtree-mainline:106535751b
git-subtree-split:3324193e15
This commit is contained in:
commit
ae0afd3408
11 changed files with 314 additions and 0 deletions
13
packages/typed_service/.gitignore
vendored
Normal file
13
packages/typed_service/.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
8
packages/typed_service/CHANGELOG.md
Normal file
8
packages/typed_service/CHANGELOG.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# 1.0.1
|
||||
* Explicitly extend `Service<Id, T>`.
|
||||
* Override `readData`.
|
||||
* Use `Service<Id, Map<String, dynamic>>` for `inner`, instead of just
|
||||
`Service<Id, Map>`.
|
||||
|
||||
# 1.0.0
|
||||
* Initial version.
|
21
packages/typed_service/LICENSE
Normal file
21
packages/typed_service/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
29
packages/typed_service/README.md
Normal file
29
packages/typed_service/README.md
Normal file
|
@ -0,0 +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
packages/typed_service/analysis_options.yaml
Normal file
4
packages/typed_service/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
5
packages/typed_service/example/README.md
Normal file
5
packages/typed_service/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
|
||||
```
|
22
packages/typed_service/example/data.json
Normal file
22
packages/typed_service/example/data.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"id": "0",
|
||||
"createdAt": null,
|
||||
"updatedAt": null,
|
||||
"idAsInt": null,
|
||||
"text": "Yes",
|
||||
"completed": false,
|
||||
"created_at": "2019-04-26T09:51:27.494884",
|
||||
"updated_at": "2019-04-26T09:51:27.494884"
|
||||
},
|
||||
{
|
||||
"id": "1",
|
||||
"createdAt": null,
|
||||
"updatedAt": null,
|
||||
"idAsInt": null,
|
||||
"text": "nOPE",
|
||||
"completed": false,
|
||||
"created_at": "2019-04-26T09:51:37.847741",
|
||||
"updated_at": "2019-04-26T09:51:37.847741"
|
||||
}
|
||||
]
|
42
packages/typed_service/example/main.dart
Normal file
42
packages/typed_service/example/main.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_file_service/angel_file_service.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_typed_service/angel_typed_service.dart';
|
||||
import 'package:file/local.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 fs = LocalFileSystem();
|
||||
var exampleDir = fs.file(Platform.script).parent;
|
||||
var dataJson = exampleDir.childFile('data.json');
|
||||
var service = TypedService<String, Todo>(JsonFileService(dataJson));
|
||||
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);
|
||||
}
|
97
packages/typed_service/lib/angel_typed_service.dart
Normal file
97
packages/typed_service/lib/angel_typed_service.dart
Normal file
|
@ -0,0 +1,97 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
|
||||
/// An Angel service that uses reflection to (de)serialize Dart objects.
|
||||
class TypedService<Id, T> extends Service<Id, T> {
|
||||
/// The inner service.
|
||||
final Service<Id, Map<String, dynamic>> inner;
|
||||
|
||||
TypedService(this.inner) : super() {
|
||||
if (!reflectType(T).isAssignableTo(reflectType(Model)))
|
||||
throw Exception(
|
||||
"If you specify a type for TypedService, it must extend Model.");
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<T> Function(RequestContext, ResponseContext) get readData =>
|
||||
_readData;
|
||||
|
||||
T _readData(RequestContext req, ResponseContext res) =>
|
||||
deserialize(req.bodyAsMap);
|
||||
|
||||
/// Attempts to deserialize [x] into an instance of [T].
|
||||
T deserialize(x) {
|
||||
// print('DESERIALIZE: $x (${x.runtimeType})');
|
||||
if (x is T)
|
||||
return x;
|
||||
else if (x is Map) {
|
||||
Map data = x.keys.fold({}, (map, key) {
|
||||
var value = x[key];
|
||||
|
||||
if ((key == 'createdAt' ||
|
||||
key == 'updatedAt' ||
|
||||
key == 'created_at' ||
|
||||
key == 'updated_at') &&
|
||||
value is String) {
|
||||
return map..[key] = DateTime.parse(value);
|
||||
} else {
|
||||
return map..[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
var result = god.deserializeDatum(data, outputType: T);
|
||||
|
||||
if (data['createdAt'] is DateTime) {
|
||||
result.createdAt = data['createdAt'] as DateTime;
|
||||
} else if (data['created_at'] is DateTime) {
|
||||
result.createdAt = data['created_at'] as DateTime;
|
||||
}
|
||||
|
||||
if (data['updatedAt'] is DateTime) {
|
||||
result.updatedAt = data['updatedAt'] as DateTime;
|
||||
} else if (data['updated_at'] is DateTime) {
|
||||
result.updatedAt = data['updated_at'] as DateTime;
|
||||
}
|
||||
|
||||
// print('x: $x\nresult: $result');
|
||||
return result as T;
|
||||
} else
|
||||
throw ArgumentError('Cannot convert $x to $T');
|
||||
}
|
||||
|
||||
/// Serializes [x] into a [Map].
|
||||
Map<String, dynamic> serialize(x) {
|
||||
if (x is Model)
|
||||
return (god.serializeObject(x) as Map).cast<String, dynamic>();
|
||||
else if (x is Map)
|
||||
return x.cast<String, dynamic>();
|
||||
else
|
||||
throw ArgumentError('Cannot serialize ${x.runtimeType}');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<T>> index([Map<String, dynamic> params]) =>
|
||||
inner.index(params).then((it) => it.map(deserialize).toList());
|
||||
|
||||
@override
|
||||
Future<T> create(data, [Map<String, dynamic> params]) =>
|
||||
inner.create(serialize(data), params).then(deserialize);
|
||||
|
||||
@override
|
||||
Future<T> read(Id id, [Map<String, dynamic> params]) =>
|
||||
inner.read(id, params).then(deserialize);
|
||||
|
||||
@override
|
||||
Future<T> modify(Id id, T data, [Map<String, dynamic> params]) =>
|
||||
inner.modify(id, serialize(data), params).then(deserialize);
|
||||
|
||||
@override
|
||||
Future<T> update(Id id, T data, [Map<String, dynamic> params]) =>
|
||||
inner.update(id, serialize(data), params).then(deserialize);
|
||||
|
||||
@override
|
||||
Future<T> remove(Id id, [Map<String, dynamic> params]) =>
|
||||
inner.remove(id, params).then(deserialize);
|
||||
}
|
16
packages/typed_service/pubspec.yaml
Normal file
16
packages/typed_service/pubspec.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: angel_typed_service
|
||||
version: 1.0.1
|
||||
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:
|
||||
angel_file_service: ^2.0.0
|
||||
file: ^5.0.0
|
||||
logging: ^0.11.0
|
||||
pedantic: ^1.0.0
|
||||
test: ^1.0.0
|
57
packages/typed_service/test/typed_service_test.dart
Normal file
57
packages/typed_service/test/typed_service_test.dart
Normal file
|
@ -0,0 +1,57 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_typed_service/angel_typed_service.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
var svc = TypedService<String, Todo>(MapService());
|
||||
|
||||
test('force model', () {
|
||||
expect(() => TypedService<String, int>(MapService()), throwsException);
|
||||
});
|
||||
|
||||
test('serialize', () {
|
||||
expect(svc.serialize({'foo': 'bar'}), {'foo': 'bar'});
|
||||
expect(() => svc.serialize(2), throwsArgumentError);
|
||||
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')..remove('idAsInt'), {
|
||||
'id': '3',
|
||||
'createdAt': now.toIso8601String(),
|
||||
'updatedAt': now.toIso8601String(),
|
||||
'text': 'a',
|
||||
'completed': false
|
||||
});
|
||||
});
|
||||
|
||||
test('deserialize date', () {
|
||||
var now = DateTime.now();
|
||||
var m = svc.deserialize({
|
||||
'createdAt': now.toIso8601String(),
|
||||
'updatedAt': now.toIso8601String()
|
||||
});
|
||||
expect(m, const TypeMatcher<Todo>());
|
||||
expect(m.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch);
|
||||
});
|
||||
|
||||
test('deserialize date w/ underscore', () {
|
||||
var now = DateTime.now();
|
||||
var m = svc.deserialize({
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String()
|
||||
});
|
||||
expect(m, const TypeMatcher<Todo>());
|
||||
expect(m.createdAt.millisecondsSinceEpoch, now.millisecondsSinceEpoch);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
Loading…
Reference in a new issue