-
Notifications
You must be signed in to change notification settings - Fork 399
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
Fix #1325 Added support for dynamic custom paths #1785
Changes from all commits
0c437d9
2ebf9bd
892db7b
e460eae
09566d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,9 @@ import { ListenOptions } from 'net'; | |
import { Logger, ConsoleLogger, LogLevel } from '@slack/logger'; | ||
import { InstallProvider, CallbackOptions, InstallProviderOptions, InstallURLOptions, InstallPathOptions } from '@slack/oauth'; | ||
import { URL } from 'url'; | ||
|
||
import { match } from 'path-to-regexp'; | ||
import { ParamsDictionary } from 'express-serve-static-core'; | ||
import { ParamsIncomingMessage } from './ParamsIncomingMessage'; | ||
import { verifyRedirectOpts } from './verify-redirect-opts'; | ||
import App from '../App'; | ||
import { Receiver, ReceiverEvent } from '../types'; | ||
|
@@ -392,8 +394,22 @@ export default class HTTPReceiver implements Receiver { | |
|
||
// Handle custom routes | ||
if (Object.keys(this.routes).length) { | ||
const match = this.routes[path] && this.routes[path][method] !== undefined; | ||
if (match) { return this.routes[path][method](req, res); } | ||
// Check if the request matches any of the custom routes | ||
let pathMatch : string | boolean = false; | ||
let params : ParamsDictionary = {}; | ||
Object.keys(this.routes).forEach((route) => { | ||
if (pathMatch) return; | ||
const matchRegex = match(route, { decode: decodeURIComponent }); | ||
const tempMatch = matchRegex(path); | ||
if (tempMatch) { | ||
pathMatch = route; | ||
params = tempMatch.params as ParamsDictionary; | ||
} | ||
Comment on lines
+404
to
+407
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to stop checking for matches once one is found to align with the "first come first serve" routing precedence used by Express. As is, attempting to access const app = new App({
token: "xoxb-token-here",
signingSecret: "example-signing-secret",
customRoutes: [{
path: '/crayon/blue',
method: ['GET'],
handler: (req, res) => {
res.writeHead(200);
res.end(`How cool! A true blue!`);
},
}, {
path: '/crayon/:color',
method: ['GET'],
handler: (req, res) => {
res.writeHead(200);
res.end(`There are no more ${req.params.color} crayons!`);
},
}, {
path: '/music/:genre',
method: ['GET'],
handler: (req, res) => {
res.writeHead(200);
res.end(`Oh? ${req.params.genre}? That slaps!`);
},
}, {
path: '/music/jazz',
method: ['GET'],
handler: (req, res) => {
res.writeHead(200);
res.end('Play it cool, you cat.');
},
}],
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And if not too much trouble, adding a test with multiple overlapping routes (to verify that this precedence is respected) would be wonderful! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is a very good point, I missed that. I have gone ahead and added a conditional return in the forEach and a test to make sure the first found route should be returned. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure that the conditional is enough to guarantee the ordering and have noticed it's still finicky with the above example 😕 Diving a bit deeper, it looks like the Considering that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for being so patient with me @E-Zim. Just so I understand, are you thinking it would be better to keep the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No worries at all! To offer more context, the I would suggest changing the return type of the Hoping this makes some sense, but apologies if this jumble of words is confusing. Happy to clarify more if needed! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the explanation, I believe I did it correctly. But I am happy to revise if this isn't what you were thinking. I tried to make it as simple as possible. Tests pass, but I was not able to replicate the issue with ordering that you described. Would you mind explaining how I could test that so I know for future reference? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @E-Zim bumping in case the last comment didn't notify you because I forgot to @ you There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the delay! I have extended the example above to further test this and it appeared that my local Thank you making the changes in 04ba9e0, but I'm no longer certain that these are required (through testing with the above example). If it is alright, would you be okay reverting this? In searching for examples to test the supposed undefined object ordering, I found the following from MDN stating that "the order of the array returned by I will add another comment about verifying the expected ordering behavior for this PR, but for future reference I don't believe testing an undefined object ordering can be done easily (if it's even relevant to your JS implementation). Sorry again for mistaking this behavior! |
||
}); | ||
const urlMatch = pathMatch && this.routes[pathMatch][method] !== undefined; | ||
if (urlMatch && pathMatch) { | ||
return this.routes[pathMatch][method]({ ...req, params } as ParamsIncomingMessage, res); | ||
} | ||
} | ||
|
||
// If the request did not match the previous conditions, an error is thrown. The error can be caught by | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { IncomingMessage } from 'http'; | ||
import { ParamsDictionary } from 'express-serve-static-core'; | ||
|
||
export interface ParamsIncomingMessage extends IncomingMessage { | ||
/** | ||
* **Only valid for requests with path parameters.** | ||
* | ||
* The path parameters of the request. For example, if the request URL is | ||
* `/users/123`, and the route definition is `/users/:id` | ||
* then `request.params` will be `{ id: '123' }`. | ||
*/ | ||
params?: ParamsDictionary; | ||
} |
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.
To verify that the order of these routes is respected by the receiver, we can update these routes to:
Then check for the following after making a fake request for
/test/123
:Another mock request to a different url, say
/test/abc
, could follow the tests for/test/123
and might include these asserts:And with that, I believe this would sufficiently show that the first matchable route in
customRoutes
is being matched!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 actually already doing something similar in the next
it
block from your example (line 579) the only difference is that I don't callassert(customRoutes[1].handler.notCalled);
because I set the response tosinon.fake.throw('Should not be used.')
as the handler which throws an exception. But I have updated that and I also added a secondit
block to test custom routes in reverse just to make sure that the first path is always called.