platform/lib/hooks.dart

299 lines
8.2 KiB
Dart
Raw Normal View History

2017-01-28 21:08:07 +00:00
/// Easy helper hooks.
library angel_framework.hooks;
2017-01-28 21:25:02 +00:00
import 'dart:async';
2018-08-19 15:49:33 +00:00
import 'dart:convert';
2017-01-29 19:49:28 +00:00
import 'dart:mirrors';
2017-01-28 21:08:07 +00:00
import 'angel_framework.dart';
2017-01-29 19:49:28 +00:00
/// Sequentially runs a set of [listeners].
HookedServiceEventListener chainListeners(
Iterable<HookedServiceEventListener> listeners) {
2018-06-08 07:06:26 +00:00
return (HookedServiceEvent e) {
for (HookedServiceEventListener listener in listeners) listener(e);
2017-01-29 19:49:28 +00:00
};
}
/// Runs a [callback] on every service, and listens for future services to run it again.
AngelConfigurer hookAllServices(callback(Service service)) {
2018-06-08 07:06:26 +00:00
return (Angel app) {
2017-01-29 19:49:28 +00:00
List<Service> touched = [];
for (var service in app.services.values) {
if (!touched.contains(service)) {
2018-06-08 07:06:26 +00:00
callback(service);
2017-01-29 19:49:28 +00:00
touched.add(service);
}
}
app.onService.listen((service) {
if (!touched.contains(service)) return callback(service);
});
};
}
2017-03-28 23:29:22 +00:00
/// Transforms `e.data` or `e.result` into JSON-friendly data, i.e. a Map. Runs on Iterables as well.
2017-07-09 16:50:46 +00:00
///
/// The [condition] is optional, and is passed to [transform].
2017-09-22 14:03:23 +00:00
HookedServiceEventListener toJson([condition]) =>
2018-08-19 15:49:33 +00:00
transform(json.encode, condition);
2017-01-28 21:08:07 +00:00
2017-03-11 07:13:57 +00:00
/// Mutates `e.data` or `e.result` using the given [transformer].
2017-04-15 17:42:21 +00:00
///
/// You can optionally provide a [condition], which can be:
/// * A [Providers] instance, or String, to run only on certain clients
/// * The type [Providers], in which case the transformer will run on every client, but *not* on server-side events.
/// * A function: if the function returns `true` (sync or async, doesn't matter),
/// then the transformer will run. If not, the event will be skipped.
/// * An [Iterable] of the above three.
///
/// A provided function must take a [HookedServiceEvent] as its only parameter.
HookedServiceEventListener transform(transformer(obj), [condition]) {
Iterable cond = condition is Iterable ? condition : [condition];
2017-07-09 16:50:46 +00:00
if (condition == null) cond = [];
2017-04-15 17:42:21 +00:00
2018-06-08 07:06:26 +00:00
_condition(HookedServiceEvent e, condition) {
2017-04-15 17:42:21 +00:00
if (condition is Function)
2018-06-08 07:06:26 +00:00
return condition(e);
2017-04-15 17:42:21 +00:00
else if (condition == Providers)
return true;
else {
if (e.params?.containsKey('provider') == true) {
var provider = e.params['provider'] as Providers;
if (condition is Providers)
return condition == provider;
else
return condition.toString() == provider.via;
} else {
return false;
}
}
}
2018-06-08 07:06:26 +00:00
normalize(HookedServiceEvent e, obj) {
2017-04-15 17:42:21 +00:00
bool transform = true;
for (var c in cond) {
2018-06-08 07:06:26 +00:00
var r = _condition(e, c);
2017-04-15 17:42:21 +00:00
if (r != true) {
transform = false;
break;
}
}
if (transform != true) {
if (obj == null)
return null;
else if (obj is Iterable)
return obj.toList();
else
return obj;
}
2017-03-11 07:17:52 +00:00
if (obj == null)
return null;
2017-04-15 17:42:21 +00:00
else if (obj is Iterable) {
var r = [];
for (var o in obj) {
2018-06-08 07:06:26 +00:00
r.add(normalize(e, o));
2017-04-15 17:42:21 +00:00
}
return r;
} else
2017-03-11 07:17:52 +00:00
return transformer(obj);
}
2018-06-08 07:06:26 +00:00
return (HookedServiceEvent e) {
2017-03-11 07:17:52 +00:00
if (e.isBefore) {
2018-06-08 07:06:26 +00:00
e.data = normalize(e, e.data);
} else if (e.isAfter) e.result = normalize(e, e.result);
2017-03-11 07:17:52 +00:00
};
2017-03-11 07:13:57 +00:00
}
2017-01-29 19:49:28 +00:00
/// Transforms `e.data` or `e.result` into an instance of the given [type],
2017-01-28 21:08:07 +00:00
/// if it is not already.
HookedServiceEventListener toType(Type type) {
return (HookedServiceEvent e) {
2017-01-29 19:49:28 +00:00
normalize(obj) {
if (obj != null && obj.runtimeType != type)
2017-02-13 00:38:33 +00:00
return god.deserializeDatum(obj, outputType: type);
return obj;
2017-01-29 19:49:28 +00:00
}
2017-02-13 00:38:33 +00:00
if (e.isBefore) {
2017-03-11 07:17:52 +00:00
e.data = normalize(e.data);
2017-02-13 00:38:33 +00:00
} else
e.result = normalize(e.result);
2017-01-28 21:08:07 +00:00
};
}
2017-01-28 21:25:02 +00:00
2017-01-29 19:49:28 +00:00
/// Removes one or more [key]s from `e.data` or `e.result`.
/// Works on single objects and iterables.
2017-03-06 04:55:13 +00:00
///
/// Only applies to the client-side.
2017-03-04 21:12:39 +00:00
HookedServiceEventListener remove(key, [remover(key, obj)]) {
2017-01-28 21:25:02 +00:00
return (HookedServiceEvent e) async {
2018-06-08 07:06:26 +00:00
_remover(key, obj) async {
2017-01-28 21:25:02 +00:00
if (remover != null)
return remover(key, obj);
else if (obj is List)
return obj..remove(key);
else if (obj is Iterable)
2018-06-23 03:29:38 +00:00
return obj.where((k) => key != true);
2017-01-28 21:25:02 +00:00
else if (obj is Map)
return obj..remove(key);
2017-01-29 19:49:28 +00:00
else {
try {
2018-06-23 03:29:38 +00:00
reflect(obj).setField(new Symbol(key.toString()), null);
2017-01-29 19:49:28 +00:00
return obj;
} catch (e) {
2017-04-15 17:42:21 +00:00
throw new ArgumentError("Cannot remove key '$key' from $obj.");
2017-01-29 19:49:28 +00:00
}
}
2017-01-28 21:25:02 +00:00
}
var keys = key is Iterable ? key : [key];
_removeAll(obj) async {
var r = obj;
for (var key in keys) {
r = await _remover(key, r);
}
return r;
}
2017-01-29 19:49:28 +00:00
normalize(obj) async {
if (obj != null) {
if (obj is Iterable) {
2017-04-26 22:55:47 +00:00
return await Future.wait(obj.map(_removeAll));
2017-01-29 19:49:28 +00:00
} else
2018-06-08 07:06:26 +00:00
return _removeAll(obj);
2017-01-29 19:49:28 +00:00
}
}
2017-03-28 23:29:22 +00:00
if (e.params?.containsKey('provider') == true) {
if (e.isBefore) {
2018-06-08 07:06:26 +00:00
e.data = normalize(e.data);
2017-03-28 23:29:22 +00:00
} else if (e.isAfter) {
2018-06-08 07:06:26 +00:00
e.result = normalize(e.result);
2017-03-28 23:29:22 +00:00
}
}
2017-01-28 21:25:02 +00:00
};
}
2017-01-29 19:49:28 +00:00
/// Disables a service method for client access from a provider.
2017-01-28 21:25:02 +00:00
///
/// [provider] can be either a String, [Providers], an Iterable of String, or a
/// function that takes a [HookedServiceEvent] and returns a bool.
/// Futures are allowed.
2017-01-29 19:49:28 +00:00
///
/// If [provider] is `null`, then it will be disabled to all clients.
2017-01-28 21:25:02 +00:00
HookedServiceEventListener disable([provider]) {
2018-06-08 07:06:26 +00:00
return (HookedServiceEvent e) {
2017-01-29 19:49:28 +00:00
if (e.params.containsKey('provider')) {
if (provider == null)
throw new AngelHttpException.methodNotAllowed();
else if (provider is Function) {
2018-06-08 07:06:26 +00:00
var r = provider(e);
2017-01-29 19:49:28 +00:00
if (r != true) throw new AngelHttpException.methodNotAllowed();
} else {
_provide(p) => p is Providers ? p : new Providers(p.toString());
2017-01-28 21:25:02 +00:00
2017-01-29 19:49:28 +00:00
var providers = provider is Iterable
? provider.map(_provide)
: [_provide(provider)];
2017-01-28 21:25:02 +00:00
2017-01-29 19:49:28 +00:00
if (providers.any((Providers p) => p == e.params['provider'])) {
throw new AngelHttpException.methodNotAllowed();
}
2017-01-28 21:25:02 +00:00
}
}
};
}
2017-03-28 23:29:22 +00:00
/// Serializes the current time to `e.data` or `e.result`.
/// You can provide an [assign] function to set the property on your object, and skip reflection.
2017-05-27 12:39:45 +00:00
/// If [serialize] is `true` (default), then the set date will be a `String`. If not, a raw `DateTime` will be used.
2017-03-28 23:29:22 +00:00
///
/// Default key: `createdAt`
2017-05-27 12:39:45 +00:00
HookedServiceEventListener addCreatedAt(
2017-08-15 23:01:16 +00:00
{assign(obj, now), String key, bool serialize: true}) {
2017-03-28 23:29:22 +00:00
var name = key?.isNotEmpty == true ? key : 'createdAt';
2018-06-08 07:06:26 +00:00
return (HookedServiceEvent e) {
2017-08-15 23:01:16 +00:00
_assign(obj, now) {
2017-03-28 23:29:22 +00:00
if (assign != null)
return assign(obj, now);
else if (obj is Map)
2017-04-29 04:18:45 +00:00
obj[name] = now;
2017-03-28 23:29:22 +00:00
else {
try {
reflect(obj).setField(new Symbol(name), now);
} catch (e) {
throw new ArgumentError("Cannot set key '$name' on $obj.");
}
}
}
2017-05-27 12:39:45 +00:00
var d = new DateTime.now().toUtc();
var now = serialize == false ? d : d.toIso8601String();
2017-03-28 23:29:22 +00:00
2018-06-08 07:06:26 +00:00
normalize(obj) {
2017-03-28 23:29:22 +00:00
if (obj != null) {
if (obj is Iterable) {
obj.forEach(normalize);
} else {
2018-06-08 07:06:26 +00:00
_assign(obj, now);
2017-03-28 23:29:22 +00:00
}
}
}
2018-06-08 07:06:26 +00:00
normalize(e.isBefore ? e.data : e.result);
2017-03-28 23:29:22 +00:00
};
}
/// Serializes the current time to `e.data` or `e.result`.
2017-05-27 12:39:45 +00:00
/// You can provide an [assign] function to set the property on your object, and skip reflection.
/// If [serialize] is `true` (default), then the set date will be a `String`. If not, a raw `DateTime` will be used.
///
/// Default key: `updatedAt`
HookedServiceEventListener addUpdatedAt(
2017-08-15 23:01:16 +00:00
{assign(obj, now), String key, bool serialize: true}) {
2017-03-28 23:29:22 +00:00
var name = key?.isNotEmpty == true ? key : 'updatedAt';
2018-06-08 07:06:26 +00:00
return (HookedServiceEvent e) {
2017-08-15 23:01:16 +00:00
_assign(obj, now) {
2017-03-28 23:29:22 +00:00
if (assign != null)
return assign(obj, now);
else if (obj is Map)
2017-04-29 04:18:45 +00:00
obj[name] = now;
2017-03-28 23:29:22 +00:00
else {
try {
reflect(obj).setField(new Symbol(name), now);
} catch (e) {
throw new ArgumentError("Cannot SET key '$name' ON $obj.");
}
}
}
2017-05-27 12:39:45 +00:00
var d = new DateTime.now().toUtc();
var now = serialize == false ? d : d.toIso8601String();
2017-03-28 23:29:22 +00:00
2018-06-08 07:06:26 +00:00
normalize(obj) {
2017-03-28 23:29:22 +00:00
if (obj != null) {
if (obj is Iterable) {
obj.forEach(normalize);
} else {
2018-06-08 07:06:26 +00:00
_assign(obj, now);
2017-03-28 23:29:22 +00:00
}
}
}
2018-06-08 07:06:26 +00:00
normalize(e.isBefore ? e.data : e.result);
2017-03-28 23:29:22 +00:00
};
}