-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(rest): Implement trie-based routing and tidy up path validation #1765
Conversation
c70333d
to
e6d6765
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM at a high level -- I do have some questions to better understand this PR. See comments below.
@@ -127,6 +128,8 @@ export class RoutingTable { | |||
); | |||
} | |||
this._routes.push(route); | |||
// Sort routes | |||
this._routes.sort(compareRoute); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What benefit do we get by sorting the routes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think about this example:
/**
* A simple controller to bounce back http requests
*/
export class PingController {
constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {}
@get('/ping/:me')
pingMe(@param.path.string('me') me: string): object {
return {
greeting: `Hello from ${me}`,
};
}
// Map to `GET /ping`
@get('/ping')
ping(): object {
return {
greeting: 'Hello from LoopBack',
date: new Date(),
url: this.req.url,
headers: Object.assign({}, this.req.headers),
};
}
@get('/ping/xyz')
pingXyz(): object {
return {
greeting: 'Hello from XYZ',
};
}
}
I would expect GET /ping/xyz
is routed to pingXyz()
instead of pingMe()
. Without this PR, it matches GET /ping/:me
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add an integration test.
expect(path).to.eql('/{foo}'); | ||
}); | ||
|
||
it('allows /:foo/bar', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're going to start supporting express style of parameters? If so, we should document it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't do any check before this PR. I think it will be good to allow express style too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't do any check before this PR. I think it will be good to allow express style too.
I would rather not introduce multiple parameter styles, I am concerned about the confusion it can create and the extra work it will add for any consumer of routes.
@@ -80,7 +81,7 @@ function resolveControllerSpec(constructor: Function): ControllerSpec { | |||
|
|||
const endpoint = endpoints[op]; | |||
const verb = endpoint.verb!; | |||
const path = endpoint.path!; | |||
const path = toOpenApiPath(endpoint.path!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm ... why is path
not a required property? (If it was we wouldn't need the !
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If path
is missing, it's default to /
'.
0833be5
to
b595511
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work; I've left some comments, but LGTM overall.
export function compareRoute( | ||
route1: Pick<RouteEntry, 'verb' | 'path'>, | ||
route2: Pick<RouteEntry, 'verb' | 'path'>, | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have the return type here as number
?
route2: Pick<RouteEntry, 'verb' | 'path'>, | ||
) { | ||
// First check verb | ||
const verb1 = HTTP_VERBS[route1.verb.toLowerCase()] || 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For clarification, why do we set the default verb to patch
here?
} | ||
|
||
// Now check token by token | ||
for (let i = 0; i < tokensForPath1.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have test coverage for this code path?
b595511
to
267017d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not very happy about this change at high level. For medium to long term, we should abandon Express router and use a more performant alternative (probably based on Trie). This was planned all the way back in LoopBack pre-3.0 days, and we have a loopback-next issue keeping track of this work too - see #98.
I would prefer to switch to the new router first and only then start adding new features like express-styled parameters, to ensure we don't accidentally add an Express-specific feature that is difficult to support with the new router.
Having said that, the example where /ping/{me}
takes precedence over /ping/xyz
is clearly a bug (a violation of OpenAPI Spec) that should be fixed.
expect(path).to.eql('/{foo}'); | ||
}); | ||
|
||
it('allows /:foo/bar', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't do any check before this PR. I think it will be good to allow express style too.
I would rather not introduce multiple parameter styles, I am concerned about the confusion it can create and the extra work it will add for any consumer of routes.
I can update the PR to report errors if Express styles are used for the GA. Here is what led me to create this PR:
|
267017d
to
8983d99
Compare
@bajtos I reverted the sorting based approach and implemented trie-based routing based on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
let tokens = pathToRegExp.parse(path); | ||
if (tokens.some(t => typeof t === 'object')) { | ||
throw new Error( | ||
`Invalid path template: '${path}'. Please use {param} instead of ':param'`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
*/ | ||
export function toOpenApiPath(path: string = '/') { | ||
validatePath(path); | ||
return path.replace(/\:([A-Za-z0-9_]+)/g, '{$1}'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this replace
call still needed, considering that validatePath
rejects values containing express-style parameters? Maybe I am missing something?
// Add the route to the trie | ||
const path = route.path.startsWith('/') ? route.path : `/${route.path}`; | ||
const verb = route.verb.toLowerCase() || 'get'; | ||
this._router.on(`/${verb}${path}`, () => {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we store route
on the function passed to this._router.on
to avoid costly lookup (iteration through this._routes
later on? E.g.
// store
this._router.on(`/${verb}${path}`, Object.assign(() => {}, {route}));
// pick up - I hope that's the Wayfarer API
const found = this._router.match(path);
const route = found.route;
.expect(200, 'hello john'); | ||
await whenIMakeRequestTo(app) | ||
.get('/greet/world') | ||
.expect(200, 'hello WORLD'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this test much more than the previous version 👍
Could you also update the benchmark to show the new performance with Wayfarer under the hood? See https://github.com/strongloop/loopback-next/tree/master/benchmark |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks awesome 👍
9338db3
to
1181189
Compare
1181189
to
bc883f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have few more comments I'd like to see addressed.
I don't want to block the progress though, so feel free to address them in a follow-up pull request if you prefer.
debug(' -> found with params: %j', pathParams); | ||
|
||
return createResolvedRoute(this, pathParams); | ||
this.path = path.replace(/{([A-Za-z0-9_]*)}/g, ':$1'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This RegExp will convert /{}
to /:
- is that intentional? Could you please add a test for this case? I think we should preserve /{}
as /{}
and use +
instead of *
in the regular expression.
I see you are allowing only [A-Za-z0-9_]
characters. Is this based on character list allowed by OpenAPI spec? Please add some tests to show what happens when a different character is used in the path param name, e.g. /{foo*}
or /{jméno}
("jméno" is "name" in Czech)
} | ||
|
||
greetWorld() { | ||
return `hello WORLD`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use HELLO WORLD
to make the distinction from greet
more obvious. You can also use regular apostrophes instead of back-ticks.
} | ||
} | ||
|
||
const spec = anOpenApiSpec() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this test setup can be greatly simplified by using app.route
API, see https://loopback.io/doc/en/lb4/Routes.html#using-partial-openapi-spec-fragments
app.route(new Route(
'get',
'/greet/{name}',
anOperationSpec().withParameter({name: 'name', in: 'path', type: 'string'}),
(name: string) => `hello ${name}`,
));
app.route(new Route(
'get',
'/greet/world',
anOperationSpec(),
() => 'HELLO WORLD',
));
In case .route()
is not exposed on RestApplication class, you can call app.restServer.route()
instead (or add the missing RestApplication.prototype.route()
shortcut).
* Validate and normalize the path to OpenAPI path template. No parameter | ||
* modifier, custom pattern, or unnamed parameter is allowed. | ||
* | ||
* @param path Express style path (https://github.com/pillarjs/path-to-regexp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this confusing - the method is used in a way that OpenAPI-styled path is passed in the input.
I think we should remove this method completely and modify resolveControllerSpec
implementation to call validatePath
instead. Thoughts?
On the second thought, I think we should call validatePath
right at the time when a new route is created, regardless of whether the route is Controller-based or handler-based (see app.route
API).
Could you please add some tests to verify that invalid paths are correctly rejected for all possible sources of path strings? I am thinking about the following sources: @get
decorator on a controller method, app.route
/restServer.route
, paths provided in the OpenAPI spec document passed to app.api()
.
b35a395
to
056062a
Compare
I added optimization for those routes that don't have path variables. The |
e2c61d9
to
285e128
Compare
@raymondfeng I looked at your recent changes at a high level and they look great 👍. I'll look into them in detail tomorrow. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a fan of having multiple routers when it's not clear how should LB4 users decided which router to use. Personally, I prefer the framework to make to he hard decisions and let users to focus on delivering business value.
I am also concerned about adding ~1.2k lines of new code that we will have to maintain and fix bug inside.
Having said that, if others thinks this is a good idea, then I am not going to block progress.
At minimum, please add documentation explaining the users how to configure the app to use a different router, explain the trade-offs between our two provided routers so that users can better decide which one to use.
Nice to have: document how to implement a custom router. Most importantly, we need a clear documentation on the assumption made by RestServer about the behavior of a custom router. A test suite that people implementing a custom router can execute to validate their implementation would be ideal.
Extensibility comes with costs if we want to make it user friendly.
packages/rest/src/rest.server.ts
Outdated
this._httpHandler = new HttpHandler(this); | ||
const router = this.getSync(RestBindings.ROUTER, {optional: true}); | ||
let routingTable: RoutingTable; | ||
if (router) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this if
to RoutingTable
constructor so that RestServer can be simplified as follows:
const routingTable = new RoutingTable(router);
packages/rest/src/router/index.ts
Outdated
// License text available at https://opensource.org/licenses/MIT | ||
|
||
export { | ||
RouteEntry, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering how many items we are exporting, I think it may be best to simply export everything.
export * from `./routing-table';
Thoughts?
private readonly _routes: RouteEntry[] = []; | ||
private readonly _router: RestRouter = new RegExpRouter(); | ||
|
||
constructor(router?: RestRouter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
constructor(private readonly router: RestRouter = new RegExpRouter()) {
// ...
}
fe823e7
to
c40c548
Compare
@bajtos Addressed your coding comments and added docs. PTAL. I would like to have it landed with our GA as the mapping of routes without variables are much faster now. |
docs/site/Routing-requests.md
Outdated
|
||
```ts | ||
import {RestBindings} from '@loopback/rest'; | ||
app.bind(RestBindings.ROUTER).toClass(TrieRouter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: Which file would this be in and where can users import TrieRouter
from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
/** | ||
* Check if there is custom router in the context | ||
*/ | ||
const router = this.getSync(RestBindings.ROUTER, {optional: true}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a try/catch
block for the getSync
call here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, as it uses an optional
flag.
const server = await app.getServer(RestServer); | ||
const handler = await server.get(RestBindings.HANDLER); | ||
// tslint:disable-next-line:no-any | ||
expect((handler as any)._routes._router).to.be.instanceof(TrieRouter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: why do we need to cast handler as any
here? can we do const handler = await server.get<HttpHandler>(RestBindings.HANDLER);
above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a hacky way to test non-public properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raymondfeng Please add a comment to the code to preserve this explanation for future readers.
312cb32
to
13a5adb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed the code in details, PTAL at my comments below.
Please update README in benchmark folder to show the new performance statistics. It would be great to have a similar README for your newly added benchmark, one that shows "baseline" data of the current implementation.
I am not very happy about having to review such a large change in the short time we have :(
One of my major concerns is that we don't have a Router test suite that we could run on both RegExpRouter and TrieRouter to verify that all routers are correctly handling all edge cases we have identified so far. Having two sets of tests (one for each router) is not manageable.
I would prefer very much to leave TrieRouter
out of this pull request, probably with BaseRouter
too. Adding an extension point (a custom router) can be easily done post-4.0 GA as it's a semver-minor change. Changing the API contract of an ill-designed extension point is much more difficult as it's a breaking change.
I am not convinced the current design of the extension point is good enough to serve us for the next 6+ months:
- See my comments about BaseRouter design
- There is no easy way how to test all aspects of a new Router to ensure it's fully compliant with our expectations
- It's not clear to me if (and how) can users customize the router at Server vs. Application level.
Let's give us more time to find a good design for Router extensibility and implement a robust solution that's easy to use both for users and Router writers.
packages/rest/package.json
Outdated
@@ -44,7 +44,8 @@ | |||
"path-to-regexp": "^2.2.0", | |||
"qs": "^6.5.2", | |||
"strong-error-handler": "^3.2.0", | |||
"validator": "^10.4.0" | |||
"validator": "^10.4.0", | |||
"wayfarer": "^6.6.4" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT, wayfarer
is not used by our code, can you remove this dependency please?
path: string, | ||
spec: OperationObject, | ||
handler: Function, | ||
): Binding; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use this new API to simplify Routing
doc page - see https://loopback.io/doc/en/lb4/Routes.html#using-partial-openapi-spec-fragments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use this new API to simplify Routing doc page - see https://loopback.io/doc/en/lb4/Routes.html#using-partial-openapi-spec-fragments
This hasn't been addressed AFAICT, please take a look ☝️
const router = this.getSync(RestBindings.ROUTER, {optional: true}); | ||
const routingTable = new RoutingTable(router); | ||
|
||
this._httpHandler = new HttpHandler(this, routingTable); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if it's a good idea for HttpHandler to accept RoutingTable instance. Have you considered an alternative design where HttpHandler accepts a router instance only?
const router = this.getSync(RestBindings.ROUTER, {optional: true});
this._httpHandler = new HttpHandler(this, router);
IMO, the RoutingTable class is an implementation detail of HttpHandler that's not open for extensibility to consumers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note that using the constructor the pass down a version of RestRouter is still internal implementation details, which we can change later. The public contract is to bind a custom RestRouter to the given key.
* | ||
* @param path Path template with `{var}` | ||
*/ | ||
export function toOpenApiPath(path: string = '/') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't understand the purpose of this toOpenApiPath
function - what are the benefits of calling toOpenApiPath
as compared to calling validatePath
directly?
* Validate the path to be compatible with OpenAPI path template. No parameter | ||
* modifier, custom pattern, or unnamed parameter is allowed. | ||
*/ | ||
export function validatePath(path: string = '/') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps validateApiPath
would be a better name, to make it clear that we are validating REST API/URL paths and not filesystem paths?
debug('Route matched: %j', found); | ||
|
||
if (found) { | ||
route = found.node.value!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you fix the type definition of trie.match
to communicate the fact that value
is always set when a route was found?
For example:
export interface ResolvedNode<T> {
node: Node<T> & {value: T}; // the value is always set
// tslint:disable-next-line:no-any
params?: {[name: string]: any};
}
packages/rest/src/router/trie.ts
Outdated
const keys = path.split('/').filter(Boolean); | ||
const params = {}; | ||
const resolved = search(keys, 0, params, this.root); | ||
if (resolved && resolved.node.value != null) return resolved; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (resolved && this.isNodeWithValue(resolved.node)) return resolved;
let i = 0; | ||
for (const n of child.names) { | ||
const val = match[++i]; | ||
resolved.params![n] = decodeURIComponent(val); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid relying on !
parameter?
E.g.
// you should probably define a new type for param map (don't we already have one?)
const params: {[name: string]: any} = {};
let i = 0;
for (const n of child.names) {
const val = match[++i];
params[n] = decodeURIComponent(val);
}
return {params, node: child};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid relying on
!
operator please?
PTAL ☝️
class TestController {} | ||
|
||
const table = givenRoutingTable(); | ||
table.registerController(spec, TestController); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered using route-base approach to avoid the complexity of dealing with controllers?
expect(route).to.have.property('pathParams'); | ||
expect(route.describe()).to.equal('TestController.greet'); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add tests to verify how templated paths like /hello/{name}
are handled.
32c7e4b
to
e05b421
Compare
@bajtos I cleaned up the code based on your feedback. The final benchmark now shows
I now make |
b3215d3
to
8eecce6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The patch looks reasonable now. Not all of my comments have been addressed and I found few more places where to improve the implementation (see below), but I don't see any major problems now.
FWIW, LGTM.
path: string, | ||
spec: OperationObject, | ||
handler: Function, | ||
): Binding; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use this new API to simplify Routing doc page - see https://loopback.io/doc/en/lb4/Routes.html#using-partial-openapi-spec-fragments
This hasn't been addressed AFAICT, please take a look ☝️
*/ | ||
interface RegExpRouteEntry extends RouteEntry { | ||
regexp?: RegExp; | ||
keys?: pathToRegExp.Key[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we have RegExpRouteEntry
instance with undefined regexp
and keys
? AFAICT, these two properties are always set by our code.
this.routes.push(entry); | ||
const path = toExpressPath(route.path); | ||
entry.keys = []; | ||
entry.regexp = pathToRegExp(path, entry.keys, {strict: false, end: true}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you marked keys
and regexp
as optiona to support the code in addRouteWithPathVars
, then I think the following implementation may be better:
const path = toExpressPath(route.path);
const keys = [];
const regexp = pathToRegExp(path, keys, {strict: false, end: true});
const entry: RegExpRouteEntry = Object.assign(route, {keys, regexp});
pathMatch: RegExpExecArray, | ||
): PathParameterValues { | ||
const pathParams: PathParameterValues = {}; | ||
for (const ix in route.keys!) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
route.keys
is always set, we should not need to use !
here.
continue; | ||
} | ||
|
||
const match = r.regexp!.exec(request.path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r.regexp
is always set, we should not need to use !
here.
this._sort(); | ||
for (const r of this.routes) { | ||
debug('trying endpoint %s', inspect(r, {depth: 5})); | ||
if (r.verb !== request.method!.toLowerCase()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure we need !
after request.method
? BaseRouter#getKeyForRequest
does not use it and calls request.method.toLowerCase()
.
let i = 0; | ||
for (const n of child.names) { | ||
const val = match[++i]; | ||
resolved.params![n] = decodeURIComponent(val); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid relying on
!
operator please?
PTAL ☝️
28d87fe
to
30dcd0b
Compare
6d60f14
to
7b5430e
Compare
- validate and normalize paths for openapi - add benchmark for routing - make the router pluggable - simplify app routing to plain functions - optimize simple route mapping and use regexp as default - add `rest.router` binding to allow customization - add docs for routing requests
7b5430e
to
e628cd3
Compare
@bajtos I addressed your additional comments. |
The PR introduces the following improvements:
rest.router
binding to allow customizationChecklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated