Skip to content

Commit

Permalink
Merge pull request #27 from brunoscopelliti/issue-17
Browse files Browse the repository at this point in the history
Hanlde multi-parametric route (issue #17)
  • Loading branch information
delvedor authored Sep 27, 2017
2 parents ddc113a + 003b37d commit 99f4934
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 24 deletions.
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

0 comments on commit 99f4934

Please sign in to comment.