diff --git a/README.md b/README.md index 6974fe84..7ea3b0d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_framework -[![pub 1.0.0-dev.52](https://img.shields.io/badge/pub-1.0.0--dev.52-red.svg)](https://pub.dartlang.org/packages/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) [![build status](https://travis-ci.org/angel-dart/framework.svg)](https://travis-ci.org/angel-dart/framework) Core libraries for the Angel Framework. diff --git a/lib/hooks.dart b/lib/hooks.dart index 6962ee3b..c16408a4 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -2,27 +2,62 @@ library angel_framework.hooks; import 'dart:async'; +import 'dart:mirrors'; import 'package:json_god/json_god.dart' as god; import 'angel_framework.dart'; -/// Transforms `e.data` into JSON-friendly data, i.e. a Map. -HookedServiceEventListener toJson() { - return (HookedServiceEvent e) { - if (e.data != null && e.data is! Map) e.data = god.serializeObject(e.data); +/// Sequentially runs a set of [listeners]. +HookedServiceEventListener chainListeners( + Iterable listeners) { + return (HookedServiceEvent e) async { + for (HookedServiceEventListener listener in listeners) await listener(e); }; } -/// Transforms `e.data` into an instance of the given [type], +/// Runs a [callback] on every service, and listens for future services to run it again. +AngelConfigurer hookAllServices(callback(Service service)) { + return (Angel app) async { + List touched = []; + + for (var service in app.services.values) { + if (!touched.contains(service)) { + await callback(service); + touched.add(service); + } + } + + app.onService.listen((service) { + if (!touched.contains(service)) return callback(service); + }); + }; +} + +/// Transforms `e.data` or `e.result` into JSON-friendly data, i.e. a Map. +HookedServiceEventListener toJson() { + return (HookedServiceEvent e) { + normalize(obj) { + if (obj != null && obj is! Map) obj = god.serializeObject(obj); + } + + normalize(e.isBefore ? e.data : e.result); + }; +} + +/// Transforms `e.data` or `e.result` into an instance of the given [type], /// if it is not already. HookedServiceEventListener toType(Type type) { return (HookedServiceEvent e) { - if (e.data != null && e.data.runtimeType != type) - e.data = god.deserializeDatum(e.data, outputType: type); + normalize(obj) { + if (obj != null && obj.runtimeType != type) + obj = god.deserializeDatum(obj, outputType: type); + } + + normalize(e.isBefore ? e.data : e.result); }; } -/// Removes one or more [key]s from service results. -/// Works on single results, and iterable results. +/// Removes one or more [key]s from `e.data` or `e.result`. +/// Works on single objects and iterables. HookedServiceEventListener remove(key, remover(key, obj)) { return (HookedServiceEvent e) async { if (!e.isAfter) throw new StateError("'remove' only works on after hooks."); @@ -38,8 +73,14 @@ HookedServiceEventListener remove(key, remover(key, obj)) { return obj..remove(key); else if (obj is Extensible) return obj..properties.remove(key); - else - throw new ArgumentError("Cannot remove key 'key' from $obj."); + else { + try { + reflect(obj).setField(new Symbol(key), null); + return obj; + } catch (e) { + throw new ArgumentError("Cannot remove key 'key' from $obj."); + } + } } var keys = key is Iterable ? key : [key]; @@ -54,33 +95,45 @@ HookedServiceEventListener remove(key, remover(key, obj)) { return r; } - if (e.result is Iterable) { - var r = await Future.wait(e.result.map(_removeAll)); - e.result = e.result is List ? r.toList() : r; - } else - e.result = await _removeAll(e.result); + normalize(obj) async { + if (obj != null) { + if (obj is Iterable) { + var r = await Future.wait(obj.map(_removeAll)); + obj = obj is List ? r.toList() : r; + } else + obj = await _removeAll(obj); + } + } + + await normalize(e.isBefore ? e.data : e.result); }; } -/// Disables a service method for access from a provider. +/// Disables a service method for client access from a provider. /// /// [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. +/// +/// If [provider] is `null`, then it will be disabled to all clients. HookedServiceEventListener disable([provider]) { return (HookedServiceEvent e) async { - if (provider is Function) { - var r = await provider(e); - - if (r != true) throw new AngelHttpException.methodNotAllowed(); - } else { - _provide(p) => p is Providers ? p : new Providers(p.toString()); - - var providers = - provider is Iterable ? provider.map(_provide) : [_provide(provider)]; - - if (providers.any((Providers p) => p == e.params['provider'])) { + if (e.params.containsKey('provider')) { + if (provider == null) throw new AngelHttpException.methodNotAllowed(); + else if (provider is Function) { + var r = await provider(e); + if (r != true) throw new AngelHttpException.methodNotAllowed(); + } else { + _provide(p) => p is Providers ? p : new Providers(p.toString()); + + var providers = provider is Iterable + ? provider.map(_provide) + : [_provide(provider)]; + + if (providers.any((Providers p) => p == e.params['provider'])) { + throw new AngelHttpException.methodNotAllowed(); + } } } }; diff --git a/pubspec.yaml b/pubspec.yaml index 6d04f23c..9d02ae63 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_framework -version: 1.0.0-dev.52 +version: 1.0.0-dev.53 description: Core libraries for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_framework