This commit is contained in:
thosakwe 2017-02-01 16:43:18 -05:00
parent 0696705809
commit 6eebc7afa8
8 changed files with 127 additions and 32 deletions

View file

@ -1,6 +1,6 @@
# angel_framework # angel_framework
[![pub 1.0.0-dev.53](https://img.shields.io/badge/pub-1.0.0--dev.53-red.svg)](https://pub.dartlang.org/packages/angel_framework) [![pub 1.0.0-dev.54](https://img.shields.io/badge/pub-1.0.0--dev.54-red.svg)](https://pub.dartlang.org/packages/angel_framework)
[![build status](https://travis-ci.org/angel-dart/framework.svg)](https://travis-ci.org/angel-dart/framework) [![build status](https://travis-ci.org/angel-dart/framework.svg)](https://travis-ci.org/angel-dart/framework)
Core libraries for the Angel Framework. Core libraries for the Angel Framework.

View file

@ -0,0 +1,50 @@
import 'dart:async';
import 'service.dart';
/// An easy helper class to create one-off services without having to create an entire class.
///
/// Well-suited for testing.
class AnonymousService extends Service {
Function _index, _read, _create, _modify, _update, _remove;
AnonymousService(
{Future<List> index([Map params]),
Future read(id, [Map params]),
Future create(data, [Map params]),
Future modify(id, data, [Map params]),
Future update(id, data, [Map params]),
Future remove(id, [Map params])})
: super() {
_index = index;
_read = read;
_create = create;
_modify = modify;
_update = update;
_remove = remove;
}
@override
index([Map params]) => _index != null ? _index(params) : super.index(params);
@override
read(id, [Map params]) =>
_read != null ? _read(id, params) : super.read(id, params);
@override
create(data, [Map params]) =>
_create != null ? _create(data, params) : super.create(data, params);
@override
modify(id, data, [Map params]) => _modify != null
? _modify(id, data, params)
: super.modify(id, data, params);
@override
update(id, data, [Map params]) => _update != null
? _update(id, data, params)
: super.update(id, data, params);
@override
remove(id, [Map params]) =>
_remove != null ? _remove(id, params) : super.remove(id, params);
}

View file

@ -138,7 +138,7 @@ class HookedService extends Service {
get( get(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])), .read(toId(req.params['id']), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -146,8 +146,8 @@ class HookedService extends Service {
Middleware modifyMiddleware = getAnnotation(inner.modify, Middleware); Middleware modifyMiddleware = getAnnotation(inner.modify, Middleware);
patch( patch(
'/:id', '/:id',
(req, res) async => await this.modify( (req, res) async => await this.modify(toId(req.params['id']), req.body,
req.params['id'], req.body, mergeMap([req.query, restProvider])), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -156,8 +156,8 @@ class HookedService extends Service {
Middleware updateMiddleware = getAnnotation(inner.update, Middleware); Middleware updateMiddleware = getAnnotation(inner.update, Middleware);
post( post(
'/:id', '/:id',
(req, res) async => await this.update( (req, res) async => await this.update(toId(req.params['id']), req.body,
req.params['id'], req.body, mergeMap([req.query, restProvider])), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -166,8 +166,8 @@ class HookedService extends Service {
Middleware removeMiddleware = getAnnotation(inner.remove, Middleware); Middleware removeMiddleware = getAnnotation(inner.remove, Middleware);
delete( delete(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this.remove(
.remove(req.params['id'], mergeMap([req.query, restProvider])), toId(req.params['id']), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(

View file

@ -4,6 +4,7 @@ library angel_framework.http;
export 'package:angel_route/angel_route.dart'; export 'package:angel_route/angel_route.dart';
export 'angel_base.dart'; export 'angel_base.dart';
export 'angel_http_exception.dart'; export 'angel_http_exception.dart';
export 'anonymous_service.dart';
export 'base_middleware.dart'; export 'base_middleware.dart';
export 'base_plugin.dart'; export 'base_plugin.dart';
export 'controller.dart'; export 'controller.dart';

View file

@ -8,11 +8,25 @@ import '../defs.dart';
import 'angel_http_exception.dart'; import 'angel_http_exception.dart';
import 'service.dart'; import 'service.dart';
int _getId(id) {
try {
return int.parse(id.toString());
} catch (e) {
throw new AngelHttpException.badRequest(message: 'Invalid ID.');
}
}
/// An in-memory [Service]. /// An in-memory [Service].
class MemoryService<T> extends Service { class MemoryService<T> extends Service {
Map <int, MemoryModel> items = {}; /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
///
/// `false` by default.
final bool allowRemoveAll;
MemoryService() :super() { //// The data contained in this service.
final Map<int, MemoryModel> items = {};
MemoryService({this.allowRemoveAll: false}) : super() {
if (!reflectType(T).isAssignableTo(reflectType(MemoryModel))) { if (!reflectType(T).isAssignableTo(reflectType(MemoryModel))) {
throw new Exception( throw new Exception(
"MemoryServices only support classes that inherit from MemoryModel."); "MemoryServices only support classes that inherit from MemoryModel.");
@ -31,19 +45,22 @@ class MemoryService<T> extends Service {
} }
Future read(id, [Map params]) async { Future read(id, [Map params]) async {
int desiredId = int.parse(id.toString()); int desiredId = _getId(id);
if (items.containsKey(desiredId)) { if (items.containsKey(desiredId)) {
MemoryModel found = items[desiredId]; MemoryModel found = items[desiredId];
if (found != null) { if (found != null) {
return _makeJson(desiredId, found); return _makeJson(desiredId, found);
} else throw new AngelHttpException.notFound(); } else
} else throw new AngelHttpException.notFound(); throw new AngelHttpException.notFound();
} else
throw new AngelHttpException.notFound();
} }
Future create(data, [Map params]) async { Future create(data, [Map params]) async {
//try { //try {
MemoryModel created = (data is MemoryModel) ? data : god.deserializeDatum( MemoryModel created = (data is MemoryModel)
data, outputType: T); ? data
: god.deserializeDatum(data, outputType: T);
created.id = items.length; created.id = items.length;
items[created.id] = created; items[created.id] = created;
@ -54,39 +71,50 @@ class MemoryService<T> extends Service {
} }
Future modify(id, data, [Map params]) async { Future modify(id, data, [Map params]) async {
int desiredId = int.parse(id.toString()); int desiredId = _getId(id);
if (items.containsKey(desiredId)) { if (items.containsKey(desiredId)) {
try { try {
Map existing = god.serializeObject(items[desiredId]); Map existing = god.serializeObject(items[desiredId]);
data = mergeMap([existing, data]); data = mergeMap([existing, data]);
items[desiredId] = items[desiredId] =
(data is Map) ? god.deserializeDatum(data, outputType: T) : data; (data is Map) ? god.deserializeDatum(data, outputType: T) : data;
return _makeJson(desiredId, items[desiredId]); return _makeJson(desiredId, items[desiredId]);
} catch (e) { } catch (e) {
throw new AngelHttpException.badRequest(message: 'Invalid data.'); throw new AngelHttpException.badRequest(message: 'Invalid data.');
} }
} else throw new AngelHttpException.notFound(); } else
throw new AngelHttpException.notFound();
} }
Future update(id, data, [Map params]) async { Future update(id, data, [Map params]) async {
int desiredId = int.parse(id.toString()); int desiredId = _getId(id);
if (items.containsKey(desiredId)) { if (items.containsKey(desiredId)) {
try { try {
items[desiredId] = items[desiredId] =
(data is Map) ? god.deserializeDatum(data, outputType: T) : data; (data is Map) ? god.deserializeDatum(data, outputType: T) : data;
return _makeJson(desiredId, items[desiredId]); return _makeJson(desiredId, items[desiredId]);
} catch (e) { } catch (e) {
throw new AngelHttpException.badRequest(message: 'Invalid data.'); throw new AngelHttpException.badRequest(message: 'Invalid data.');
} }
} else throw new AngelHttpException.notFound(); } else
throw new AngelHttpException.notFound();
} }
Future remove(id, [Map params]) async { Future remove(id, [Map params]) async {
int desiredId = int.parse(id.toString()); if (id == null ||
id == 'null' &&
(allowRemoveAll == true ||
params?.containsKey('provider') != true)) {
items.clear();
return {};
}
int desiredId = _getId(id);
if (items.containsKey(desiredId)) { if (items.containsKey(desiredId)) {
MemoryModel item = items[desiredId]; MemoryModel item = items[desiredId];
items[desiredId] = null; items[desiredId] = null;
return _makeJson(desiredId, item); return _makeJson(desiredId, item);
} else throw new AngelHttpException.notFound(); } else
throw new AngelHttpException.notFound();
} }
} }

View file

@ -70,6 +70,11 @@ class Angel extends AngelBase {
/// Returns the parent instance of this application, if any. /// Returns the parent instance of this application, if any.
Angel get parent => _parent; Angel get parent => _parent;
/// Plug-ins to be called right before server startup.
///
/// If the server is never started, they will never be called.
final List<AngelConfigurer> justBeforeStart = [];
/// Always run before responses are sent. /// Always run before responses are sent.
/// ///
/// These will only not run if an [AngelFatalError] occurs. /// These will only not run if an [AngelFatalError] occurs.
@ -126,6 +131,7 @@ class Angel extends AngelBase {
Future<HttpServer> startServer([InternetAddress address, int port]) async { Future<HttpServer> startServer([InternetAddress address, int port]) async {
var host = address ?? InternetAddress.LOOPBACK_IP_V4; var host = address ?? InternetAddress.LOOPBACK_IP_V4;
this.httpServer = await _serverGenerator(host, port ?? 0); this.httpServer = await _serverGenerator(host, port ?? 0);
await Future.wait(justBeforeStart.map(configure));
preprocessRoutes(); preprocessRoutes();
return httpServer..listen(handleRequest); return httpServer..listen(handleRequest);
} }
@ -139,6 +145,7 @@ class Angel extends AngelBase {
container.singleton(this); container.singleton(this);
} }
/// Runs some [handler]. Returns `true` if request execution should continue.
Future<bool> executeHandler( Future<bool> executeHandler(
handler, RequestContext req, ResponseContext res) async { handler, RequestContext req, ResponseContext res) async {
if (handler is RequestMiddleware) { if (handler is RequestMiddleware) {

View file

@ -68,6 +68,15 @@ class Service extends Routable {
throw new AngelHttpException.methodNotAllowed(); throw new AngelHttpException.methodNotAllowed();
} }
/// Transforms an [id] string into one acceptable by a service.
toId(String id) {
if (id == 'null' || id == null)
return null;
else
return id;
}
/// Generates RESTful routes pointing to this class's methods.
void addRoutes() { void addRoutes() {
Map restProvider = {'provider': Providers.REST}; Map restProvider = {'provider': Providers.REST};
@ -100,7 +109,7 @@ class Service extends Routable {
get( get(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])), .read(toId(req.params['id']), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -108,8 +117,8 @@ class Service extends Routable {
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware); Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
patch( patch(
'/:id', '/:id',
(req, res) async => await this.modify( (req, res) async => await this.modify(toId(req.params['id']), req.body,
req.params['id'], req.body, mergeMap([req.query, restProvider])), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -118,8 +127,8 @@ class Service extends Routable {
Middleware updateMiddleware = getAnnotation(this.update, Middleware); Middleware updateMiddleware = getAnnotation(this.update, Middleware);
post( post(
'/:id', '/:id',
(req, res) async => await this.update( (req, res) async => await this.update(toId(req.params['id']), req.body,
req.params['id'], req.body, mergeMap([req.query, restProvider])), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -128,8 +137,8 @@ class Service extends Routable {
Middleware removeMiddleware = getAnnotation(this.remove, Middleware); Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
delete( delete(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this.remove(
.remove(req.params['id'], mergeMap([req.query, restProvider])), toId(req.params['id']), mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(

View file

@ -1,5 +1,5 @@
name: angel_framework name: angel_framework
version: 1.0.0-dev.53 version: 1.0.0-dev.54
description: Core libraries for the Angel framework. description: Core libraries for the Angel framework.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework homepage: https://github.com/angel-dart/angel_framework