platform/lib/src/http/service.dart

237 lines
6.8 KiB
Dart
Raw Normal View History

library angel_framework.http.service;
import 'dart:async';
2017-03-28 23:29:22 +00:00
import 'package:angel_framework/src/http/response_context.dart';
2017-09-22 04:48:22 +00:00
import 'package:angel_http_exception/angel_http_exception.dart';
import 'package:merge_map/merge_map.dart';
import '../util.dart';
import 'angel_base.dart';
2017-02-23 00:37:15 +00:00
import 'hooked_service.dart' show HookedService;
import 'metadata.dart';
import 'routable.dart';
2016-04-18 03:27:23 +00:00
2016-06-21 22:56:04 +00:00
/// Indicates how the service was accessed.
///
/// This will be passed to the `params` object in a service method.
/// When requested on the server side, this will be null.
2016-06-19 05:02:41 +00:00
class Providers {
2016-06-21 22:56:04 +00:00
/// The transport through which the client is accessing this service.
2016-06-19 05:02:41 +00:00
final String via;
2016-06-21 22:56:04 +00:00
const Providers(String this.via);
2016-06-19 05:02:41 +00:00
static const String viaRest = "rest";
static const String viaWebsocket = "websocket";
static const String viaGraphQL = "graphql";
2016-06-21 22:56:04 +00:00
/// Represents a request via REST.
static const Providers rest = const Providers(viaRest);
2016-06-21 22:56:04 +00:00
/// Represents a request over WebSockets.
static const Providers websocket = const Providers(viaWebsocket);
/// Represents a request parsed from GraphQL.
static const Providers graphql = const Providers(viaGraphQL);
2017-01-28 03:47:00 +00:00
@override
bool operator ==(other) => other is Providers && other.via == via;
2017-11-28 18:14:50 +00:00
@override
String toString() {
return 'via:$via';
}
2016-06-19 05:02:41 +00:00
}
2016-06-21 22:56:04 +00:00
/// A front-facing interface that can present data to and operate on data on behalf of the user.
///
/// Heavily inspired by FeathersJS. <3
2016-04-18 03:27:23 +00:00
class Service extends Routable {
2017-06-06 12:42:33 +00:00
/// A [List] of keys that services should ignore, should they see them in the query.
static const List<String> specialQueryKeys = const [
2017-06-06 12:42:33 +00:00
r'$limit',
r'$sort',
2017-07-09 16:50:46 +00:00
'page',
'token'
2017-06-06 12:42:33 +00:00
];
/// The [Angel] app powering this service.
AngelBase app;
2016-04-18 03:27:23 +00:00
2017-04-25 02:44:22 +00:00
/// Closes this service, including any database connections or stream controllers.
Future close() async {}
2016-04-18 03:27:23 +00:00
/// Retrieves all resources.
2017-02-13 00:38:33 +00:00
Future index([Map params]) {
2017-01-28 21:08:07 +00:00
throw new AngelHttpException.methodNotAllowed();
2016-04-18 03:27:23 +00:00
}
/// Retrieves the desired resource.
Future read(id, [Map params]) {
2017-01-28 21:08:07 +00:00
throw new AngelHttpException.methodNotAllowed();
2016-04-18 03:27:23 +00:00
}
/// Creates a resource.
2016-06-19 05:02:41 +00:00
Future create(data, [Map params]) {
2017-01-28 21:08:07 +00:00
throw new AngelHttpException.methodNotAllowed();
2016-04-18 03:27:23 +00:00
}
/// Modifies a resource.
2016-06-19 05:02:41 +00:00
Future modify(id, data, [Map params]) {
2017-01-28 21:08:07 +00:00
throw new AngelHttpException.methodNotAllowed();
}
/// Overwrites a resource.
2016-06-19 05:02:41 +00:00
Future update(id, data, [Map params]) {
2017-01-28 21:08:07 +00:00
throw new AngelHttpException.methodNotAllowed();
2016-04-18 03:27:23 +00:00
}
/// Removes the given resource.
Future remove(id, [Map params]) {
2017-01-28 21:08:07 +00:00
throw new AngelHttpException.methodNotAllowed();
2016-04-18 03:27:23 +00:00
}
2017-02-01 21:43:18 +00:00
/// 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.
2016-11-23 09:10:47 +00:00
void addRoutes() {
Map restProvider = {'provider': Providers.rest};
2016-06-19 05:02:41 +00:00
2016-06-23 19:09:49 +00:00
// Add global middleware if declared on the instance itself
Middleware before = getAnnotation(this, Middleware);
2016-10-22 20:41:36 +00:00
final handlers = [];
2016-11-23 09:10:47 +00:00
if (before != null) handlers.addAll(before.handlers);
2016-06-23 19:09:49 +00:00
Middleware indexMiddleware = getAnnotation(this.index, Middleware);
2016-06-21 04:19:43 +00:00
get('/', (req, res) async {
2017-02-13 00:38:33 +00:00
return await this.index(mergeMap([
{'query': req.query},
2017-02-23 00:37:15 +00:00
restProvider,
req.serviceParams
2017-02-13 00:38:33 +00:00
]));
2016-10-22 20:41:36 +00:00
},
middleware: []
..addAll(handlers)
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
Middleware createMiddleware = getAnnotation(this.create, Middleware);
2017-03-28 23:29:22 +00:00
post('/', (req, ResponseContext res) async {
var r = await this.create(
await req.lazyBody(),
mergeMap([
{'query': req.query},
restProvider,
req.serviceParams
]));
res.statusCode = 201;
return r;
},
middleware: []
..addAll(handlers)
..addAll(
(createMiddleware == null) ? [] : createMiddleware.handlers));
Middleware readMiddleware = getAnnotation(this.read, Middleware);
get(
'/:id',
(req, res) async => await this.read(
2017-02-13 00:38:33 +00:00
toId(req.params['id']),
mergeMap([
{'query': req.query},
2017-02-23 00:37:15 +00:00
restProvider,
req.serviceParams
2017-02-13 00:38:33 +00:00
])),
middleware: []
..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
patch(
'/:id',
(req, res) async => await this.modify(
2017-02-13 00:38:33 +00:00
toId(req.params['id']),
2017-03-28 23:29:22 +00:00
await req.lazyBody(),
2017-02-13 00:38:33 +00:00
mergeMap([
{'query': req.query},
2017-02-23 00:37:15 +00:00
restProvider,
req.serviceParams
2017-02-13 00:38:33 +00:00
])),
middleware: []
..addAll(handlers)
..addAll(
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
Middleware updateMiddleware = getAnnotation(this.update, Middleware);
post(
'/:id',
(req, res) async => await this.update(
2017-02-13 00:38:33 +00:00
toId(req.params['id']),
2017-03-28 23:29:22 +00:00
await req.lazyBody(),
mergeMap([
{'query': req.query},
restProvider,
req.serviceParams
])),
middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
2017-03-28 23:29:22 +00:00
put(
'/:id',
(req, res) async => await this.update(
2017-03-28 23:29:22 +00:00
toId(req.params['id']),
await req.lazyBody(),
2017-02-13 00:38:33 +00:00
mergeMap([
{'query': req.query},
2017-02-23 00:37:15 +00:00
restProvider,
req.serviceParams
2017-02-13 00:38:33 +00:00
])),
middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
2017-03-28 23:29:22 +00:00
delete(
'/',
(req, res) async => await this.remove(
2017-03-28 23:29:22 +00:00
null,
mergeMap([
{'query': req.query},
restProvider,
req.serviceParams
])),
middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
delete(
'/:id',
(req, res) async => await this.remove(
2017-02-13 00:38:33 +00:00
toId(req.params['id']),
mergeMap([
{'query': req.query},
2017-02-23 00:37:15 +00:00
restProvider,
req.serviceParams
2017-02-13 00:38:33 +00:00
])),
middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
2017-03-28 23:29:22 +00:00
// REST compliance
put('/', () => throw new AngelHttpException.notFound());
patch('/', () => throw new AngelHttpException.notFound());
}
2017-02-23 00:37:15 +00:00
/// Invoked when this service is wrapped within a [HookedService].
void onHooked(HookedService hookedService) {}
}