diff --git a/README.md b/README.md index 7ea3b0d8..827a6ee2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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) Core libraries for the Angel Framework. diff --git a/lib/src/http/anonymous_service.dart b/lib/src/http/anonymous_service.dart new file mode 100644 index 00000000..dbe927eb --- /dev/null +++ b/lib/src/http/anonymous_service.dart @@ -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 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); +} diff --git a/lib/src/http/hooked_service.dart b/lib/src/http/hooked_service.dart index d5824548..e46d0103 100644 --- a/lib/src/http/hooked_service.dart +++ b/lib/src/http/hooked_service.dart @@ -138,7 +138,7 @@ class HookedService extends Service { get( '/:id', (req, res) async => await this - .read(req.params['id'], mergeMap([req.query, restProvider])), + .read(toId(req.params['id']), mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); @@ -146,8 +146,8 @@ class HookedService extends Service { Middleware modifyMiddleware = getAnnotation(inner.modify, Middleware); patch( '/:id', - (req, res) async => await this.modify( - req.params['id'], req.body, mergeMap([req.query, restProvider])), + (req, res) async => await this.modify(toId(req.params['id']), req.body, + mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll( @@ -156,8 +156,8 @@ class HookedService extends Service { Middleware updateMiddleware = getAnnotation(inner.update, Middleware); post( '/:id', - (req, res) async => await this.update( - req.params['id'], req.body, mergeMap([req.query, restProvider])), + (req, res) async => await this.update(toId(req.params['id']), req.body, + mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll( @@ -166,8 +166,8 @@ class HookedService extends Service { Middleware removeMiddleware = getAnnotation(inner.remove, Middleware); delete( '/:id', - (req, res) async => await this - .remove(req.params['id'], mergeMap([req.query, restProvider])), + (req, res) async => await this.remove( + toId(req.params['id']), mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll( diff --git a/lib/src/http/http.dart b/lib/src/http/http.dart index 5ca7da13..67eca08c 100644 --- a/lib/src/http/http.dart +++ b/lib/src/http/http.dart @@ -4,6 +4,7 @@ library angel_framework.http; export 'package:angel_route/angel_route.dart'; export 'angel_base.dart'; export 'angel_http_exception.dart'; +export 'anonymous_service.dart'; export 'base_middleware.dart'; export 'base_plugin.dart'; export 'controller.dart'; diff --git a/lib/src/http/memory_service.dart b/lib/src/http/memory_service.dart index 852589bf..1d6d1b6b 100644 --- a/lib/src/http/memory_service.dart +++ b/lib/src/http/memory_service.dart @@ -8,11 +8,25 @@ import '../defs.dart'; import 'angel_http_exception.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]. class MemoryService extends Service { - Map 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 items = {}; + + MemoryService({this.allowRemoveAll: false}) : super() { if (!reflectType(T).isAssignableTo(reflectType(MemoryModel))) { throw new Exception( "MemoryServices only support classes that inherit from MemoryModel."); @@ -31,19 +45,22 @@ class MemoryService extends Service { } Future read(id, [Map params]) async { - int desiredId = int.parse(id.toString()); + int desiredId = _getId(id); if (items.containsKey(desiredId)) { MemoryModel found = items[desiredId]; if (found != null) { return _makeJson(desiredId, found); - } else throw new AngelHttpException.notFound(); - } else throw new AngelHttpException.notFound(); + } else + throw new AngelHttpException.notFound(); + } else + throw new AngelHttpException.notFound(); } Future create(data, [Map params]) async { //try { - MemoryModel created = (data is MemoryModel) ? data : god.deserializeDatum( - data, outputType: T); + MemoryModel created = (data is MemoryModel) + ? data + : god.deserializeDatum(data, outputType: T); created.id = items.length; items[created.id] = created; @@ -54,39 +71,50 @@ class MemoryService extends Service { } Future modify(id, data, [Map params]) async { - int desiredId = int.parse(id.toString()); + int desiredId = _getId(id); if (items.containsKey(desiredId)) { try { Map existing = god.serializeObject(items[desiredId]); data = mergeMap([existing, data]); 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]); } catch (e) { throw new AngelHttpException.badRequest(message: 'Invalid data.'); } - } else throw new AngelHttpException.notFound(); + } else + throw new AngelHttpException.notFound(); } Future update(id, data, [Map params]) async { - int desiredId = int.parse(id.toString()); + int desiredId = _getId(id); if (items.containsKey(desiredId)) { try { 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]); } catch (e) { throw new AngelHttpException.badRequest(message: 'Invalid data.'); } - } else throw new AngelHttpException.notFound(); + } else + throw new AngelHttpException.notFound(); } 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)) { MemoryModel item = items[desiredId]; items[desiredId] = null; return _makeJson(desiredId, item); - } else throw new AngelHttpException.notFound(); + } else + throw new AngelHttpException.notFound(); } -} \ No newline at end of file +} diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 93c72a05..c66c4c67 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -70,6 +70,11 @@ class Angel extends AngelBase { /// Returns the parent instance of this application, if any. 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 justBeforeStart = []; + /// Always run before responses are sent. /// /// These will only not run if an [AngelFatalError] occurs. @@ -126,6 +131,7 @@ class Angel extends AngelBase { Future startServer([InternetAddress address, int port]) async { var host = address ?? InternetAddress.LOOPBACK_IP_V4; this.httpServer = await _serverGenerator(host, port ?? 0); + await Future.wait(justBeforeStart.map(configure)); preprocessRoutes(); return httpServer..listen(handleRequest); } @@ -139,6 +145,7 @@ class Angel extends AngelBase { container.singleton(this); } + /// Runs some [handler]. Returns `true` if request execution should continue. Future executeHandler( handler, RequestContext req, ResponseContext res) async { if (handler is RequestMiddleware) { diff --git a/lib/src/http/service.dart b/lib/src/http/service.dart index 5512e7b5..227856b2 100644 --- a/lib/src/http/service.dart +++ b/lib/src/http/service.dart @@ -68,6 +68,15 @@ class Service extends Routable { 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() { Map restProvider = {'provider': Providers.REST}; @@ -100,7 +109,7 @@ class Service extends Routable { get( '/:id', (req, res) async => await this - .read(req.params['id'], mergeMap([req.query, restProvider])), + .read(toId(req.params['id']), mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); @@ -108,8 +117,8 @@ class Service extends Routable { Middleware modifyMiddleware = getAnnotation(this.modify, Middleware); patch( '/:id', - (req, res) async => await this.modify( - req.params['id'], req.body, mergeMap([req.query, restProvider])), + (req, res) async => await this.modify(toId(req.params['id']), req.body, + mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll( @@ -118,8 +127,8 @@ class Service extends Routable { Middleware updateMiddleware = getAnnotation(this.update, Middleware); post( '/:id', - (req, res) async => await this.update( - req.params['id'], req.body, mergeMap([req.query, restProvider])), + (req, res) async => await this.update(toId(req.params['id']), req.body, + mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll( @@ -128,8 +137,8 @@ class Service extends Routable { Middleware removeMiddleware = getAnnotation(this.remove, Middleware); delete( '/:id', - (req, res) async => await this - .remove(req.params['id'], mergeMap([req.query, restProvider])), + (req, res) async => await this.remove( + toId(req.params['id']), mergeMap([req.query, restProvider])), middleware: [] ..addAll(handlers) ..addAll( diff --git a/pubspec.yaml b/pubspec.yaml index 9d02ae63..4b82e01d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_framework -version: 1.0.0-dev.53 +version: 1.0.0-dev.54 description: Core libraries for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_framework