-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathrouter_entry.dart
More file actions
136 lines (117 loc) · 4.34 KB
/
router_entry.dart
File metadata and controls
136 lines (117 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:async';
import 'package:shelf/shelf.dart';
/// Check if the [regexp] is non-capturing.
bool _isNoCapture(String regexp) {
// Construct a new regular expression matching anything containing regexp,
// then match with empty-string and count number of groups.
return RegExp('^(?:$regexp)|.*\$').firstMatch('')!.groupCount == 0;
}
/// Entry in the router.
///
/// This class implements the logic for matching the path pattern.
class RouterEntry {
/// Pattern for parsing the route pattern
static final RegExp _parser = RegExp(r'([^<]*)(?:<([^>|]+)(?:\|([^>]*))?>)?');
final String verb, route;
final Function _handler;
final Middleware _middleware;
/// If the arguments should be applied or not to the handler function.
/// This is useful to have as false when there is
/// internal logic that registers routes and the number of expected arguments
/// by the user is unknown. i.e: [Router.mount]
/// When this is false, this [RouterEntry] is provided as an argument along
/// the [Request] so that the caller can read information from the route.
final bool _applyParamsOnHandle;
/// Expression that the request path must match.
///
/// This also captures any parameters in the route pattern.
final RegExp _routePattern;
/// Names for the parameters in the route pattern.
final List<String> _params;
/// List of parameter names in the route pattern.
List<String> get params => _params.toList(); // exposed for using generator.
RouterEntry._(this.verb, this.route, this._handler, this._middleware,
this._routePattern, this._params, this._applyParamsOnHandle);
factory RouterEntry(
String verb,
String route,
Function handler, {
Middleware? middleware,
bool applyParamsOnHandle = true,
}) {
middleware = middleware ?? ((Handler fn) => fn);
if (!route.startsWith('/')) {
throw ArgumentError.value(
route, 'route', 'expected route to start with a slash');
}
final params = <String>[];
var pattern = '';
for (var m in _parser.allMatches(route)) {
pattern += RegExp.escape(m[1]!);
if (m[2] != null) {
params.add(m[2]!);
if (m[3] != null && !_isNoCapture(m[3]!)) {
throw ArgumentError.value(
route, 'route', 'expression for "${m[2]}" is capturing');
}
pattern += '(${m[3] ?? r'[^/]+'})';
}
}
final routePattern = RegExp('^$pattern\$');
return RouterEntry._(
verb,
route,
handler,
middleware,
routePattern,
params,
applyParamsOnHandle,
);
}
/// Returns a map from parameter name to value, if the path matches the
/// route pattern. Otherwise returns null.
Map<String, String>? match(String path) {
// Check if path matches the route pattern
var m = _routePattern.firstMatch(path);
if (m == null) {
return null;
}
// Construct map from parameter name to matched value
var params = <String, String>{};
for (var i = 0; i < _params.length; i++) {
// first group is always the full match, we ignore this group.
params[_params[i]] = m[i + 1]!;
}
return params;
}
// invoke handler with given request and params
Future<Response> invoke(Request request, Map<String, String> params) async {
request = request.change(context: {'shelf_router/params': params});
return await _middleware((request) async {
if (!_applyParamsOnHandle) {
// We handle the request just providing this route
return await _handler(request, this) as Response;
}
if (_handler is Handler || _params.isEmpty) {
return await _handler(request) as Response;
}
return await Function.apply(_handler, [
request,
..._params.map((n) => params[n]),
]) as Response;
})(request);
}
}