-
Notifications
You must be signed in to change notification settings - Fork 8
/
Router.ts
152 lines (131 loc) · 5.82 KB
/
Router.ts
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// TODO: look at whether we can remove the dependency on underscore
import _ from 'underscore';
import {
IRouter,
ProcessorOrProcessors,
PathParams,
RouterOptions,
NextCallback,
ErrorHandlingRequestProcessor,
} from './interfaces';
import { IRequestMatchingProcessorChain, IProcessorChain } from './chains/ProcessorChain';
import { Request, Response } from '.';
import { wrapRequestProcessor, wrapRequestProcessors } from './utils/wrapRequestProcessor';
import { RouteMatchingProcessorChain } from './chains/RouteMatchingProcessorChain';
import { MatchAllRequestsProcessorChain } from './chains/MatchAllRequestsProcessorChain';
import { SubRouterProcessorChain } from './chains/SubRouterProcessorChain';
const DEFAULT_OPTS: RouterOptions = {
caseSensitive: false,
};
export default class Router implements IRouter {
public readonly routerOptions: RouterOptions;
private readonly _processors: IRequestMatchingProcessorChain[] = [];
public constructor(options?: RouterOptions) {
this.routerOptions = _.defaults(options, DEFAULT_OPTS);
}
// TODO: do we need `router.route`?
// https://expressjs.com/en/guide/routing.html#app-route
// https://expressjs.com/en/4x/api.html#router.route
// If we do add it, we need to set the case-sensitivity of the sub-router it creates
// using the case-sensitivity setting of this router.
public handle(originalErr: unknown, req: Request, resp: Response, done: NextCallback): void {
const processors = _.filter(this._processors, (p) => { return p.matches(req); });
const go = _.reduce(processors.reverse(), (next: NextCallback, p: IProcessorChain): NextCallback => {
return (err) => {
p.run(err, req, resp, next);
};
}, done);
go(originalErr);
}
/**
* Mounts a sub-router to this router. In Express, this is part of the overloaded `use`
* method, but we separated it out in Lambda Express to allow better type safety and
* code hinting / auto-completion.
*/
public addSubRouter(path: PathParams, router: Router): this {
// Note: this overriding of the case sensitivity of the passed-in router is likely
// ineffective for most usecases because the user probably created their router and
// added a bunch of routes to it before adding that router to this one. When the
// routes are created (technically the `IRequestMatchingProcessorChain` objects, in
// particular `RouteMatchingProcessorChain`), the case sensitivity is already set
// (inherited from the router that created the chain).
router.routerOptions.caseSensitive = this.routerOptions.caseSensitive;
this._processors.push(new SubRouterProcessorChain(path, router));
return this;
}
/**
* Mounts middleware, error handlers, or route handlers to a specific HTTP method and
* route. Not included in standard Express, this is specific to Lambda Express.
*
* Note that this method creates a route-chain of all the handlers passed to it so that
* they are treated as a single handler. That allows any of the handlers passed to this
* method to call `next('route')` to skip to the next route handler (or route handler
* chain) for this route.
*/
public mount(method: string | undefined, path: PathParams, ...processors: ProcessorOrProcessors[]): this {
const wrapped: ErrorHandlingRequestProcessor[] = wrapRequestProcessors(_.flatten(processors)),
isCaseSensitive = this.routerOptions.caseSensitive;
this._processors.push(new RouteMatchingProcessorChain(wrapped, path, isCaseSensitive, method));
return this;
}
/**
* Express-standard routing method for adding middleware and handlers that get invoked
* for all routes handled by this router.
*/
public use(...processors: ProcessorOrProcessors[]): this {
_.each(_.flatten(processors), (rp: ErrorHandlingRequestProcessor) => {
this._processors.push(new MatchAllRequestsProcessorChain([ wrapRequestProcessor(rp) ]));
});
return this;
}
/**
* Express-standard routing method for adding handlers that get invoked regardless of
* the request method (e.g. `OPTIONS`, `GET`, `POST`, etc) for a specific path (or set
* of paths).
*/
public all(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount(undefined, path, ...processors);
}
/**
* Express-standard routing method for `HEAD` requests.
*/
public head(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('HEAD', path, ...processors);
}
/**
* Express-standard routing method for `GET` requests.
*/
public get(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('GET', path, ...processors);
}
/**
* Express-standard routing method for `POST` requests.
*/
public post(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('POST', path, ...processors);
}
/**
* Express-standard routing method for `PUT` requests.
*/
public put(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('PUT', path, ...processors);
}
/**
* Express-standard routing method for `DELETE` requests.
*/
public delete(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('DELETE', path, ...processors);
}
/**
* Express-standard routing method for `PATCH` requests.
*/
public patch(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('PATCH', path, ...processors);
}
/**
* Express-standard routing method for `OPTIONS` requests.
*/
public options(path: PathParams, ...processors: ProcessorOrProcessors[]): this {
return this.mount('OPTIONS', path, ...processors);
}
}