Permalink
Browse files

Fixed bugs and added tests for builtin parsers

  • Loading branch information...
1 parent 1952cd6 commit c2e3266a89dbb62fac70ee4ec2f321208fb12596 @tarruda committed Aug 28, 2012
Showing with 126 additions and 27 deletions.
  1. +30 −17 src/routers.coffee
  2. +96 −10 test/routers.coffee
View
@@ -4,6 +4,20 @@ url = require('url')
escapeRegex = (s) -> s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
+
+# The most basic parameter parser, which ensures no slashes
+# in the string and can optionally validate string length.
+defaultParser = (str, opts) ->
+ if str.indexOf('/') != -1
+ return null
+ if opts
+ if (isFinite(opts.len) && str.length != opts.len) ||
+ (isFinite(opts.min) && str.length < opts.min) ||
+ (isFinite(opts.max) && str.length > opts.max)
+ return null
+ return str
+
+
class RegexExtractor
constructor: (@regex) ->
@@ -42,7 +56,10 @@ class RuleExtractor extends RegexExtractor
extractedArgs = []
for i in [1...m.length]
param = params[i - 1]
- value = parsers[param.parserName](m[i], param.parserOpts)
+ parser = parsers[param.parserName]
+ if typeof parser != 'function'
+ parser = defaultParser
+ value = parser(m[i], param.parserOpts)
if value == null then return null
extractedArgs[i - 1] = extractedArgs[param.name] = value
return extractedArgs
@@ -55,20 +72,25 @@ class Compiler
# Default parsers which take care of parsing/validating arguments.
@parsers =
int: (str, opts) ->
- str = str.trim()
+ str = str.trim().toLowerCase()
+ # Remove leading zeros for comparsion after parsing.
+ for i in [0...str.length - 1]
+ if str.charAt(i) != '0'
+ break
+ str = str.slice(i)
base = 10
if opts?.base
base = opts.base
rv = parseInt(str, base)
if !isFinite(rv) || rv.toString(base) != str
return null
if opts
- if (isFinite(opts.min) && rv < min) ||
- (isFinite(opts.max) && rv > max)
+ if (isFinite(opts.min) && rv < opts.min) ||
+ (isFinite(opts.max) && rv > opts.max)
return null
return rv
- float: (str, opts) ->
+ number: (str, opts) ->
str = str.trim()
rv = parseFloat(str)
if !isFinite(rv) || rv.toString() != str
@@ -79,16 +101,7 @@ class Compiler
return null
return rv
- # Doesn't accept slashes
- str: (str, opts) ->
- if str.indexOf('/') != -1
- return null
- if opts
- if (isFinite(opts.len) && rv.length != opts.len) ||
- (isFinite(opts.minlen) && rv.length < opts.minlen) ||
- (isFinite(opts.maxlen) && rv.length > opts.maxlen)
- return null
- return str
+ str: (str, opts) -> defaultParser(str, opts)
path: (str) -> str
if parsers
@@ -128,7 +141,7 @@ class Compiler
parseOpts: (rawOpts) ->
rv = {}
- while match = @parserOptRe.exec(rawArgs)
+ while match = @parserOptRe.exec(rawOpts)
name = match[1]
if match[2] # boolean
rv[name] = Boolean(match[2])
@@ -153,7 +166,7 @@ class Compiler
ruleParam.parserName = match[2]
if match[3]
# Parser options
- ruleParam.parserOptions = @parseOpts(match[3])
+ ruleParam.parserOpts = @parseOpts(match[3])
# Parameter name
ruleParam.name = match[4]
extractor.pushParam(ruleParam)
View
@@ -1,44 +1,130 @@
connect = require('connect')
+routers = require('../src/routers')
-describe 'router.middleware', ->
- router = require('../src/routers')()
+describe 'Static rule matching', ->
+ router = routers()
app = connect()
app.use(router.middleware)
- router.get '/simple/get/pattern', (req, res) ->
+ router.get '/$imple/.get/pattern$', (req, res) ->
res.write('body1')
res.end()
- router.post('/simple/no-get/pattern', -> res.end())
+ router.post('/not-a-get/pattern*', -> res.end())
- router.del('/simple/no-get/pattern', -> res.end())
+ router.del('/not-a-get/pattern*', -> res.end())
- router.get '/pattern/that/uses/many/handlers',
+ router.get '/^pattern/that/uses/many/handlers',
(req, res, next) -> res.write('part1'); next(),
(req, res, next) -> res.write('part2'); next()
- router.get '/pattern/that/uses/many/handlers',
+ router.get '/^pattern/that/uses/many/handlers',
(req, res) -> res.write('part3'); res.end()
it 'should match simple patterns', (done) ->
app.request()
- .get('/simple/get/pattern')
+ .get('/$imple/.get/pattern$')
.end (res) ->
res.body.should.eql('body1')
done()
it "should return 405 when pattern doesn't match method", (done) ->
app.request()
- .get('/simple/no-get/pattern')
+ .get('/not-a-get/pattern*')
.end (res) ->
res.statusCode.should.eql(405)
res.headers['allow'].should.eql('POST, DELETE')
done()
it 'should pipe request through all handlers', (done) ->
app.request()
- .get('/pattern/that/uses/many/handlers')
+ .get('/^pattern/that/uses/many/handlers')
.end (res) ->
res.body.should.eql('part1part2part3')
done()
+
+describe 'Builtin string parser', ->
+ router = routers()
+ app = connect()
+ app.use(router.middleware)
+
+ router.get '/users/<str(max=5,min=2):id>', (req, res) ->
+ res.write('range')
+ res.end()
+
+ router.get '/users/<str(len=7):id>', (req, res) ->
+ res.write('exact')
+ res.end()
+
+ router.get '/customers/<id>', (req, res) ->
+ res.write(req.params.id)
+ res.end()
+
+ it 'should match strings inside range', (done) ->
+ app.request()
+ .get('/users/foo')
+ .end (res) ->
+ res.body.should.eql('range')
+ done()
+
+ it 'should not match strings outside range', (done) ->
+ app.request()
+ .get('/users/foobar')
+ .expect 404, ->
+ app.request()
+ .get('/users/f')
+ .expect(404, done)
+
+ it 'should match strings of configured length', (done) ->
+ app.request()
+ .get('/users/1234567')
+ .end (res) ->
+ res.body.should.eql('exact')
+ done()
+
+ it 'should be used when no parser is specified', (done) ->
+ app.request()
+ .get('/customers/abcdefghijk')
+ .end (res) ->
+ res.body.should.eql('abcdefghijk')
+ done()
+
+ it 'should not match strings containing slashes', (done) ->
+ app.request()
+ .get('/customers/abcdef/ghijk')
+ .expect(404, done)
+
+
+describe 'Builtin integer parser', ->
+ router = routers()
+ app = connect()
+ app.use(router.middleware)
+
+ router.get '/users/<int(base=16,max=255):id>', (req, res) ->
+ res.write(JSON.stringify(req.params.id))
+ res.end()
+
+ it 'should match numbers with leading zeros', (done) ->
+ app.request()
+ .get('/users/000')
+ .end (res) ->
+ JSON.parse(res.body).should.eql(0)
+ done()
+
+ it 'should take numeric base into consideration', (done) ->
+ app.request()
+ .get('/users/ff')
+ .end (res) ->
+ JSON.parse(res.body).should.eql(255)
+ done()
+
+ it 'should not match numbers outside range', (done) ->
+ app.request()
+ .get('/users/100')
+ .expect(404, done)
+
+ it 'should not match floats', (done) ->
+ app.request()
+ .get('/users/50.3')
+ .expect(404, done)

0 comments on commit c2e3266

Please sign in to comment.