platform/packages/route/lib/src/router.dart

494 lines
15 KiB
Dart
Raw Normal View History

2016-11-22 03:31:09 +00:00
library angel_route.src.router;
2019-11-28 17:46:57 +00:00
import 'dart:async';
2017-11-27 02:21:19 +00:00
import 'package:combinator/combinator.dart';
import 'package:string_scanner/string_scanner.dart';
2018-08-20 18:59:51 +00:00
2017-10-09 13:39:14 +00:00
import '../string_util.dart';
2018-08-20 18:59:51 +00:00
import 'routing_exception.dart';
2017-11-27 02:21:19 +00:00
part 'grammar.dart';
2016-11-22 03:31:09 +00:00
part 'route.dart';
2016-11-25 23:22:33 +00:00
part 'routing_result.dart';
2018-08-20 18:59:51 +00:00
part 'symlink_route.dart';
2019-11-28 17:40:32 +00:00
//final RegExp _param = RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?');
//final RegExp _rgxEnd = RegExp(r'\$+$');
//final RegExp _rgxStart = RegExp(r'^\^+');
2018-08-20 18:59:51 +00:00
//final RegExp _rgxStraySlashes =
2019-11-28 17:40:32 +00:00
// RegExp(r'(^((\\+/)|(/))+)|(((\\+/)|(/))+$)');
//final RegExp _slashDollar = RegExp(r'/+\$');
final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
2016-10-12 17:58:32 +00:00
/// An abstraction over complex [Route] trees. Use this instead of the raw API. :)
2018-08-20 18:59:51 +00:00
class Router<T> {
2018-08-20 19:18:43 +00:00
final Map<String, Iterable<RoutingResult<T>>> _cache = {};
2018-08-20 18:59:51 +00:00
2017-11-26 05:33:17 +00:00
//final List<_ChainedRouter> _chained = [];
2018-08-20 18:59:51 +00:00
final List<T> _middleware = [];
final Map<Pattern, Router<T>> _mounted = {};
final List<Route<T>> _routes = [];
2017-11-26 05:33:17 +00:00
bool _useCache = false;
2016-10-21 03:13:13 +00:00
2019-11-28 17:40:32 +00:00
List<T> get middleware => List<T>.unmodifiable(_middleware);
2016-11-25 23:22:33 +00:00
2018-12-09 02:32:55 +00:00
Map<Pattern, Router<T>> get mounted =>
2019-11-28 17:40:32 +00:00
Map<Pattern, Router<T>>.unmodifiable(_mounted);
2016-11-25 23:22:33 +00:00
2018-08-20 18:59:51 +00:00
List<Route<T>> get routes {
return _routes.fold<List<Route<T>>>([], (out, route) {
if (route is SymlinkRoute<T>) {
var childRoutes =
route.router.routes.fold<List<Route<T>>>([], (out, r) {
2017-10-08 22:44:11 +00:00
return out
..add(
2019-11-28 17:40:32 +00:00
route.path.isEmpty ? r : Route.join(route, r),
2017-10-08 22:44:11 +00:00
);
});
return out..addAll(childRoutes);
} else {
return out..add(route);
}
});
2017-03-17 17:36:04 +00:00
}
2016-10-12 17:58:32 +00:00
/// Provide a `root` to make this Router revolve around a pre-defined route.
/// Not recommended.
2017-11-28 21:07:14 +00:00
Router();
2016-10-12 17:58:32 +00:00
2017-11-26 05:33:17 +00:00
/// Enables the use of a cache to eliminate the overhead of consecutive resolutions of the same path.
void enableCache() {
_useCache = true;
}
2016-10-12 17:58:32 +00:00
/// Adds a route that responds to the given path
/// for requests with the given method (case-insensitive).
/// Provide '*' as the method to respond to all methods.
2021-04-03 05:50:52 +00:00
Route<T> addRoute(String method, String path, T handler,
2021-05-07 05:21:13 +00:00
{Iterable<T> middleware = const []}) {
2019-11-28 17:40:32 +00:00
if (_useCache == true) {
throw StateError('Cannot add routes after caching is enabled.');
}
2017-11-26 05:33:17 +00:00
2016-11-25 23:22:33 +00:00
// Check if any mounted routers can match this
2018-08-20 18:59:51 +00:00
final handlers = <T>[handler];
2021-05-07 05:21:13 +00:00
//middleware ??= <T>[];
2021-04-03 05:50:52 +00:00
handlers.insertAll(0, middleware);
2019-11-28 17:40:32 +00:00
final route = Route<T>(path, method: method, handlers: handlers);
2016-11-25 23:22:33 +00:00
_routes.add(route);
2017-11-27 02:21:19 +00:00
return route;
2016-10-12 17:58:32 +00:00
}
2018-08-20 18:59:51 +00:00
/// Prepends the given [middleware] to any routes created
2016-11-27 22:24:30 +00:00
/// by the resulting router.
2017-03-17 17:36:04 +00:00
///
2016-11-27 22:24:30 +00:00
/// The resulting router can be chained, too.
2018-08-20 18:59:51 +00:00
_ChainedRouter<T> chain(Iterable<T> middleware) {
2019-11-28 17:40:32 +00:00
var piped = _ChainedRouter<T>(this, middleware);
var route = SymlinkRoute<T>('/', piped);
2017-10-08 22:44:11 +00:00
_routes.add(route);
return piped;
2017-03-17 17:36:04 +00:00
}
2016-11-27 22:24:30 +00:00
2016-11-23 18:58:34 +00:00
/// Returns a [Router] with a duplicated version of this tree.
2018-08-20 18:59:51 +00:00
Router<T> clone() {
2019-11-28 17:40:32 +00:00
final router = Router<T>();
final newMounted = Map<Pattern, Router<T>>.from(mounted);
2016-11-25 23:22:33 +00:00
2018-08-20 18:59:51 +00:00
for (var route in routes) {
if (route is! SymlinkRoute<T>) {
2016-11-25 23:22:33 +00:00
router._routes.add(route.clone());
2018-08-20 18:59:51 +00:00
} else if (route is SymlinkRoute<T>) {
2016-11-27 04:04:47 +00:00
final newRouter = route.router.clone();
newMounted[route.path] = newRouter;
2019-11-28 17:40:32 +00:00
final symlink = SymlinkRoute<T>(route.path, newRouter);
2016-11-27 04:04:47 +00:00
router._routes.add(symlink);
2016-11-25 23:22:33 +00:00
}
2016-11-23 18:58:34 +00:00
}
2016-11-25 23:22:33 +00:00
return router.._mounted.addAll(newMounted);
2016-11-23 18:58:34 +00:00
}
2016-10-12 17:58:32 +00:00
/// Creates a visual representation of the route hierarchy and
/// passes it to a callback. If none is provided, `print` is called.
void dumpTree(
2021-04-03 05:50:52 +00:00
{Function(String tree)? callback,
2019-04-11 15:51:38 +00:00
String header = 'Dumping route tree:',
String tab = ' '}) {
2019-11-28 17:40:32 +00:00
final buf = StringBuffer();
2021-04-03 05:50:52 +00:00
var tabs = 0;
2016-10-12 17:58:32 +00:00
2021-04-03 05:50:52 +00:00
if (header.isNotEmpty) {
2016-11-25 23:22:33 +00:00
buf.writeln(header);
}
2016-10-20 09:21:59 +00:00
2016-11-27 04:04:47 +00:00
buf.writeln('<root>');
2021-04-03 05:50:52 +00:00
void indent() {
for (var i = 0; i < tabs; i++) {
2019-11-28 17:40:32 +00:00
buf.write(tab);
}
2016-11-25 23:22:33 +00:00
}
2016-10-12 17:58:32 +00:00
2021-04-03 05:50:52 +00:00
void dumpRouter(Router router) {
2016-11-25 23:22:33 +00:00
indent();
tabs++;
2016-10-12 17:58:32 +00:00
2021-04-03 05:50:52 +00:00
for (var route in router.routes) {
2016-11-25 23:22:33 +00:00
indent();
2017-11-27 02:21:19 +00:00
buf.write('- ');
if (route is! SymlinkRoute) buf.write('${route.method} ');
buf.write('${route.path.isNotEmpty ? route.path : '/'}');
2016-10-12 17:58:32 +00:00
2018-08-20 19:18:43 +00:00
if (route is SymlinkRoute<T>) {
2016-11-25 23:22:33 +00:00
buf.writeln();
dumpRouter(route.router);
} else {
2021-04-03 05:50:52 +00:00
buf.writeln(' => ${route.handlers.length} handler(s)');
2016-11-25 23:22:33 +00:00
}
2016-10-23 00:52:28 +00:00
}
2016-10-12 17:58:32 +00:00
tabs--;
}
2016-11-25 23:22:33 +00:00
dumpRouter(this);
2016-10-12 17:58:32 +00:00
(callback ?? print)(buf.toString());
2016-10-12 17:58:32 +00:00
}
/// Creates a route, and allows you to add child routes to it
/// via a [Router] instance.
///
/// Returns the created route.
/// You can also register middleware within the router.
2021-04-03 05:50:52 +00:00
SymlinkRoute<T> group(String path, void Function(Router<T> router) callback,
2021-05-07 05:21:13 +00:00
{Iterable<T> middleware = const [], String name = ''}) {
2019-11-28 17:40:32 +00:00
final router = Router<T>().._middleware.addAll(middleware);
2017-11-28 21:07:14 +00:00
callback(router);
2018-08-20 18:59:51 +00:00
return mount(path, router)..name = name;
2016-11-25 23:22:33 +00:00
}
2016-10-12 17:58:32 +00:00
2019-11-28 17:46:57 +00:00
/// Asynchronous equivalent of [group].
Future<SymlinkRoute<T>> groupAsync(
2021-04-03 05:50:52 +00:00
String path, FutureOr<void> Function(Router<T> router) callback,
2021-05-07 05:21:13 +00:00
{Iterable<T> middleware = const [], String name = ''}) async {
2019-11-28 17:46:57 +00:00
final router = Router<T>().._middleware.addAll(middleware);
await callback(router);
return mount(path, router)..name = name;
}
2016-11-25 23:22:33 +00:00
/// Generates a URI string based on the given input.
/// Handy when you have named routes.
///
/// Each item in `linkParams` should be a [Route],
/// `String` or `Map<String, dynamic>`.
///
/// Strings should be route names, namespaces, or paths.
/// Maps should be parameters, which will be filled
/// into the previous route.
///
/// Paths and segments should correspond to the way
/// you declared them.
///
/// For example, if you declared a route group on
/// `'users/:id'`, it would not be resolved if you
/// passed `'users'` in [linkParams].
///
/// Leading and trailing slashes are automatically
/// removed.
///
/// Set [absolute] to `true` to insert a forward slash
/// before the generated path.
///
/// Example:
/// ```dart
/// router.navigate(['users/:id', {'id': '1337'}, 'profile']);
/// ```
2019-04-11 15:51:38 +00:00
String navigate(Iterable linkParams, {bool absolute = true}) {
2021-04-03 05:50:52 +00:00
final segments = <String>[];
2016-11-25 23:22:33 +00:00
Router search = this;
2021-03-18 00:11:45 +00:00
Route? lastRoute;
2016-11-25 23:22:33 +00:00
for (final param in linkParams) {
2021-04-03 05:50:52 +00:00
var resolved = false;
2016-11-25 23:22:33 +00:00
if (param is String) {
// Search by name
2021-04-03 05:50:52 +00:00
for (var route in search.routes) {
2016-11-25 23:22:33 +00:00
if (route.name == param) {
segments.add(route.path.replaceAll(_straySlashes, ''));
lastRoute = route;
2018-08-20 19:18:43 +00:00
if (route is SymlinkRoute<T>) {
2016-11-25 23:22:33 +00:00
search = route.router;
}
resolved = true;
break;
}
}
// Search by path
2017-11-27 02:21:19 +00:00
if (!resolved) {
2019-11-28 17:40:32 +00:00
var scanner = SpanScanner(param.replaceAll(_straySlashes, ''));
2021-04-03 05:50:52 +00:00
for (var route in search.routes) {
var pos = scanner.position;
var parseResult = route.parser?.parse(scanner);
if (parseResult != null) {
if (parseResult.successful && scanner.isDone) {
segments.add(route.path.replaceAll(_straySlashes, ''));
lastRoute = route;
if (route is SymlinkRoute<T>) {
search = route.router;
}
resolved = true;
break;
} else {
scanner.position = pos;
2017-11-27 02:21:19 +00:00
}
2019-11-28 17:40:32 +00:00
} else {
2017-11-27 02:21:19 +00:00
scanner.position = pos;
2019-11-28 17:40:32 +00:00
}
2016-11-25 23:22:33 +00:00
}
}
if (!resolved) {
2019-11-28 17:40:32 +00:00
throw RoutingException(
2016-11-25 23:22:33 +00:00
'Cannot resolve route for link param "$param".');
}
} else if (param is Route) {
segments.add(param.path.replaceAll(_straySlashes, ''));
} else if (param is Map<String, dynamic>) {
if (lastRoute == null) {
2019-11-28 17:40:32 +00:00
throw RoutingException(
2016-11-25 23:22:33 +00:00
'Maps in link params must be preceded by a Route or String.');
} else {
segments.removeLast();
segments.add(lastRoute.makeUri(param).replaceAll(_straySlashes, ''));
}
2019-11-28 17:40:32 +00:00
} else {
throw RoutingException(
2016-11-25 23:22:33 +00:00
'Link param $param is not Route, String, or Map<String, dynamic>.');
2019-11-28 17:40:32 +00:00
}
2016-10-12 17:58:32 +00:00
}
2016-11-25 23:22:33 +00:00
return absolute
? '/${segments.join('/').replaceAll(_straySlashes, '')}'
: segments.join('/');
2016-10-12 17:58:32 +00:00
}
2016-11-22 03:31:09 +00:00
/// Finds the first [Route] that matches the given path,
/// with the given method.
2021-05-09 11:16:15 +00:00
bool resolve(String absolute, String relative, List<RoutingResult<T?>> out,
2019-04-11 15:51:38 +00:00
{String method = 'GET', bool strip = true}) {
2017-11-25 00:26:06 +00:00
final cleanRelative =
2021-04-03 05:50:52 +00:00
strip == false ? relative : stripStraySlashes(relative);
2019-11-28 17:40:32 +00:00
var scanner = SpanScanner(cleanRelative);
2016-11-22 03:31:09 +00:00
2018-08-20 19:34:00 +00:00
bool crawl(Router<T> r) {
2021-04-03 05:50:52 +00:00
var success = false;
2017-11-27 02:21:19 +00:00
2018-08-20 19:34:00 +00:00
for (var route in r.routes) {
2021-04-03 05:50:52 +00:00
var pos = scanner.position;
2017-11-27 02:21:19 +00:00
2018-08-20 19:18:43 +00:00
if (route is SymlinkRoute<T>) {
2021-04-03 05:50:52 +00:00
if (route.parser != null) {
var pp = route.parser!;
if (pp.parse(scanner).successful) {
var s = crawl(route.router);
if (s) success = true;
}
2016-11-27 22:24:30 +00:00
}
2017-11-27 02:21:19 +00:00
scanner.position = pos;
} else if (route.method == '*' || route.method == method) {
2021-04-03 05:50:52 +00:00
var parseResult = route.parser?.parse(scanner);
if (parseResult != null) {
if (parseResult.successful && scanner.isDone) {
var tailResult = parseResult.value?.tail ?? '';
2021-05-11 09:03:52 +00:00
//print(tailResult);
2021-04-03 05:50:52 +00:00
var result = RoutingResult<T>(
parseResult: parseResult,
params: parseResult.value!.params,
shallowRoute: route,
shallowRouter: this,
tail: tailResult + scanner.rest);
out.add(result);
success = true;
}
2017-11-27 02:21:19 +00:00
}
scanner.position = pos;
2016-11-23 09:13:42 +00:00
}
2016-11-27 22:24:30 +00:00
}
2017-11-27 02:21:19 +00:00
return success;
2016-11-22 03:31:09 +00:00
}
2017-11-27 02:21:19 +00:00
return crawl(this);
2016-11-23 18:58:34 +00:00
}
2016-11-27 22:24:30 +00:00
/// Returns the result of [resolve] with [path] passed as
/// both `absolute` and `relative`.
2021-05-11 09:03:52 +00:00
Iterable<RoutingResult<T>> resolveAbsolute(String path,
2019-04-11 15:51:38 +00:00
{String method = 'GET', bool strip = true}) =>
2017-11-25 00:26:06 +00:00
resolveAll(path, path, method: method, strip: strip);
2016-11-27 22:24:30 +00:00
2016-11-25 23:22:33 +00:00
/// Finds every possible [Route] that matches the given path,
/// with the given method.
2021-05-11 09:03:52 +00:00
Iterable<RoutingResult<T>> resolveAll(String absolute, String relative,
2019-04-11 15:51:38 +00:00
{String method = 'GET', bool strip = true}) {
2017-11-26 05:33:17 +00:00
if (_useCache == true) {
2017-11-28 18:07:27 +00:00
return _cache.putIfAbsent('$method$absolute',
2017-11-26 05:33:17 +00:00
() => _resolveAll(absolute, relative, method: method, strip: strip));
}
return _resolveAll(absolute, relative, method: method, strip: strip);
}
2021-04-03 05:50:52 +00:00
Iterable<RoutingResult<T>> _resolveAll(String absolute, String relative,
2019-04-11 15:51:38 +00:00
{String method = 'GET', bool strip = true}) {
2018-08-20 19:18:43 +00:00
var results = <RoutingResult<T>>[];
2017-11-25 00:26:06 +00:00
resolve(absolute, relative, results, method: method, strip: strip);
2016-11-22 03:31:09 +00:00
2017-06-06 12:48:55 +00:00
// _printDebug(
// 'Results of $method "/${absolute.replaceAll(_straySlashes, '')}": ${results.map((r) => r.route).toList()}');
2016-11-25 23:22:33 +00:00
return results;
}
2016-10-12 17:58:32 +00:00
/// Incorporates another [Router]'s routes into this one's.
2018-08-20 18:59:51 +00:00
SymlinkRoute<T> mount(String path, Router<T> router) {
2019-11-28 17:40:32 +00:00
final route = SymlinkRoute<T>(path, router);
2016-11-27 04:04:47 +00:00
_mounted[route.path] = router;
2016-11-25 23:22:33 +00:00
_routes.add(route);
2019-11-28 17:40:32 +00:00
//route._head = RegExp(route.matcher.pattern.replaceAll(_rgxEnd, ''));
2016-11-22 03:31:09 +00:00
2018-08-20 18:59:51 +00:00
return route;
2016-10-12 17:58:32 +00:00
}
/// Adds a route that responds to any request matching the given path.
2021-05-07 05:21:13 +00:00
Route<T> all(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('*', path, handler, middleware: middleware);
}
/// Adds a route that responds to a DELETE request.
2021-05-07 05:21:13 +00:00
Route<T> delete(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('DELETE', path, handler, middleware: middleware);
}
/// Adds a route that responds to a GET request.
2021-05-07 05:21:13 +00:00
Route<T> get(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('GET', path, handler, middleware: middleware);
}
/// Adds a route that responds to a HEAD request.
2021-05-07 05:21:13 +00:00
Route<T> head(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('HEAD', path, handler, middleware: middleware);
}
/// Adds a route that responds to a OPTIONS request.
2021-05-07 05:21:13 +00:00
Route<T> options(String path, T handler,
{Iterable<T> middleware = const {}}) {
2016-10-12 17:58:32 +00:00
return addRoute('OPTIONS', path, handler, middleware: middleware);
}
/// Adds a route that responds to a POST request.
2021-05-07 05:21:13 +00:00
Route<T> post(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('POST', path, handler, middleware: middleware);
}
/// Adds a route that responds to a PATCH request.
2021-05-07 05:21:13 +00:00
Route<T> patch(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('PATCH', path, handler, middleware: middleware);
}
/// Adds a route that responds to a PUT request.
2021-05-07 05:21:13 +00:00
Route put(String path, T handler, {Iterable<T> middleware = const []}) {
2016-10-12 17:58:32 +00:00
return addRoute('PUT', path, handler, middleware: middleware);
}
}
2016-11-27 22:24:30 +00:00
2018-08-20 18:59:51 +00:00
class _ChainedRouter<T> extends Router<T> {
final List<T> _handlers = <T>[];
2021-04-03 05:50:52 +00:00
Router _root;
2016-11-27 22:24:30 +00:00
2021-04-03 05:50:52 +00:00
_ChainedRouter.empty() : _root = Router();
2016-11-27 22:24:30 +00:00
2021-04-03 05:50:52 +00:00
_ChainedRouter(this._root, Iterable<T> middleware) {
2018-08-20 18:59:51 +00:00
_handlers.addAll(middleware);
2016-11-27 22:24:30 +00:00
}
@override
2021-04-03 05:50:52 +00:00
Route<T> addRoute(String method, String path, handler,
2021-05-07 05:21:13 +00:00
{Iterable<T> middleware = const []}) {
2021-04-03 05:50:52 +00:00
var route = super.addRoute(method, path, handler,
middleware: [..._handlers, ...middleware]);
2017-10-08 22:44:11 +00:00
//_root._routes.add(route);
return route;
2016-11-27 22:24:30 +00:00
}
2019-04-11 15:48:01 +00:00
@override
2021-04-03 05:50:52 +00:00
SymlinkRoute<T> group(String path, void Function(Router<T> router) callback,
2021-05-07 05:21:13 +00:00
{Iterable<T> middleware = const [], String? name}) {
2021-04-03 05:50:52 +00:00
final router = _ChainedRouter<T>(_root, [..._handlers, ...middleware]);
2017-11-28 21:07:14 +00:00
callback(router);
2018-08-20 18:59:51 +00:00
return mount(path, router)..name = name;
2017-03-17 17:36:04 +00:00
}
2019-11-28 17:46:57 +00:00
@override
Future<SymlinkRoute<T>> groupAsync(
2021-04-03 05:50:52 +00:00
String path, FutureOr<void> Function(Router<T> router) callback,
2021-05-07 05:21:13 +00:00
{Iterable<T> middleware = const [], String? name}) async {
2021-04-03 05:50:52 +00:00
final router = _ChainedRouter<T>(_root, [..._handlers, ...middleware]);
2019-11-28 17:46:57 +00:00
await callback(router);
return mount(path, router)..name = name;
}
2016-11-27 22:24:30 +00:00
@override
2018-08-20 18:59:51 +00:00
SymlinkRoute<T> mount(String path, Router<T> router) {
2021-04-03 05:50:52 +00:00
final route = super.mount(path, router);
2016-11-27 22:24:30 +00:00
route.router._middleware.insertAll(0, _handlers);
2017-10-08 22:44:11 +00:00
//_root._routes.add(route);
2016-11-27 22:24:30 +00:00
return route;
}
@override
2018-08-20 18:59:51 +00:00
_ChainedRouter<T> chain(Iterable<T> middleware) {
2019-11-28 17:40:32 +00:00
final piped = _ChainedRouter<T>.empty().._root = _root;
2021-04-03 05:50:52 +00:00
piped._handlers.addAll([..._handlers, ...middleware]);
2019-11-28 17:40:32 +00:00
var route = SymlinkRoute<T>('/', piped);
2017-10-08 22:44:11 +00:00
_routes.add(route);
return piped;
2016-11-27 22:24:30 +00:00
}
}
2017-11-26 05:33:17 +00:00
/// Optimizes a router by condensing all its routes into one level.
2018-08-20 19:18:43 +00:00
Router<T> flatten<T>(Router<T> router) {
2019-11-28 17:40:32 +00:00
var flattened = Router<T>();
2017-11-26 05:33:17 +00:00
for (var route in router.routes) {
2018-08-20 19:18:43 +00:00
if (route is SymlinkRoute<T>) {
2017-11-26 05:33:17 +00:00
var base = route.path.replaceAll(_straySlashes, '');
var child = flatten(route.router);
for (var route in child.routes) {
var path = route.path.replaceAll(_straySlashes, '');
var joined = '$base/$path'.replaceAll(_straySlashes, '');
flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''),
2021-04-03 05:50:52 +00:00
route.handlers.last,
2017-11-26 05:33:17 +00:00
middleware:
2021-04-03 05:50:52 +00:00
route.handlers.take(route.handlers.length - 1).toList());
2017-11-26 05:33:17 +00:00
}
} else {
2021-04-03 05:50:52 +00:00
flattened.addRoute(route.method, route.path, route.handlers.last,
middleware: route.handlers.take(route.handlers.length - 1).toList());
2017-11-26 05:33:17 +00:00
}
}
2017-11-27 23:42:50 +00:00
return flattened..enableCache();
2017-11-26 05:33:17 +00:00
}