Finished implementing router. Backuping non-class method
This commit is contained in:
parent
b8df546f18
commit
26c9b4a27e
9 changed files with 694 additions and 139 deletions
|
@ -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
2
benchmark/index.bat
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
start /B /WAIT /REALTIME node index.js
|
||||||
|
pause
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
368
flaska.mjs
368
flaska.mjs
|
@ -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() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
96
test/client.mjs
Normal 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)
|
||||||
|
}
|
|
@ -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
233
test/router.test.mjs
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue