Skip to content

Commit

Permalink
Merge pull request #43 from hekike/feat/off
Browse files Browse the repository at this point in the history
feat(off): add off feature
  • Loading branch information
delvedor authored Nov 17, 2017
2 parents 52c2eaa + 3128d80 commit 9b0ba14
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 1 deletion.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ parametric(regex)
multi parametric(regex)
```

<a name="off"></a>
#### off(method, path)
Deregister a route.
```js
router.off('GET', '/example')
// => { handler: Function, params: Object, store: Object}
// => null
```

##### off(methods[], path, handler, [store])
Deregister a route for each method specified in the `methods` array.
It comes handy when you need to deregister multiple routes with the same path but different methods.
```js
router.off(['GET', 'POST'], '/example')
// => [{ handler: Function, params: Object, store: Object}]
// => null
```

<a name="reset"></a>
#### reset()
Empty router.
```js
router.reset()
```

##### Caveats
* Since *static* routes have greater priority than *parametric* routes, when you register a static route and a dynamic route, which have part of their path equal, the static route shadows the parametric route, that becomes not accessible. For example:
```js
Expand Down
40 changes: 40 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function Router (opts) {
}

this.tree = new Node()
this.routes = []
}

Router.prototype.on = function on (method, path, handler, store) {
Expand All @@ -58,6 +59,13 @@ Router.prototype.on = function on (method, path, handler, store) {
const params = []
var j = 0

this.routes.push({
method: method,
path: path,
handler: handler,
store: store
})

for (var i = 0, len = path.length; i < len; i++) {
// search for parametric or wildcard routes
// parametric route
Expand Down Expand Up @@ -183,6 +191,38 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler
}
}

Router.prototype.reset = function reset () {
this.tree = new Node()
this.routes = []
}

Router.prototype.off = function off (method, path) {
var self = this

if (Array.isArray(method)) {
return method.map(function (method) {
return self.off(method, path)
})
}

// method validation
assert(typeof method === 'string', 'Method should be a string')
assert(httpMethods.indexOf(method) !== -1, `Method '${method}' is not an http method.`)
// path validation
assert(typeof path === 'string', 'Path should be a string')
assert(path.length > 0, 'The path could not be empty')
assert(path[0] === '/' || path[0] === '*', 'The first character of a path should be `/` or `*`')

// Rebuild tree without the specific route
var newRoutes = self.routes.filter(function (route) {
return !(method === route.method && path === route.path)
})
self.reset()
newRoutes.forEach(function (route) {
self.on(route.method, route.path, route.handler, route.store)
})
}

Router.prototype.lookup = function lookup (req, res) {
var handle = this.find(req.method, sanitizeUrl(req.url))
if (!handle) return this._defaultRoute(req, res)
Expand Down
150 changes: 149 additions & 1 deletion test/methods.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,45 @@ const test = t.test
const FindMyWay = require('../')

test('the router is an object with methods', t => {
t.plan(3)
t.plan(4)

const findMyWay = FindMyWay()

t.is(typeof findMyWay.on, 'function')
t.is(typeof findMyWay.off, 'function')
t.is(typeof findMyWay.lookup, 'function')
t.is(typeof findMyWay.find, 'function')
})

test('on throws for invalid method', t => {
t.plan(1)
const findMyWay = FindMyWay()

t.throws(() => {
findMyWay.on('INVALID', '/a/b')
})
})

test('on throws for invalid path', t => {
t.plan(3)
const findMyWay = FindMyWay()

// Non string
t.throws(() => {
findMyWay.on('GET', 1)
})

// Empty
t.throws(() => {
findMyWay.on('GET', '')
})

// Doesn't start with / or *
t.throws(() => {
findMyWay.on('GET', 'invalid')
})
})

test('register a route', t => {
t.plan(1)
const findMyWay = FindMyWay()
Expand All @@ -37,6 +67,124 @@ test('register a route with multiple methods', t => {
findMyWay.lookup({ method: 'POST', url: '/test' }, null)
})

test('off throws for invalid method', t => {
t.plan(1)
const findMyWay = FindMyWay()

t.throws(() => {
findMyWay.off('INVALID', '/a/b')
})
})

test('off throws for invalid path', t => {
t.plan(3)
const findMyWay = FindMyWay()

// Non string
t.throws(() => {
findMyWay.off('GET', 1)
})

// Empty
t.throws(() => {
findMyWay.off('GET', '')
})

// Doesn't start with / or *
t.throws(() => {
findMyWay.off('GET', 'invalid')
})
})

test('off with nested wildcards with parametric and static', t => {
t.plan(3)
const findMyWay = FindMyWay({
defaultRoute: (req, res) => {
t.fail('we should not be here, the url is: ' + req.url)
}
})

findMyWay.on('GET', '*', (req, res, params) => {
t.is(params['*'], '/foo2/first/second')
})
findMyWay.on('GET', '/foo1/*', () => {})
findMyWay.on('GET', '/foo2/*', () => {})
findMyWay.on('GET', '/foo3/:param', () => {})
findMyWay.on('GET', '/foo3/*', () => {})
findMyWay.on('GET', '/foo4/param/hello/test/long/route', () => {})

var route1 = findMyWay.find('GET', '/foo3/first/second')
t.is(route1.params['*'], 'first/second')

findMyWay.off('GET', '/foo3/*')

var route2 = findMyWay.find('GET', '/foo3/first/second')
t.is(route2.params['*'], '/foo3/first/second')

findMyWay.off('GET', '/foo2/*')
findMyWay.lookup(
{ method: 'GET', url: '/foo2/first/second' },
null
)
})

test('deregister a route without children', t => {
t.plan(2)
const findMyWay = FindMyWay()

findMyWay.on('GET', '/a', () => {})
findMyWay.on('GET', '/a/b', () => {})
findMyWay.off('GET', '/a/b')

t.ok(findMyWay.find('GET', '/a'))
t.notOk(findMyWay.find('GET', '/a/b'))
})

test('deregister a route with children', t => {
t.plan(2)
const findMyWay = FindMyWay()

findMyWay.on('GET', '/a', () => {})
findMyWay.on('GET', '/a/b', () => {})
findMyWay.off('GET', '/a')

t.notOk(findMyWay.find('GET', '/a'))
t.ok(findMyWay.find('GET', '/a/b'))
})

test('deregister a route by method', t => {
t.plan(2)
const findMyWay = FindMyWay()

findMyWay.on(['GET', 'POST'], '/a', () => {})
findMyWay.off('GET', '/a')

t.notOk(findMyWay.find('GET', '/a'))
t.ok(findMyWay.find('POST', '/a'))
})

test('deregister a route with multiple methods', t => {
t.plan(2)
const findMyWay = FindMyWay()

findMyWay.on(['GET', 'POST'], '/a', () => {})
findMyWay.off(['GET', 'POST'], '/a')

t.notOk(findMyWay.find('GET', '/a'))
t.notOk(findMyWay.find('POST', '/a'))
})

test('reset a router', t => {
t.plan(2)
const findMyWay = FindMyWay()

findMyWay.on(['GET', 'POST'], '/a', () => {})
findMyWay.reset()

t.notOk(findMyWay.find('GET', '/a'))
t.notOk(findMyWay.find('POST', '/a'))
})

test('default route', t => {
t.plan(1)

Expand Down

0 comments on commit 9b0ba14

Please sign in to comment.