platform/lib/src/http/hooked_service.dart

504 lines
17 KiB
Dart
Raw Normal View History

library angel_framework.http;
import 'dart:async';
import 'package:merge_map/merge_map.dart';
import '../util.dart';
2017-01-20 22:11:20 +00:00
import 'request_context.dart';
import 'response_context.dart';
import 'metadata.dart';
import 'service.dart';
2016-06-19 05:02:41 +00:00
/// Wraps another service in a service that broadcasts events on actions.
class HookedService extends Service {
2016-06-21 22:56:04 +00:00
/// Tbe service that is proxied by this hooked one.
final Service inner;
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher beforeIndexed =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher beforeRead = new HookedServiceEventDispatcher();
HookedServiceEventDispatcher beforeCreated =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher beforeModified =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher beforeUpdated =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher beforeRemoved =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher afterIndexed =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher afterRead = new HookedServiceEventDispatcher();
HookedServiceEventDispatcher afterCreated =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher afterModified =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher afterUpdated =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedServiceEventDispatcher afterRemoved =
2016-10-22 20:41:36 +00:00
new HookedServiceEventDispatcher();
2016-06-21 04:19:43 +00:00
HookedService(Service this.inner) {
2016-06-24 19:19:02 +00:00
// Clone app instance
2016-10-22 20:41:36 +00:00
if (inner.app != null) this.app = inner.app;
2016-11-23 09:10:47 +00:00
}
2016-06-24 19:19:02 +00:00
2017-01-20 22:40:48 +00:00
RequestContext _getRequest(Map params) {
if (params == null) return null;
return params['__requestctx'];
}
ResponseContext _getResponse(Map params) {
if (params == null) return null;
return params['__responsectx'];
}
2017-01-20 22:11:20 +00:00
Map _stripReq(Map params) {
if (params == null)
return params;
else
2017-01-28 03:47:00 +00:00
return params.keys
.where((key) => key != '__requestctx' && key != '__responsectx')
.fold({}, (map, key) => map..[key] = params[key]);
2017-01-20 22:11:20 +00:00
}
/// Adds hooks to this instance.
2016-12-10 14:05:40 +00:00
void addHooks() {
Hooks hooks = getAnnotation(inner, Hooks);
final before = [];
final after = [];
if (hooks != null) {
before.addAll(hooks.before);
after.addAll(hooks.after);
}
void applyListeners(Function fn, HookedServiceEventDispatcher dispatcher,
[bool isAfter]) {
Hooks hooks = getAnnotation(fn, Hooks);
final listeners = []..addAll(isAfter == true ? after : before);
if (hooks != null)
listeners.addAll(isAfter == true ? hooks.after : hooks.before);
listeners.forEach(dispatcher.listen);
}
applyListeners(inner.index, beforeIndexed);
applyListeners(inner.read, beforeRead);
applyListeners(inner.created, beforeCreated);
applyListeners(inner.modify, beforeModified);
applyListeners(inner.updated, beforeUpdated);
applyListeners(inner.removed, beforeRemoved);
applyListeners(inner.index, afterIndexed, true);
applyListeners(inner.read, afterRead, true);
applyListeners(inner.created, afterCreated, true);
applyListeners(inner.modify, afterModified, true);
applyListeners(inner.updated, afterUpdated, true);
applyListeners(inner.removed, afterRemoved, true);
}
2017-01-20 22:11:20 +00:00
/// Adds routes to this instance.
2016-11-23 09:10:47 +00:00
@override
void addRoutes() {
2016-06-24 19:19:02 +00:00
// Set up our routes. We still need to copy middleware from inner service
Map restProvider = {'provider': Providers.REST};
// Add global middleware if declared on the instance itself
Middleware before = getAnnotation(inner, Middleware);
2017-01-28 03:47:00 +00:00
final handlers = [
(RequestContext req, ResponseContext res) async {
req.query
..['__requestctx'] = req
..['__responsectx'] = res;
return true;
}
];
2016-10-22 20:41:36 +00:00
2016-11-23 09:10:47 +00:00
if (before != null) handlers.addAll(before.handlers);
2016-06-24 19:19:02 +00:00
Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
2016-06-24 19:19:02 +00:00
get('/', (req, res) async {
2017-01-28 03:47:00 +00:00
return await this.index(mergeMap([req.query, restProvider]));
2016-10-22 20:41:36 +00:00
},
middleware: []
..addAll(handlers)
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
2016-06-24 19:19:02 +00:00
Middleware createMiddleware = getAnnotation(inner.create, Middleware);
2016-12-10 14:05:40 +00:00
post(
'/',
2017-01-28 03:47:00 +00:00
(req, res) async =>
await this.create(req.body, mergeMap([req.query, restProvider])),
2016-10-22 20:41:36 +00:00
middleware: []
..addAll(handlers)
..addAll(
(createMiddleware == null) ? [] : createMiddleware.handlers));
2016-06-24 19:19:02 +00:00
Middleware readMiddleware = getAnnotation(inner.read, Middleware);
2016-06-24 19:19:02 +00:00
get(
'/:id',
2017-01-28 03:47:00 +00:00
(req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])),
2016-10-22 20:41:36 +00:00
middleware: []
..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
2016-06-24 19:19:02 +00:00
Middleware modifyMiddleware = getAnnotation(inner.modify, Middleware);
2016-06-24 19:19:02 +00:00
patch(
'/:id',
2016-12-10 14:05:40 +00:00
(req, res) async => await this.modify(
2017-01-28 03:47:00 +00:00
req.params['id'], req.body, mergeMap([req.query, restProvider])),
2016-10-22 20:41:36 +00:00
middleware: []
..addAll(handlers)
..addAll(
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
2016-06-24 19:19:02 +00:00
Middleware updateMiddleware = getAnnotation(inner.update, Middleware);
2016-06-24 19:19:02 +00:00
post(
'/:id',
2016-12-10 14:05:40 +00:00
(req, res) async => await this.update(
2017-01-28 03:47:00 +00:00
req.params['id'], req.body, mergeMap([req.query, restProvider])),
2016-10-22 20:41:36 +00:00
middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
2016-06-24 19:19:02 +00:00
Middleware removeMiddleware = getAnnotation(inner.remove, Middleware);
2016-06-24 19:19:02 +00:00
delete(
'/:id',
2017-01-28 03:47:00 +00:00
(req, res) async => await this
.remove(req.params['id'], mergeMap([req.query, restProvider])),
2016-10-22 20:41:36 +00:00
middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
2016-12-10 14:05:40 +00:00
addHooks();
}
2017-01-28 21:08:07 +00:00
/// Runs the [listener] before every service method specified.
void before(
Iterable<String> eventNames, HookedServiceEventListener listener) {
eventNames.map((name) {
switch (name) {
case HookedServiceEvent.INDEXED:
return beforeIndexed;
case HookedServiceEvent.READ:
return beforeRead;
case HookedServiceEvent.CREATED:
return beforeCreated;
case HookedServiceEvent.MODIFIED:
return beforeModified;
case HookedServiceEvent.UPDATED:
return beforeUpdated;
case HookedServiceEvent.REMOVED:
return beforeRemoved;
default:
throw new ArgumentError('Invalid service method: ${name}');
}
}).forEach((HookedServiceEventDispatcher dispatcher) =>
dispatcher.listen(listener));
}
/// Runs the [listener] after every service method specified.
void after(Iterable<String> eventNames, HookedServiceEventListener listener) {
eventNames.map((name) {
switch (name) {
case HookedServiceEvent.INDEXED:
return afterIndexed;
case HookedServiceEvent.READ:
return afterRead;
case HookedServiceEvent.CREATED:
return afterCreated;
case HookedServiceEvent.MODIFIED:
return afterModified;
case HookedServiceEvent.UPDATED:
return afterUpdated;
case HookedServiceEvent.REMOVED:
return afterRemoved;
default:
throw new ArgumentError('Invalid service method: ${name}');
}
}).forEach((HookedServiceEventDispatcher dispatcher) =>
dispatcher.listen(listener));
}
/// Runs the [listener] before every service method.
2017-01-28 03:47:00 +00:00
void beforeAll(HookedServiceEventListener listener) {
beforeIndexed.listen(listener);
beforeRead.listen(listener);
beforeCreated.listen(listener);
beforeModified.listen(listener);
beforeUpdated.listen(listener);
beforeRemoved.listen(listener);
}
/// Runs the [listener] after every service method.
void afterAll(HookedServiceEventListener listener) {
afterIndexed.listen(listener);
afterRead.listen(listener);
afterCreated.listen(listener);
afterModified.listen(listener);
afterUpdated.listen(listener);
afterRemoved.listen(listener);
}
/// Runs the [listener] before [create], [modify] and [update].
void beforeModify(HookedServiceEventListener listener) {
beforeCreated.listen(listener);
beforeModified.listen(listener);
beforeUpdated.listen(listener);
}
@override
2017-01-20 22:11:20 +00:00
Future<List> index([Map _params]) async {
var params = _stripReq(_params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent before = await beforeIndexed._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(false, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.INDEXED,
2016-06-21 04:19:43 +00:00
params: params));
2016-06-19 05:02:41 +00:00
if (before._canceled) {
2016-06-21 04:19:43 +00:00
HookedServiceEvent after = await beforeIndexed._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.INDEXED,
2016-06-21 04:19:43 +00:00
params: params, result: before.result));
return after.result;
2016-06-19 05:02:41 +00:00
}
List result = await inner.index(params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent after = await afterIndexed._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.INDEXED,
2016-06-21 04:19:43 +00:00
params: params, result: result));
return after.result;
}
@override
2017-01-20 22:11:20 +00:00
Future read(id, [Map _params]) async {
var params = _stripReq(_params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent before = await beforeRead._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(false, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.READ,
2016-06-21 04:19:43 +00:00
id: id, params: params));
2016-06-21 04:19:43 +00:00
if (before._canceled) {
HookedServiceEvent after = await afterRead._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.READ,
2016-06-21 04:19:43 +00:00
id: id, params: params, result: before.result));
return after.result;
}
var result = await inner.read(id, params);
HookedServiceEvent after = await afterRead._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.READ,
2016-06-21 04:19:43 +00:00
id: id, params: params, result: result));
return after.result;
}
@override
2017-01-20 22:11:20 +00:00
Future create(data, [Map _params]) async {
var params = _stripReq(_params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent before = await beforeCreated._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(false, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.CREATED,
2016-06-21 04:19:43 +00:00
data: data, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterCreated._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.CREATED,
2016-06-21 04:19:43 +00:00
data: data, params: params, result: before.result));
return after.result;
}
var result = await inner.create(data, params);
HookedServiceEvent after = await afterCreated._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.CREATED,
2016-06-21 04:19:43 +00:00
data: data, params: params, result: result));
return after.result;
}
@override
2017-01-20 22:11:20 +00:00
Future modify(id, data, [Map _params]) async {
var params = _stripReq(_params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent before = await beforeModified._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(false, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.MODIFIED,
2016-06-21 04:19:43 +00:00
id: id, data: data, params: params));
2016-06-21 04:19:43 +00:00
if (before._canceled) {
HookedServiceEvent after = await afterModified._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.MODIFIED,
2016-06-21 04:19:43 +00:00
id: id, data: data, params: params, result: before.result));
return after.result;
}
var result = await inner.modify(id, data, params);
HookedServiceEvent after = await afterModified._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.MODIFIED,
2016-06-21 04:19:43 +00:00
id: id, data: data, params: params, result: result));
return after.result;
}
@override
2017-01-20 22:11:20 +00:00
Future update(id, data, [Map _params]) async {
var params = _stripReq(_params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent before = await beforeUpdated._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(false, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.UPDATED,
2016-06-21 04:19:43 +00:00
id: id, data: data, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterUpdated._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.UPDATED,
2016-06-21 04:19:43 +00:00
id: id, data: data, params: params, result: before.result));
return after.result;
}
var result = await inner.update(id, data, params);
HookedServiceEvent after = await afterUpdated._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.UPDATED,
2016-06-21 04:19:43 +00:00
id: id, data: data, params: params, result: result));
return after.result;
}
@override
2017-01-20 22:11:20 +00:00
Future remove(id, [Map _params]) async {
var params = _stripReq(_params);
2016-06-21 04:19:43 +00:00
HookedServiceEvent before = await beforeRemoved._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(false, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.REMOVED,
2016-06-21 04:19:43 +00:00
id: id, params: params));
if (before._canceled) {
HookedServiceEvent after = await afterRemoved._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.REMOVED,
2016-06-21 04:19:43 +00:00
id: id, params: params, result: before.result));
return after.result;
}
var result = await inner.remove(id, params);
HookedServiceEvent after = await afterRemoved._emit(
2017-01-28 21:08:07 +00:00
new HookedServiceEvent._base(true, _getRequest(_params),
2017-01-20 22:40:48 +00:00
_getResponse(_params), inner, HookedServiceEvent.REMOVED,
2016-06-21 04:19:43 +00:00
id: id, params: params, result: result));
return after.result;
}
2017-01-20 22:11:20 +00:00
/// Fires an `after` event. This will not be propagated to clients,
/// but will be broadcasted to WebSockets, etc.
2017-01-20 22:40:48 +00:00
Future<HookedServiceEvent> fire(String eventName, result,
[HookedServiceEventListener callback]) async {
2017-01-20 22:11:20 +00:00
HookedServiceEventDispatcher dispatcher;
switch (eventName) {
case HookedServiceEvent.INDEXED:
dispatcher = afterIndexed;
break;
case HookedServiceEvent.READ:
dispatcher = afterRead;
break;
case HookedServiceEvent.CREATED:
dispatcher = afterCreated;
break;
case HookedServiceEvent.MODIFIED:
dispatcher = afterModified;
break;
case HookedServiceEvent.UPDATED:
dispatcher = afterUpdated;
break;
case HookedServiceEvent.REMOVED:
dispatcher = afterRemoved;
break;
default:
throw new ArgumentError("Invalid service event name: '$eventName'");
}
2017-01-28 21:08:07 +00:00
var ev = new HookedServiceEvent._base(true, null, null, this, eventName);
2017-01-20 22:11:20 +00:00
if (callback != null) await callback(ev);
return await dispatcher._emit(ev);
}
2016-06-19 05:02:41 +00:00
}
/// Fired when a hooked service is invoked.
class HookedServiceEvent {
2016-06-21 04:19:43 +00:00
static const String INDEXED = "indexed";
static const String READ = "read";
static const String CREATED = "created";
static const String MODIFIED = "modified";
static const String UPDATED = "updated";
static const String REMOVED = "removed";
2016-06-19 05:02:41 +00:00
/// Use this to end processing of an event.
2017-01-28 21:25:02 +00:00
void cancel([result]) {
2016-06-19 05:02:41 +00:00
_canceled = true;
2017-01-28 21:25:02 +00:00
this.result = result ?? this.result;
2016-06-19 05:02:41 +00:00
}
bool _canceled = false;
2016-06-21 04:19:43 +00:00
String _eventName;
var _id;
2017-01-28 21:08:07 +00:00
bool _isAfter;
2016-06-19 05:02:41 +00:00
var data;
2016-06-21 04:19:43 +00:00
Map _params;
2017-01-20 22:11:20 +00:00
RequestContext _request;
ResponseContext _response;
2017-01-28 21:25:02 +00:00
var result;
2016-06-21 04:19:43 +00:00
String get eventName => _eventName;
get id => _id;
2017-01-28 21:08:07 +00:00
bool get isAfter => _isAfter == true;
bool get isBefore => !isAfter;
2016-06-21 04:19:43 +00:00
Map get params => _params;
2017-01-20 22:11:20 +00:00
RequestContext get request => _request;
ResponseContext get response => _response;
2016-06-19 05:02:41 +00:00
/// The inner service whose method was hooked.
Service service;
2017-01-28 21:08:07 +00:00
HookedServiceEvent._base(this._isAfter, this._request, this._response,
Service this.service, String this._eventName,
2017-01-28 21:25:02 +00:00
{id, this.data, Map params, this.result}) {
2016-06-21 04:19:43 +00:00
_id = id;
_params = params ?? {};
2016-06-19 05:02:41 +00:00
}
2016-06-21 04:19:43 +00:00
}
/// Triggered on a hooked service event.
2017-01-20 22:11:20 +00:00
typedef HookedServiceEventListener(HookedServiceEvent event);
2016-06-21 04:19:43 +00:00
/// Can be listened to, but events may be canceled.
class HookedServiceEventDispatcher {
List<HookedServiceEventListener> listeners = [];
/// Fires an event, and returns it once it is either canceled, or all listeners have run.
Future<HookedServiceEvent> _emit(HookedServiceEvent event) async {
for (var listener in listeners) {
await listener(event);
if (event._canceled) return event;
}
return event;
}
/// Registers the listener to be called whenever an event is triggered.
void listen(HookedServiceEventListener listener) {
listeners.add(listener);
}
}