2016-09-15 19:53:01 +00:00
|
|
|
library angel_framework.http.controller;
|
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:mirrors';
|
2016-10-22 20:41:36 +00:00
|
|
|
import 'package:angel_route/angel_route.dart';
|
2017-08-03 16:40:21 +00:00
|
|
|
import 'package:meta/meta.dart';
|
2016-09-15 19:53:01 +00:00
|
|
|
import 'metadata.dart';
|
|
|
|
import 'request_context.dart';
|
|
|
|
import 'response_context.dart';
|
|
|
|
import 'routable.dart';
|
2017-09-24 19:43:14 +00:00
|
|
|
import 'server.dart' show Angel;
|
2016-06-27 00:20:42 +00:00
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
/// Supports grouping routes with shared functionality.
|
2016-06-27 00:20:42 +00:00
|
|
|
class Controller {
|
2016-12-31 01:46:41 +00:00
|
|
|
Angel _app;
|
|
|
|
|
2017-03-28 23:29:22 +00:00
|
|
|
/// The [Angel] application powering this controller.
|
2016-12-31 01:46:41 +00:00
|
|
|
Angel get app => _app;
|
2017-03-28 23:29:22 +00:00
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
final bool debug;
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
/// If `true` (default), this class will inject itself as a singleton into the [app]'s container when bootstrapped.
|
|
|
|
final bool injectSingleton;
|
|
|
|
|
|
|
|
/// Middleware to run before all handlers in this class.
|
2016-06-27 00:20:42 +00:00
|
|
|
List middleware = [];
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
/// A mapping of route paths to routes, produced from the [Expose] annotations on this class.
|
2016-09-15 19:53:01 +00:00
|
|
|
Map<String, Route> routeMappings = {};
|
2016-06-27 00:20:42 +00:00
|
|
|
|
2017-03-28 23:29:22 +00:00
|
|
|
Controller({this.debug: false, this.injectSingleton: true});
|
2016-12-31 01:46:41 +00:00
|
|
|
|
2017-08-03 16:40:21 +00:00
|
|
|
@mustCallSuper
|
2017-10-04 14:09:12 +00:00
|
|
|
Future configureServer(Angel app) async {
|
2017-03-28 23:29:22 +00:00
|
|
|
_app = app;
|
|
|
|
|
|
|
|
if (injectSingleton != false) _app.container.singleton(this);
|
2016-10-22 20:41:36 +00:00
|
|
|
|
2016-06-27 00:20:42 +00:00
|
|
|
// Load global expose decl
|
|
|
|
ClassMirror classMirror = reflectClass(this.runtimeType);
|
2016-12-31 02:00:52 +00:00
|
|
|
Expose exposeDecl = findExpose();
|
2016-06-27 00:20:42 +00:00
|
|
|
|
2016-10-22 20:41:36 +00:00
|
|
|
if (exposeDecl == null) {
|
2016-06-27 00:20:42 +00:00
|
|
|
throw new Exception(
|
|
|
|
"All controllers must carry an @Expose() declaration.");
|
2016-10-22 20:41:36 +00:00
|
|
|
}
|
|
|
|
|
2017-10-04 14:09:12 +00:00
|
|
|
var routable = new Routable();
|
2016-11-28 21:48:00 +00:00
|
|
|
app.use(exposeDecl.path, routable);
|
2016-11-23 09:10:47 +00:00
|
|
|
TypeMirror typeMirror = reflectType(this.runtimeType);
|
2016-12-31 01:46:41 +00:00
|
|
|
String name = exposeDecl.as?.isNotEmpty == true
|
|
|
|
? exposeDecl.as
|
|
|
|
: MirrorSystem.getName(typeMirror.simpleName);
|
2016-11-23 09:10:47 +00:00
|
|
|
|
|
|
|
app.controllers[name] = this;
|
2016-10-22 20:41:36 +00:00
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
// Pre-reflect methods
|
|
|
|
InstanceMirror instanceMirror = reflect(this);
|
|
|
|
final handlers = []..addAll(exposeDecl.middleware)..addAll(middleware);
|
|
|
|
final routeBuilder = _routeBuilder(instanceMirror, routable, handlers);
|
|
|
|
classMirror.instanceMembers.forEach(routeBuilder);
|
|
|
|
configureRoutes(routable);
|
|
|
|
}
|
2016-11-23 09:10:47 +00:00
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
Function _routeBuilder(
|
|
|
|
InstanceMirror instanceMirror, Routable routable, List handlers) {
|
|
|
|
return (Symbol methodName, MethodMirror method) {
|
|
|
|
if (method.isRegularMethod &&
|
|
|
|
methodName != #toString &&
|
|
|
|
methodName != #noSuchMethod &&
|
|
|
|
methodName != #call &&
|
|
|
|
methodName != #equals &&
|
|
|
|
methodName != #==) {
|
|
|
|
Expose exposeDecl = method.metadata
|
|
|
|
.map((m) => m.reflectee)
|
|
|
|
.firstWhere((r) => r is Expose, orElse: () => null);
|
|
|
|
|
|
|
|
if (exposeDecl == null) return;
|
|
|
|
|
|
|
|
var reflectedMethod = instanceMirror.getField(methodName).reflectee;
|
|
|
|
var middleware = []..addAll(handlers)..addAll(exposeDecl.middleware);
|
2017-01-12 01:52:06 +00:00
|
|
|
String name = exposeDecl.as?.isNotEmpty == true
|
|
|
|
? exposeDecl.as
|
|
|
|
: MirrorSystem.getName(methodName);
|
2016-12-31 01:46:41 +00:00
|
|
|
|
|
|
|
// Check if normal
|
|
|
|
if (method.parameters.length == 2 &&
|
|
|
|
method.parameters[0].type.reflectedType == RequestContext &&
|
|
|
|
method.parameters[1].type.reflectedType == ResponseContext) {
|
|
|
|
// Create a regular route
|
2017-01-12 01:52:06 +00:00
|
|
|
routeMappings[name] = routable
|
|
|
|
.addRoute(exposeDecl.method, exposeDecl.path, (req, res) async {
|
|
|
|
var result = await reflectedMethod(req, res);
|
|
|
|
return result is RequestHandler ? await result(req, res) : result;
|
|
|
|
}, middleware: middleware);
|
2016-12-31 01:46:41 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-06-27 00:20:42 +00:00
|
|
|
|
2017-01-15 19:52:14 +00:00
|
|
|
var injection = preInject(reflectedMethod);
|
|
|
|
|
|
|
|
if (exposeDecl?.allowNull?.isNotEmpty == true)
|
|
|
|
injection.optional?.addAll(exposeDecl.allowNull);
|
|
|
|
|
|
|
|
routeMappings[name] = routable.addRoute(exposeDecl.method,
|
|
|
|
exposeDecl.path, handleContained(reflectedMethod, injection),
|
2016-12-31 01:46:41 +00:00
|
|
|
middleware: middleware);
|
2016-06-27 00:20:42 +00:00
|
|
|
}
|
2016-11-23 09:10:47 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
/// Used to add additional routes to the router from within a [Controller].
|
|
|
|
void configureRoutes(Routable routable) {}
|
2016-12-31 02:00:52 +00:00
|
|
|
|
|
|
|
/// Finds the [Expose] declaration for this class.
|
|
|
|
Expose findExpose() => reflectClass(runtimeType)
|
|
|
|
.metadata
|
|
|
|
.map((m) => m.reflectee)
|
|
|
|
.firstWhere((r) => r is Expose, orElse: () => null);
|
2016-12-31 01:46:41 +00:00
|
|
|
}
|