Finished implementing router. Backuping non-class method

This commit is contained in:
Jonatan Nilsson 2021-07-05 17:54:00 +00:00
parent b8df546f18
commit 26c9b4a27e
9 changed files with 694 additions and 139 deletions

View file

@ -9,7 +9,7 @@ export const dummy = function() { callItem() }
export const allRoutes = [ export const allRoutes = [
'/', '/',
'/api/articles', '/api/articles',
'/api/articles/:articleId/file', '/api/articles/:id/file',
'/api/articles/:id', '/api/articles/:id',
'/api/articles/public', '/api/articles/public',
'/api/articles/public/:id', '/api/articles/public/:id',
@ -19,8 +19,8 @@ export const allRoutes = [
'/api/media/:id', '/api/media/:id',
'/api/pages', '/api/pages',
'/api/pages/:id', '/api/pages/:id',
'/api/pages/:pageId/articles', '/api/pages/:id/articles',
'/api/pages/:pageId/articles/public', '/api/pages/:id/articles/public',
'/api/staff', '/api/staff',
'/api/staff/:id', '/api/staff/:id',
] ]
@ -28,7 +28,7 @@ export const allRoutes = [
export const allManyRoutes = [ export const allManyRoutes = [
'/', '/',
'/api/articles', '/api/articles',
'/api/articles/:articleId/file', '/api/articles/:id/file',
'/api/articles/:id', '/api/articles/:id',
'/api/articles/public', '/api/articles/public',
'/api/articles/public/:id', '/api/articles/public/:id',
@ -36,9 +36,9 @@ export const allManyRoutes = [
'/api/categories/:categoryId/products', '/api/categories/:categoryId/products',
'/api/categories/:categoryId/properties', '/api/categories/:categoryId/properties',
'/api/categories/:categoryId/values/:props', '/api/categories/:categoryId/values/:props',
'/api/categories/:id', '/api/categories/:categoryId',
'/api/categories/:id/products/:productId', '/api/categories/:categoryId/products/:productId',
'/api/categories/:id/products/:productId', '/api/categories/:categoryId/products/:productId',
'/api/customers', '/api/customers',
'/api/customers/:id', '/api/customers/:id',
'/api/customers/kennitala/:kennitala', '/api/customers/kennitala/:kennitala',
@ -51,10 +51,10 @@ export const allManyRoutes = [
'/api/orderitem', '/api/orderitem',
'/api/orderitem/:id', '/api/orderitem/:id',
'/api/orders', '/api/orders',
'/api/orders/:id', '/api/orders/:orderId',
'/api/orders/:orderId/sell', '/api/orders/:orderId/sell',
'/api/pages', '/api/pages',
'/api/pages/:id', '/api/pages/:pageId',
'/api/pages/:pageId/articles', '/api/pages/:pageId/articles',
'/api/pages/:pageId/articles/public', '/api/pages/:pageId/articles/public',
'/api/products', '/api/products',

2
benchmark/index.bat Normal file
View file

@ -0,0 +1,2 @@
start /B /WAIT /REALTIME node index.js
pause

View file

@ -1,7 +1,7 @@
import assert from 'assert' import assert from 'assert'
import Benchmark from 'benchmarkjs-pretty' import Benchmark from 'benchmarkjs-pretty'
import { koaRouter1, koaRouter2 } from './router_koa.js' import { koaRouter1, koaRouter2 } from './router_koa.js'
import { flaskaRouter1, flaskaRouter2 } from './router_flaska.js' import { flaskaRouter1, flaskaRouter2, flaskaClassRouter1, flaskaClassRouter2 } from './router_flaska.js'
import { expressRouter1, expressRouter2 } from './router_express.js' import { expressRouter1, expressRouter2 } from './router_express.js'
import * as consts from './const.js' import * as consts from './const.js'
@ -28,6 +28,10 @@ function TestSmallStaticRoute() {
testData = flaskaRouter1.match('/api/staff') testData = flaskaRouter1.match('/api/staff')
assert.ok(testData.handler) assert.ok(testData.handler)
}) })
.add('bottle-router-alt', function() {
testData = flaskaClassRouter1.match('/api/staff')
assert.ok(testData.handler)
})
.run() .run()
.then(function() {}, function(e) { .then(function() {}, function(e) {
console.error('error:', e) console.error('error:', e)
@ -53,6 +57,10 @@ function TestSmallParamRoute() {
testData = flaskaRouter1.match('/api/staff/justatest') testData = flaskaRouter1.match('/api/staff/justatest')
assert.ok(testData.handler) assert.ok(testData.handler)
}) })
.add('bottle-router-alt', function() {
testData = flaskaClassRouter1.match('/api/staff/justatest')
assert.ok(testData.handler)
})
.run() .run()
.then(function() {}, function(e) { .then(function() {}, function(e) {
console.error('error:', e) console.error('error:', e)
@ -78,6 +86,10 @@ function TestLargeStaticRoute() {
testData = flaskaRouter2.match('/api/staff') testData = flaskaRouter2.match('/api/staff')
assert.ok(testData.handler) assert.ok(testData.handler)
}) })
.add('bottle-router-alt', function() {
testData = flaskaClassRouter2.match('/api/staff')
assert.ok(testData.handler)
})
.run() .run()
.then(function() {}, function(e) { .then(function() {}, function(e) {
console.error('error:', e) console.error('error:', e)
@ -103,6 +115,39 @@ function TestLargeParamRoute() {
testData = flaskaRouter2.match('/api/staff/justatest') testData = flaskaRouter2.match('/api/staff/justatest')
assert.ok(testData.handler) assert.ok(testData.handler)
}) })
.add('bottle-router-alt', function() {
testData = flaskaClassRouter2.match('/api/staff/justatest')
assert.ok(testData.handler)
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
process.exit(1)
})
}
function TestLargeParamLargeUrlRoute() {
return new Benchmark.default('Large router param route long route benchmark: /api/products/:id/sub_products/:productId (58 routes registered)')
.add('expressjs', function() {
testData = null
expressRouter2.handle({
url: '/api/products/justatest/sub_products/foobar',
method: 'GET',
}, {}, function() { })
assert.ok(testData)
})
.add('koa-router', function() {
testData = koaRouter2.match('/api/products/justatest/sub_products/foobar', 'GET')
assert.ok(testData.route)
})
.add('bottle-router', function() {
testData = flaskaRouter2.match('/api/products/justatest/sub_products/foobar')
assert.ok(testData.handler)
})
.add('bottle-router-alt', function() {
testData = flaskaClassRouter2.match('/api/products/justatest/sub_products/foobar')
assert.ok(testData.handler)
})
.run() .run()
.then(function() {}, function(e) { .then(function() {}, function(e) {
console.error('error:', e) console.error('error:', e)
@ -120,6 +165,9 @@ TestSmallStaticRoute()
.then(function() { .then(function() {
return TestLargeParamRoute() return TestLargeParamRoute()
}) })
.then(function() {
return TestLargeParamLargeUrlRoute()
})
.then(function() { .then(function() {
process.exit(0) process.exit(0)
}) })

View file

@ -1,18 +1,25 @@
import { FlaskaRouter } from '../flaska.mjs' import { FlaskaRouter, FlaskaRouterClass } from '../flaska.mjs'
import * as consts from './const.js' import * as consts from './const.js'
const router1 = new FlaskaRouter() const router1 = new FlaskaRouter()
const router2 = new FlaskaRouter() const router2 = new FlaskaRouter()
const classRouter1 = new FlaskaRouterClass()
const classRouter2 = new FlaskaRouterClass()
for (let key in consts.allRoutes) { for (let key in consts.allRoutes) {
router1.addRoute(consts.allRoutes[key], consts.dummy) router1.addRoute(consts.allRoutes[key], consts.dummy)
classRouter1.addRoute(consts.allRoutes[key], consts.dummy)
} }
for (let key in consts.allManyRoutes) { for (let key in consts.allManyRoutes) {
router2.addRoute(consts.allManyRoutes[key], consts.dummy) router2.addRoute(consts.allManyRoutes[key], consts.dummy)
classRouter2.addRoute(consts.allManyRoutes[key], consts.dummy)
} }
export { export {
router1 as flaskaRouter1, router1 as flaskaRouter1,
router2 as flaskaRouter2, router2 as flaskaRouter2,
classRouter1 as flaskaClassRouter1,
classRouter2 as flaskaClassRouter2,
} }

View file

@ -2,123 +2,335 @@
* Router * Router
*/ */
function Branch() { class Branch {
this._map = new Map() constructor() {
this._paramName = null this.children = new Map()
this._paramPrototype = null this.paramName = null
this._handler = null this.fullparamName = null
this.handler = null
this.middlewares = []
}
} }
const __paramMapName = '__param' const __paramMapName = '__param'
const __fullParamMapName = '__fullparam'
function FlaskaRouter() { export class FlaskaRouter {
this._root = new Branch() constructor() {
this.root = new Branch()
} }
FlaskaRouter.prototype.addRoute = function(route, handler) { addRoute(route, orgMiddlewares, orgHandler) {
if (route[0] !== '/') if (route[0] !== '/')
throw new Error(`route "${route}" must start with forward slash`); throw new Error(`route "${route}" must start with forward slash`)
let middlewares = orgMiddlewares
let handler = orgHandler
if (!orgHandler) {
handler = orgMiddlewares
middlewares = []
}
if (middlewares && typeof(middlewares) === 'function') {
middlewares = [middlewares]
}
if (typeof(handler) !== 'function') {
throw new Error(`route "${route}" was missing a handler`)
}
let start = 1
let end = 1
let name = ''
let param = ''
let isParam = false
let isFullParam = false
let branch = this.root
if (route.indexOf(':') < 0) {
let name = route
if (name.length > 1 && name[name.length - 1] === '/') {
name = name.slice(0, -1)
}
let child = new Branch()
branch.children.set(name, child)
child.handler = handler
child.middlewares = middlewares
}
let start = 1;
let end = 1;
let name = '';
let param = '';
let objectPrototype = {};
let paramDefined = false;
let isParam = false;
let branch = this._root;
let hashConflict = new Map();
for (let i = 1; i <= route.length; i++) { for (let i = 1; i <= route.length; i++) {
if ((i === route.length || route[i] === '/') && end > start) { if ((i === route.length || route[i] === '/') && end > start) {
let child; if (branch.fullparamName) {
let number = 0; throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
name = route.substring(start, end);
if (isParam) {
param = name;
name = __paramMapName;
} }
if (branch._map.has(name)) { let child
child = branch._map.get(name); name = route.substring(start, end)
if (isFullParam) {
param = name
name = __fullParamMapName
} else if (isParam) {
param = name
name = __paramMapName
}
if (branch.children.has(name)) {
child = branch.children.get(name)
}
else if (isParam && !isFullParam && branch.children.has(__fullParamMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
else if (isFullParam && branch.children.has(__paramMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a partial param child`)
} }
else { else {
child = new Branch(); child = new Branch()
branch._map.set(name, child); branch.children.set(name, child)
} }
branch = child; branch = child
end = i; end = i
start = i; start = i
if (isParam) { if (isParam) {
branch._paramName = param; if (branch.paramName && branch.paramName !== param) {
Object.defineProperty(objectPrototype, param, { throw new Error(`route "${route}" conflicts with pre-existing param name of ${branch.paramName} instead of ${param}`)
enumerable: true,
writable: true,
value: '',
});
paramDefined = true;
isParam = false;
} }
if (isFullParam) {
branch.fullparamName = param
} else {
branch.paramName = param
}
isParam = false
}
} else if (route[i] === '/' && end === start) {
throw new Error(`route "${route}" has missing path name inbetween slashes`)
} }
if (i === route.length) { if (i === route.length) {
branch._handler = handler; branch.handler = handler
branch._paramPrototype = objectPrototype; branch.middlewares = middlewares
continue; continue
} }
if (route[i] === ':') { if (route[i] === ':') {
isParam = true; if (isParam) {
end = start = i + 1; isFullParam = true
}
isParam = true
end = start = i + 1
} }
else if (route[i] === '/') { else if (route[i] === '/') {
end = start = i + 1; end = start = i + 1
} }
else { else {
end++; end++
} }
} }
} }
FlaskaRouter.prototype.match = function(url) { match(orgUrl) {
let branch = this._root; let url = orgUrl
let start = 1; if (url.length > 1 && url[url.length - 1] === '/') {
let end = 1; url = url.slice(0, -1)
let output;
let map;
let name;
let char;
let paramMap = new Map();
for (let i = 1; i <= url.length; i++) {
char = url[i];
if ((i === url.length || char === '/') && end > start) {
name = url.slice(start, end);
map = branch._map;
if (output = map.get(name)) {
branch = output;
} }
else if (output = map.get(__paramMapName)) { let branch = this.root
branch = output; let start = 1
paramMap.set(branch._paramName, name); let end = 1
let output
let name
let char
let params = {}
if (output = branch.children.get(url)) {
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
for (let i = 1; i <= url.length; i++) {
char = url[i]
if ((i === url.length || char === '/') && end > start) {
name = url.slice(start, end)
if (output = branch.children.get(name)) {
branch = output
}
else if (output = branch.children.get(__paramMapName)) {
branch = output
params[branch.paramName] = name
} else { } else {
return null return null
} }
i++; i++
end = start = i; end = start = i
char = url[i]; char = url[i]
} }
if (i >= url.length) { if (i >= url.length) {
return { return {
handler: branch._handler, handler: branch.handler,
params: paramMap, middlewares: branch.middlewares,
}; params: params,
}
} }
if (char === '/') { if (char === '/') {
end = start = i + 1; end = start = i + 1
} } else {
else { end++
end++;
} }
} }
return null; return null
}
} }
export { export function FlaskaRouter() {
FlaskaRouter, this.root = new Branch()
}
FlaskaRouter.prototype.addRoute = function(route, orgMiddlewares, orgHandler) {
if (route[0] !== '/')
throw new Error(`route "${route}" must start with forward slash`)
let middlewares = orgMiddlewares
let handler = orgHandler
if (!orgHandler) {
handler = orgMiddlewares
middlewares = []
}
if (middlewares && typeof(middlewares) === 'function') {
middlewares = [middlewares]
}
if (typeof(handler) !== 'function') {
throw new Error(`route "${route}" was missing a handler`)
}
let start = 1
let end = 1
let name = ''
let param = ''
let isParam = false
let isFullParam = false
let branch = this.root
if (route.indexOf(':') < 0) {
let name = route
if (name.length > 1 && name[name.length - 1] === '/') {
name = name.slice(0, -1)
}
let child = new Branch()
branch.children.set(name, child)
child.handler = handler
child.middlewares = middlewares
}
for (let i = 1; i <= route.length; i++) {
if ((i === route.length || route[i] === '/') && end > start) {
if (branch.fullparamName) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
let child
name = route.substring(start, end)
if (isFullParam) {
param = name
name = __fullParamMapName
} else if (isParam) {
param = name
name = __paramMapName
}
if (branch.children.has(name)) {
child = branch.children.get(name)
}
else if (isParam && !isFullParam && branch.children.has(__fullParamMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
else if (isFullParam && branch.children.has(__paramMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a partial param child`)
}
else {
child = new Branch()
branch.children.set(name, child)
}
branch = child
end = i
start = i
if (isParam) {
if (branch.paramName && branch.paramName !== param) {
throw new Error(`route "${route}" conflicts with pre-existing param name of ${branch.paramName} instead of ${param}`)
}
if (isFullParam) {
branch.fullparamName = param
} else {
branch.paramName = param
}
isParam = false
}
} else if (route[i] === '/' && end === start) {
throw new Error(`route "${route}" has missing path name inbetween slashes`)
}
if (i === route.length) {
branch.handler = handler
branch.middlewares = middlewares
continue
}
if (route[i] === ':') {
if (isParam) {
isFullParam = true
}
isParam = true
end = start = i + 1
}
else if (route[i] === '/') {
end = start = i + 1
}
else {
end++
}
}
}
FlaskaRouter.prototype.match = function(orgUrl) {
let url = orgUrl
if (url.length > 1 && url[url.length - 1] === '/') {
url = url.slice(0, -1)
}
let branch = this.root
let start = 1
let end = 1
let output
let name
let char
let params = {}
if (output = branch.children.get(url)) {
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
for (let i = 1; i <= url.length; i++) {
char = url[i]
if ((i === url.length || char === '/') && end > start) {
name = url.slice(start, end)
if (output = branch.children.get(name)) {
branch = output
}
else if (output = branch.children.get(__paramMapName)) {
branch = output
params[branch.paramName] = name
} else {
return null
}
i++
end = start = i
char = url[i]
}
if (i >= url.length) {
return {
handler: branch.handler,
middlewares: branch.middlewares,
params: params,
}
}
if (char === '/') {
end = start = i + 1
} else {
end++
}
}
return null
}
export function Flaska() {
} }

View file

@ -18,6 +18,6 @@
}, },
"homepage": "https://github.com/nfp-projects/bottle-node#readme", "homepage": "https://github.com/nfp-projects/bottle-node#readme",
"devDependencies": { "devDependencies": {
"eltro": "^0.9.0" "eltro": "^1.1.0"
} }
} }

96
test/client.mjs Normal file
View file

@ -0,0 +1,96 @@
import http from 'http'
import { URL } from 'url'
// taken from isobject npm library
function isObject(val) {
return val != null && typeof val === 'object' && Array.isArray(val) === false
}
function defaults(options, def) {
let out = { }
if (options) {
Object.keys(options || {}).forEach(key => {
out[key] = options[key]
if (Array.isArray(out[key])) {
out[key] = out[key].map(item => {
if (isObject(item)) return defaults(item)
return item
})
} else if (out[key] && typeof out[key] === 'object') {
out[key] = defaults(options[key], def && def[key])
}
})
}
if (def) {
Object.keys(def).forEach(function(key) {
if (typeof out[key] === 'undefined') {
out[key] = def[key]
}
})
}
return out
}
export default function Client(port, opts) {
this.options = defaults(opts, {})
this.prefix = `http://localhost:${port}`
}
Client.prototype.customRequest = function(method = 'GET', path, body, options) {
if (path.slice(0, 4) !== 'http') {
path = this.prefix + path
}
let urlObj = new URL(path)
return new Promise((resolve, reject) => {
const opts = defaults(defaults(options, {
method: method,
timeout: 500,
protocol: urlObj.protocol,
username: urlObj.username,
password: urlObj.password,
host: urlObj.hostname,
port: Number(urlObj.port),
path: urlObj.pathname + urlObj.search,
}))
const req = http.request(opts)
if (body) {
req.write(body)
}
req.on('error', reject)
req.on('timeout', function() { reject(new Error(`Request ${method} ${path} timed out`)) })
req.on('response', res => {
res.setEncoding('utf8')
let output = ''
res.on('data', function (chunk) {
output += chunk.toString()
})
res.on('end', function () {
try {
output = JSON.parse(output)
} catch (e) {
return reject(new Error(`${e.message} while decoding: ${output}`))
}
if (output.status) {
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
err.body = output
return reject(err)
}
resolve(output)
})
})
req.end()
})
}
Client.prototype.get = function(path = '/') {
return this.customRequest('GET', path, null)
}

View file

@ -1,48 +1,5 @@
import { Eltro as t, assert} from 'eltro' import { Eltro as t, assert} from 'eltro'
import { FlaskaRouter } from '../flaska.mjs' import { Flaska } from '../flaska.mjs'
t.describe('FlaskaRouter', function() { t.describe('Flaska', function() {
t.describe('#match()', function() {
t.test('should match basic paths', function() {
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test', function() { assertMatched = true })
let result = router.match('/test')
assert.ok(result.handler)
result.handler()
assert.strictEqual(assertMatched, true)
})
t.test('should match variable paths', function() {
const assertParameter = 'bla'
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { assertMatched = true })
let result = router.match('/test/' + assertParameter)
assert.ok(result.handler)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.get('id'), assertParameter)
})
t.test('should match paths properly', function() {
let assertMatched = true
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { assertMatched = false })
router.addRoute('/test/:id/test1', function() { })
let result = router.match('/test/asdf/test1')
assert.ok(result.handler)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.get('id'), 'asdf')
})
t.test('should return null when no match is found', function() {
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { })
router.addRoute('/test/:id/test1', function() { })
let result = router.match('/test/asdf/test2')
assert.notOk(result)
})
})
}) })

233
test/router.test.mjs Normal file
View file

@ -0,0 +1,233 @@
import { Eltro as t, assert } from 'eltro'
import { FlaskaRouter } from '../flaska.mjs'
t.describe('#addRoute()', function() {
t.test('fail if trying to add non-root path', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('', function() { }) }, /forward slash/)
assert.throws(function() { router.addRoute('test') }, /forward slash/)
assert.throws(function() { router.addRoute(':test') }, /forward slash/)
assert.throws(function() { router.addRoute('test/test2') }, /forward slash/)
})
t.test('fail if missing handler', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('/') }, /handler/)
assert.throws(function() { router.addRoute('/test') }, /handler/)
assert.throws(function() { router.addRoute('/:test') }, /handler/)
})
t.test('fail if missing handler but has middleware', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('/', [function() {}]) }, /handler/)
assert.throws(function() { router.addRoute('/test', [function() {}]) }, /handler/)
assert.throws(function() { router.addRoute('/:test', [function() {}]) }, /handler/)
})
t.test('fail if adding non-name route', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('//', function() {}) }, /path/)
assert.throws(function() { router.addRoute('/test//test2', function() {}) }, /path/)
assert.throws(function() { router.addRoute('/test/:test//bla', function() {}) }, /path/)
})
t.test('fail if adding non-name param route', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('/:/', function() {}) }, /path/)
assert.throws(function() { router.addRoute('/test/:/test2', function() {}) }, /path/)
assert.throws(function() { router.addRoute('/test/:test/:/bla', function() {}) }, /path/)
})
t.test('fail if adding different named param to existing route', function() {
let router = new FlaskaRouter()
router.addRoute('/:test', function() {})
router.addRoute('/:test/asdf/:bla', function() {})
router.addRoute('/:test/bla', function() {})
router.addRoute('/bla/bla', function() {})
router.addRoute('/bla/bla/bla', function() {})
assert.throws(function() { router.addRoute('/:asdf/', function() {}) }, /param/)
assert.throws(function() { router.addRoute('/:test/asdf/:foobar', function() {}) }, /param/)
})
t.test('fail if adding multiple fullparam', function() {
let router = new FlaskaRouter()
router.addRoute('/:test', function() {})
router.addRoute('/:test/asdf/::bla', function() {})
router.addRoute('/:test/bla', function() {})
router.addRoute('/:test/::bla', function() {})
router.addRoute('/bla/bla/bla', function() {})
assert.throws(function() { router.addRoute('/:test/asdf/::bla/fail', function() {}) }, /full.+param/)
assert.throws(function() { router.addRoute('/:test/::bla/test', function() {}) }, /full.+param/)
assert.throws(function() { router.addRoute('/:test/:bla', function() {}) }, /full.+param/)
assert.throws(function() { router.addRoute('/::test', function() {}) }, /partial.+param/)
})
t.test('add route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/b/c', assertHandler)
let result = router.match('/a/b/c')
assert.strictEqual(result.handler, assertHandler)
})
t.test('add param route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/:b/c', assertHandler)
assert.ok(router.root.children.get('a'))
assert.ok(router.root.children.get('a').children.get('__param'))
assert.strictEqual(router.root.children.get('a').children.get('__param').paramName, 'b')
assert.ok(router.root.children.get('a').children.get('__param').children.get('c'))
assert.strictEqual(router.root.children.get('a').children.get('__param').children.get('c').handler, assertHandler)
})
t.test('add full param route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/::b', assertHandler)
assert.ok(router.root.children.get('a'))
assert.ok(router.root.children.get('a').children.get('__fullparam'))
assert.strictEqual(router.root.children.get('a').children.get('__fullparam').fullparamName, 'b')
assert.strictEqual(router.root.children.get('a').children.get('__fullparam').handler, assertHandler)
})
t.test('add param route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/:b/c', assertHandler)
assert.ok(router.root.children.get('a'))
assert.ok(router.root.children.get('a').children.get('__param'))
assert.strictEqual(router.root.children.get('a').children.get('__param').paramName, 'b')
assert.ok(router.root.children.get('a').children.get('__param').children.get('c'))
assert.strictEqual(router.root.children.get('a').children.get('__param').children.get('c').handler, assertHandler)
})
t.test('support single middlewares correctly', function() {
let assertHandler = function() { return 1 }
let assertMiddleware = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a', assertMiddleware, assertHandler)
assert.ok(router.root.children.get('a'))
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
})
t.test('support multi middlewares correctly', function() {
let assertHandler = function() { return 1 }
let assertMiddleware = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a', [assertMiddleware], assertHandler)
router.addRoute('/b', [assertMiddleware, assertMiddleware], assertHandler)
assert.ok(router.root.children.get('a'))
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
assert.ok(router.root.children.get('b'))
assert.strictEqual(router.root.children.get('b').handler, assertHandler)
assert.strictEqual(router.root.children.get('b').middlewares.length, 2)
assert.strictEqual(router.root.children.get('b').middlewares[0], assertMiddleware)
assert.strictEqual(router.root.children.get('b').middlewares[1], assertMiddleware)
})
})
t.describe('#match()', function() {
t.test('match basic paths', function() {
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test', function() { assertMatched = true })
let result = router.match('/test')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
})
t.test('return middlewares', function() {
let assertMatched = false
let assertMiddleware = function() { assertMatched = true }
let router = new FlaskaRouter()
router.addRoute('/test', assertMiddleware, function() { })
let result = router.match('/test')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 1)
result.middlewares[0]()
assert.strictEqual(assertMatched, true)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 1)
result.middlewares[0]()
assert.strictEqual(assertMatched, true)
})
t.test('match variable paths', function() {
const assertParameter = 'bla'
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { assertMatched = true })
let result = router.match('/test/' + assertParameter)
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/' + assertParameter + '/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
})
t.test('match paths properly', function() {
let assertMatched = true
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { assertMatched = false })
router.addRoute('/test/:id/test1', function() { })
let result = router.match('/test/asdf/test1')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, 'asdf')
// Test with extra slash at the end
result = router.match('/test/asdf/test1/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, 'asdf')
})
t.test('return null when no match is found', function() {
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { })
router.addRoute('/test/:id/test1', function() { })
let result = router.match('/test/asdf/test2')
assert.notOk(result)
})
})