Skip to content
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

Hanlde multi-parametric route (issue #17) #27

Merged
merged 1 commit into from
Sep 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const http = require('http')
const router = require('find-my-way')()

router.on('GET', '/', (req, res, params) => {
res.end('{"hello":"world"}')
res.end('{"message":"hello world"}')
})

const server = http.createServer((req, res) => {
Expand Down Expand Up @@ -50,39 +50,61 @@ const router = require('find-my-way')({

<a name="on"></a>
#### on(method, path, handler, [store])
Register a new route, `store` is an object that you can access later inside the handler function.
Register a new route.
```js
router.on('GET', '/', (req, res, params) => {
router.on('GET', '/example', (req, res, params) => {
// your code
})
```
Last argument, `store` is used to pass an object that you can access later inside the handler function. If needed, `store` can be updated.
```js
router.on('GET', '/example', (req, res, params, store) => {
assert.equal(store, { message: 'hello world' })
}, { message: 'hello world' })
```

// with store
router.on('GET', '/store', (req, res, params, store) => {
// the store can be updated
assert.equal(store, { hello: 'world' })
}, { hello: 'world' })
##### on(methods[], path, handler, [store])
Register a new route for each method specified in the `methods` array.
It comes handy when you need to declare multiple routes with the same handler but different methods.
```js
router.on(['GET', 'POST'], '/example', (req, res, params) => {
// your code
})
```
If you want to register a **parametric** path, just use the *colon* before the parameter name, if you need a **wildcard** use the *star*.

<a name="supported-path-formats"></a>
##### Supported path formats
To register a **parametric** path, use the *colon* before the parameter name. For **wildcard** use the *star*.
*Remember that static routes are always inserted before parametric and wildcard.*

```js
// parametric
router.on('GET', '/example/:name', () => {}))
router.on('GET', '/example/:userId', (req, res, params) => {}))
router.on('GET', '/example/:userId/:secretToken', (req, res, params) => {}))

// wildcard
router.on('GET', '/other-example/*', () => {}))
router.on('GET', '/example/*', (req, res, params) => {}))
```

Regex routes are supported as well, but pay attention, regex are very expensive!
Regular expression routes are supported as well, but pay attention, RegExp are very expensive in term of performance!
```js
// parametric with regex
router.on('GET', '/test/:file(^\\d+).png', () => {}))
// parametric with regexp
router.on('GET', '/example/:file(^\\d+).png', () => {}))
```

You can also pass an array of methods if you need to declare multiple routes with the same handler but different method.
It's possible to define more than one parameter within the same couple of slash ("/"). Such as:
```js
router.on(['GET', 'POST'], '/', (req, res, params) => {
// your code
})
router.on('GET', '/example/near/:lat-:lng/radius/:r', (req, res, params) => {}))
```
*Remember in this case to use the dash ("-") as parameters separator.*

Finally it's possible to have multiple parameters with RegExp.
```js
router.on('GET', '/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', (req, res, params) => {}))
```
In this case as parameter separator it's possible to use whatever character is not matched by the regular expression.

Having a route with multiple parameters may affect negatively the performance, so prefer single parameter approach whenever possible, especially on routes which are on the hot path of your application.

<a name="shorthand-methods"></a>
##### Shorthand methods
Expand Down
14 changes: 14 additions & 0 deletions bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const FindMyWay = require('./')
const findMyWay = new FindMyWay()
findMyWay.on('GET', '/', () => true)
findMyWay.on('GET', '/user/:id', () => true)
findMyWay.on('GET', '/customer/:name-:surname', () => true)
findMyWay.on('GET', '/at/:hour(^\\d+)h:minute(^\\d+)m', () => true)
findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', () => true)

suite
Expand All @@ -17,6 +19,12 @@ suite
.add('lookup dynamic route', function () {
findMyWay.lookup({ method: 'GET', url: '/user/tomas' }, null)
})
.add('lookup dynamic multi-parametric route', function () {
findMyWay.lookup({ method: 'GET', url: '/customer/john-doe' }, null)
})
.add('lookup dynamic multi-parametric route with regex', function () {
findMyWay.lookup({ method: 'GET', url: '/at/12h00m' }, null)
})
.add('lookup long static route', function () {
findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz' }, null)
})
Expand All @@ -26,6 +34,12 @@ suite
.add('find dynamic route', function () {
findMyWay.find('GET', '/user/tomas')
})
.add('find dynamic multi-parametric route', function () {
findMyWay.find('GET', '/customer/john-doe')
})
.add('find dynamic multi-parametric route with regex', function () {
findMyWay.find('GET', '/at/12h00m')
})
.add('find long static route', function () {
findMyWay.find('GET', '/abc/def/ghi/lmn/opq/rst/uvz')
})
Expand Down
55 changes: 49 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
param: 1,
matchAll: 2,
regex: 3
multi-param: 4
It's used for a parameter, that is followed by another parameter in the same part

Char codes:
'#': 35
'*': 42
'-': 45
'/': 47
':': 58
'*': 42
'?': 63
'#': 35

*/

const assert = require('assert')
Expand Down Expand Up @@ -54,13 +58,31 @@ Router.prototype.on = function (method, path, handler, store) {
// search for parametric or wildcard routes
// parametric route
if (path.charCodeAt(i) === 58) {
var nodeType = 1
j = i + 1
this._insert(method, path.slice(0, i), 0, null, null, null)

// isolate the parameter name
while (i < len && path.charCodeAt(i) !== 47) i++
var isRegex = false
while (i < len && path.charCodeAt(i) !== 47) {
isRegex = isRegex || path[i] === '('
if (isRegex) {
i = path.indexOf(')', i) + 1
break
} else if (path.charCodeAt(i) !== 45) {
i++
} else {
break
}
}

if (isRegex && (i === len || path.charCodeAt(i) === 47)) {
nodeType = 3
} else if (i < len) {
nodeType = 4
}

var parameter = path.slice(j, i)
var isRegex = parameter.indexOf('(') > -1
var regex = isRegex ? parameter.slice(parameter.indexOf('('), i) : null
if (isRegex) regex = new RegExp(regex)
params.push(parameter.slice(0, isRegex ? parameter.indexOf('(') : i))
Expand All @@ -71,10 +93,11 @@ Router.prototype.on = function (method, path, handler, store) {

// if the path is ended
if (i === len) {
return this._insert(method, path.slice(0, i), regex ? 3 : 1, params, handler, store, regex)
return this._insert(method, path.slice(0, i), nodeType, params, handler, store, regex)
}
this._insert(method, path.slice(0, i), regex ? 3 : 1, params, null, null, regex)
this._insert(method, path.slice(0, i), nodeType, params, null, null, regex)

i--
// wildcard route
} else if (path.charCodeAt(i) === 42) {
this._insert(method, path.slice(0, i), 0, null, null, null)
Expand Down Expand Up @@ -258,6 +281,26 @@ Router.prototype.find = function (method, path) {
continue
}

// multiparametric route
if (kind === 4) {
currentNode = node
i = 0
if (node.regex) {
var matchedParameter = path.match(node.regex)
if (!matchedParameter) return
i = matchedParameter[1].length
} else {
while (i < pathLen && path.charCodeAt(i) !== 47 && path.charCodeAt(i) !== 45) i++
}
decoded = fastDecode(path.slice(0, i))
if (errored) {
return null
}
params[pindex++] = decoded
path = path.slice(i)
continue
}

// route not found
if (len !== prefixLen) return null
}
Expand Down
Loading