hono 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -8
- package/example/basic/index.js +7 -0
- package/example/basic/package-lock.json +13 -0
- package/example/basic/package.json +11 -0
- package/example/basic/wrangler.toml +8 -0
- package/hono.mini.js +1 -0
- package/package.json +5 -1
- package/src/hono.js +64 -38
- package/src/hono.test.js +3 -3
- package/src/{router.js → node.js} +48 -37
- package/src/node.root.test.js +17 -0
- package/src/node.test.js +109 -0
- package/src/router.test.js +62 -52
package/README.md
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hono
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Hono [炎] - Tiny web framework for Cloudflare Workers and others.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const app = Hono()
|
|
7
|
+
|
|
8
|
+
app.get('/', () => new Response('Hono!!'))
|
|
9
|
+
|
|
10
|
+
app.fire()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Feature
|
|
14
|
+
|
|
15
|
+
- Fast - the router is implemented with Trie-Tree structure.
|
|
16
|
+
- Tiny - use only standard API.
|
|
17
|
+
- Portable - zero dependencies.
|
|
18
|
+
- Optimized for Cloudflare Workers.
|
|
4
19
|
|
|
5
20
|
## Install
|
|
6
21
|
|
|
@@ -14,18 +29,48 @@ or
|
|
|
14
29
|
$ npm install hono
|
|
15
30
|
```
|
|
16
31
|
|
|
17
|
-
##
|
|
32
|
+
## Routing
|
|
33
|
+
|
|
34
|
+
### Basic
|
|
18
35
|
|
|
19
36
|
```js
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
app.get('/', () => 'GET /')
|
|
38
|
+
app.post('/', () => 'POST /')
|
|
39
|
+
app.get('/wild/*/card', () => 'GET /wild/*/card')
|
|
40
|
+
```
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
### Named Parameter
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
```js
|
|
45
|
+
app.get('/user/:name', (req) => {
|
|
46
|
+
const name = req.params('name')
|
|
47
|
+
...
|
|
48
|
+
})
|
|
26
49
|
```
|
|
27
50
|
|
|
28
|
-
|
|
51
|
+
### Regexp
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (req) => {
|
|
55
|
+
const date = req.params('date')
|
|
56
|
+
const title = req.params('title')
|
|
57
|
+
...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Chained Route
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
app
|
|
64
|
+
.route('/api/book')
|
|
65
|
+
.get(() => 'GET /api/book')
|
|
66
|
+
.post(() => 'POST /api/book')
|
|
67
|
+
.put(() => 'PUT /api/book')
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Related projects
|
|
71
|
+
|
|
72
|
+
- goblin <https://github.com/bmf-san/goblin>
|
|
73
|
+
- itty-router <https://github.com/kwhitley/itty-router>
|
|
29
74
|
|
|
30
75
|
## Author
|
|
31
76
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sandbox",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
|
10
|
+
"license": "ISC"
|
|
11
|
+
}
|
package/hono.mini.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const Router=require("./router");class Route{constructor(t,e){this.method=t;this.handler=e}}class App{constructor(){this.router=new Router}addRoute(t,e,n){this.router.add(e,new Route(t,n))}handle(t){const e=this.dispatch(t.request);return t.respondWith(e)}dispatch(t){const e=new URL(t.url);const n=e.pathname;const r=this.router.match(n);if(!r){return this.notFound()}const o=t.method.toLowerCase();const s=r[0];if(s.method==o){const i=s.handler;return i(t)}return this.notFound()}notFound(){return new Response("Not Found",{status:404,headers:{"content-type":"text/plain"}})}fire(){addEventListener("fetch",t=>{this.handle(t)})}}const proxyHandler={get:(e,n)=>(...t)=>{if(e.constructor.prototype.hasOwnProperty(n)){return e[n](t[0])}else{e.addRoute(n,t[0],t[1]);return}}};const app=new App;function Hono(){return new Proxy(app,proxyHandler)}module.exports=Hono;class Router{constructor(){this.node=new Node({label:"/"})}add(t,e){this.node.insert(t,e)}match(t){return this.node.search(t)}}class Node{constructor({label:t,stuff:e,children:n}={}){this.label=t||"";this.stuff=e||{};this.children=n||[]}insert(t,e){let n=this;if(t=="/"){n.label=t;n.stuff=e}const r=this.splitPath(t);for(const o of r){let t=n.children[o];if(t){n=t}else{n.children[o]=new Node({label:o,stuff:e,children:[]});n=n.children[o]}}}splitPath(t){const e=[];for(const n of t.split("/")){if(n){e.push(n)}}return e}getPattern(t){const e=t.match(/^\:.+?\{(.+)\}$/);if(e){return"("+e[1]+")"}return"(.+)"}getParamName(t){const e=t.match(/^\:([^\{\}]+)/);if(e){return e[1]}}noRoute(){return null}search(t){let e=this;const n={};for(const r of this.splitPath(t)){const o=e.children[r];if(o){e=o;continue}if(Object.keys(e.children).length==0){if(e.label!=r){return this.noRoute()}break}let t=false;for(const s in e.children){if(s=="*"){e=e.children[s];t=true;break}if(s.match(/^:/)){const i=this.getPattern(s);const c=r.match(new RegExp(i));if(c){const h=this.getParamName(s);n[h]=c[0];e=e.children[s];t=true;break}return this.noRoute()}}if(t==false){return this.noRoute()}}return[e.stuff,n]}}module.exports=Router;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Minimal web framework for Cloudflare Workers",
|
|
5
5
|
"main": "src/hono.js",
|
|
6
6
|
"scripts": {
|
|
@@ -16,5 +16,9 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"jest": "^27.4.5",
|
|
18
18
|
"node-fetch": "^2.6.6"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"global": "^4.4.0",
|
|
22
|
+
"wrangler": "^0.0.0-beta.6"
|
|
19
23
|
}
|
|
20
24
|
}
|
package/src/hono.js
CHANGED
|
@@ -1,77 +1,103 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Node = require('./node')
|
|
2
2
|
|
|
3
|
-
class
|
|
4
|
-
constructor(
|
|
5
|
-
this.
|
|
6
|
-
this.
|
|
3
|
+
class Router {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.node = new Node()
|
|
6
|
+
this.tempPath = '/'
|
|
7
7
|
}
|
|
8
|
-
}
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
route(path) {
|
|
10
|
+
this.tempPath = path
|
|
11
|
+
return WrappedRouter(this)
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
addRoute(method, path, handler) {
|
|
16
|
-
this.
|
|
15
|
+
this.node.insert(method, path, handler)
|
|
16
|
+
return WrappedRouter(this)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
matchRoute(method, path) {
|
|
20
|
+
method = method.toLowerCase()
|
|
21
|
+
const res = this.node.search(method, path)
|
|
22
|
+
return res
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
handle(event) {
|
|
20
|
-
const
|
|
26
|
+
const result = this.dispatch(event.request)
|
|
27
|
+
const response = this.filter(result)
|
|
21
28
|
return event.respondWith(response)
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
filter(result) {
|
|
32
|
+
if (result instanceof Response) {
|
|
33
|
+
return result
|
|
34
|
+
}
|
|
35
|
+
if (typeof result === 'object') {
|
|
36
|
+
return new Response(JSON.stringify(result), {
|
|
37
|
+
status: 200,
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
if (typeof result === 'string') {
|
|
44
|
+
return new Response(result, {
|
|
45
|
+
status: 200,
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'text/plain',
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
return this.notFound()
|
|
52
|
+
}
|
|
53
|
+
|
|
24
54
|
dispatch(request) {
|
|
25
55
|
const url = new URL(request.url)
|
|
26
56
|
const path = url.pathname
|
|
27
|
-
const
|
|
57
|
+
const method = request.method
|
|
58
|
+
const res = this.matchRoute(method, path)
|
|
28
59
|
|
|
29
|
-
if (!
|
|
60
|
+
if (!res) {
|
|
30
61
|
return this.notFound()
|
|
31
62
|
}
|
|
32
63
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
if (route.method == method) {
|
|
36
|
-
const handler = route.handler
|
|
37
|
-
return handler(request)
|
|
38
|
-
}
|
|
39
|
-
return this.notFound()
|
|
64
|
+
const handler = res.handler
|
|
65
|
+
return handler(request)
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
notFound() {
|
|
43
69
|
return new Response('Not Found', {
|
|
44
70
|
status: 404,
|
|
45
71
|
headers: {
|
|
46
|
-
'content-type': 'text/plain'
|
|
47
|
-
}
|
|
72
|
+
'content-type': 'text/plain',
|
|
73
|
+
},
|
|
48
74
|
})
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
fire() {
|
|
52
|
-
addEventListener(
|
|
78
|
+
addEventListener('fetch', (event) => {
|
|
53
79
|
this.handle(event)
|
|
54
80
|
})
|
|
55
81
|
}
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
const proxyHandler = {
|
|
59
|
-
get:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
get:
|
|
86
|
+
(target, prop) =>
|
|
87
|
+
(...args) => {
|
|
88
|
+
if (target.constructor.prototype.hasOwnProperty(prop)) {
|
|
89
|
+
return target[prop](...args)
|
|
90
|
+
} else {
|
|
91
|
+
if (args.length === 1) {
|
|
92
|
+
return target.addRoute(prop, target.tempPath, ...args)
|
|
93
|
+
}
|
|
94
|
+
return target.addRoute(prop, ...args)
|
|
95
|
+
}
|
|
96
|
+
},
|
|
67
97
|
}
|
|
68
98
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
function Hono() {
|
|
72
|
-
return new Proxy(
|
|
73
|
-
app, proxyHandler
|
|
74
|
-
)
|
|
99
|
+
const WrappedRouter = (router = new Router()) => {
|
|
100
|
+
return new Proxy(router, proxyHandler)
|
|
75
101
|
}
|
|
76
102
|
|
|
77
|
-
module.exports =
|
|
103
|
+
module.exports = WrappedRouter
|
package/src/hono.test.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
const Hono = require('./hono')
|
|
2
2
|
const fetch = require('node-fetch')
|
|
3
3
|
|
|
4
|
-
const app =
|
|
4
|
+
const app = Hono()
|
|
5
5
|
|
|
6
6
|
describe('GET match', () => {
|
|
7
7
|
app.get('/hello', () => {
|
|
8
8
|
return new fetch.Response('hello', {
|
|
9
|
-
status: 200
|
|
9
|
+
status: 200,
|
|
10
10
|
})
|
|
11
11
|
})
|
|
12
12
|
app.notFound = () => {
|
|
13
13
|
return new fetch.Response('not found', {
|
|
14
|
-
status: 404
|
|
14
|
+
status: 404,
|
|
15
15
|
})
|
|
16
16
|
}
|
|
17
17
|
it('GET /hello is ok', () => {
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
const methodNameOfAll = 'all'
|
|
2
2
|
|
|
3
|
-
class
|
|
4
|
-
constructor() {
|
|
5
|
-
this.
|
|
6
|
-
|
|
7
|
-
add(path, stuff) {
|
|
8
|
-
this.node.insert(path, stuff);
|
|
9
|
-
}
|
|
10
|
-
match(path) {
|
|
11
|
-
return this.node.search(path);
|
|
3
|
+
class Result {
|
|
4
|
+
constructor({ handler, params } = {}) {
|
|
5
|
+
this.handler = handler
|
|
6
|
+
this.params = params || {}
|
|
12
7
|
}
|
|
13
8
|
}
|
|
14
9
|
|
|
15
10
|
class Node {
|
|
16
|
-
constructor({ label,
|
|
17
|
-
this.label = label ||
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
11
|
+
constructor({ method, label, handler, children } = {}) {
|
|
12
|
+
this.label = label || ''
|
|
13
|
+
this.children = children || []
|
|
14
|
+
this.method = {}
|
|
15
|
+
if (method && handler) {
|
|
16
|
+
this.method[method] = handler
|
|
17
|
+
}
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
insert(path,
|
|
20
|
+
insert(method, path, handler) {
|
|
23
21
|
let curNode = this
|
|
24
|
-
if (path == '/') {
|
|
25
|
-
curNode.label = path
|
|
26
|
-
curNode.stuff = stuff
|
|
27
|
-
}
|
|
28
22
|
const ps = this.splitPath(path)
|
|
29
23
|
for (const p of ps) {
|
|
30
24
|
let nextNode = curNode.children[p]
|
|
31
25
|
if (nextNode) {
|
|
32
26
|
curNode = nextNode
|
|
33
27
|
} else {
|
|
34
|
-
curNode.children[p] = new Node({
|
|
28
|
+
curNode.children[p] = new Node({
|
|
29
|
+
label: p,
|
|
30
|
+
handler: handler,
|
|
31
|
+
})
|
|
35
32
|
curNode = curNode.children[p]
|
|
36
33
|
}
|
|
37
34
|
}
|
|
35
|
+
if (!curNode.method[method]) {
|
|
36
|
+
curNode.method[method] = handler
|
|
37
|
+
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
splitPath(path) {
|
|
41
|
-
|
|
41
|
+
let ps = ['/']
|
|
42
42
|
for (const p of path.split('/')) {
|
|
43
43
|
if (p) {
|
|
44
44
|
ps.push(p)
|
|
@@ -64,30 +64,29 @@ class Node {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
search(path) {
|
|
72
|
-
|
|
67
|
+
search(method, path) {
|
|
73
68
|
let curNode = this
|
|
74
69
|
const params = {}
|
|
75
70
|
|
|
71
|
+
if (path === '/') {
|
|
72
|
+
const root = this.children['/']
|
|
73
|
+
if (!root.method[method]) {
|
|
74
|
+
return this.noRoute()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
76
78
|
for (const p of this.splitPath(path)) {
|
|
77
|
-
|
|
79
|
+
let nextNode = curNode.children[p]
|
|
80
|
+
|
|
78
81
|
if (nextNode) {
|
|
79
82
|
curNode = nextNode
|
|
80
83
|
continue
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
if (curNode.label != p) {
|
|
84
|
-
return this.noRoute()
|
|
85
|
-
}
|
|
86
|
-
break
|
|
87
|
-
}
|
|
85
|
+
|
|
88
86
|
let isParamMatch = false
|
|
89
87
|
for (const key in curNode.children) {
|
|
90
|
-
if (key
|
|
88
|
+
if (key === '*') {
|
|
89
|
+
// Wildcard
|
|
91
90
|
curNode = curNode.children[key]
|
|
92
91
|
isParamMatch = true
|
|
93
92
|
break
|
|
@@ -109,8 +108,20 @@ class Node {
|
|
|
109
108
|
return this.noRoute()
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
|
-
|
|
111
|
+
|
|
112
|
+
let handler = curNode.method[methodNameOfAll] || curNode.method[method]
|
|
113
|
+
|
|
114
|
+
if (handler) {
|
|
115
|
+
const res = new Result({ handler: handler, params: params })
|
|
116
|
+
return res
|
|
117
|
+
} else {
|
|
118
|
+
return this.noRoute()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
noRoute() {
|
|
123
|
+
return null
|
|
113
124
|
}
|
|
114
125
|
}
|
|
115
126
|
|
|
116
|
-
module.exports =
|
|
127
|
+
module.exports = Node
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const Node = require('./node')
|
|
2
|
+
|
|
3
|
+
describe('Basic Usage', () => {
|
|
4
|
+
const node = new Node()
|
|
5
|
+
node.insert('get', '/', 'get root')
|
|
6
|
+
it('get /', () => {
|
|
7
|
+
expect(node.search('get', '/')).not.toBeNull()
|
|
8
|
+
})
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('Basic Usage', () => {
|
|
12
|
+
const node = new Node()
|
|
13
|
+
node.insert('get', '/hello', 'get hello')
|
|
14
|
+
it('get /', () => {
|
|
15
|
+
expect(node.search('get', '/')).toBeNull()
|
|
16
|
+
})
|
|
17
|
+
})
|
package/src/node.test.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const Node = require('./node')
|
|
2
|
+
const node = new Node()
|
|
3
|
+
|
|
4
|
+
describe('Util Methods', () => {
|
|
5
|
+
it('node.splitPath', () => {
|
|
6
|
+
let ps = node.splitPath('/')
|
|
7
|
+
expect(ps[0]).toBe('/')
|
|
8
|
+
ps = node.splitPath('/hello')
|
|
9
|
+
expect(ps[0]).toBe('/')
|
|
10
|
+
expect(ps[1]).toBe('hello')
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('Basic Usage', () => {
|
|
15
|
+
node.insert('get', '/hello', 'get hello')
|
|
16
|
+
node.insert('post', '/hello', 'post hello')
|
|
17
|
+
node.insert('get', '/hello/foo', 'get hello foo')
|
|
18
|
+
|
|
19
|
+
it('get, post /hello', () => {
|
|
20
|
+
expect(node.search('get', '/')).toBeNull()
|
|
21
|
+
expect(node.search('post', '/')).toBeNull()
|
|
22
|
+
|
|
23
|
+
expect(node.search('get', '/hello').handler).toBe('get hello')
|
|
24
|
+
expect(node.search('post', '/hello').handler).toBe('post hello')
|
|
25
|
+
expect(node.search('put', '/hello')).toBeNull()
|
|
26
|
+
})
|
|
27
|
+
it('get /nothing', () => {
|
|
28
|
+
expect(node.search('get', '/nothing')).toBeNull()
|
|
29
|
+
})
|
|
30
|
+
it('/hello/foo, /hello/bar', () => {
|
|
31
|
+
expect(node.search('get', '/hello/foo').handler).toBe('get hello foo')
|
|
32
|
+
expect(node.search('post', '/hello/foo')).toBeNull()
|
|
33
|
+
expect(node.search('get', '/hello/bar')).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
it('/hello/foo/bar', () => {
|
|
36
|
+
expect(node.search('get', '/hello/foo/bar')).toBeNull()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('Name path', () => {
|
|
41
|
+
it('get /entry/123', () => {
|
|
42
|
+
node.insert('get', '/entry/:id', 'get entry')
|
|
43
|
+
let res = node.search('get', '/entry/123')
|
|
44
|
+
expect(res).not.toBeNull()
|
|
45
|
+
expect(res.handler).toBe('get entry')
|
|
46
|
+
expect(res.params).not.toBeNull()
|
|
47
|
+
expect(res.params['id']).toBe('123')
|
|
48
|
+
expect(res.params['id']).not.toBe('1234')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('get /entry/456/comment', () => {
|
|
52
|
+
node.insert('get', '/entry/:id', 'get entry')
|
|
53
|
+
res = node.search('get', '/entry/456/comment')
|
|
54
|
+
expect(res).toBeNull()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('get /entry/789/comment/123', () => {
|
|
58
|
+
node.insert('get', '/entry/:id/comment/:comment_id', 'get comment')
|
|
59
|
+
res = node.search('get', '/entry/789/comment/123')
|
|
60
|
+
expect(res).not.toBeNull()
|
|
61
|
+
expect(res.handler).toBe('get comment')
|
|
62
|
+
expect(res.params['id']).toBe('789')
|
|
63
|
+
expect(res.params['comment_id']).toBe('123')
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('Wildcard', () => {
|
|
68
|
+
it('/wildcard-abc/xxxxxx/wildcard-efg', () => {
|
|
69
|
+
node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard')
|
|
70
|
+
res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')
|
|
71
|
+
expect(res).not.toBeNull()
|
|
72
|
+
expect(res.handler).toBe('wildcard')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('Regexp', () => {
|
|
77
|
+
node.insert(
|
|
78
|
+
'get',
|
|
79
|
+
'/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}',
|
|
80
|
+
'regexp'
|
|
81
|
+
)
|
|
82
|
+
it('/regexp-abc/123/comment/abc', () => {
|
|
83
|
+
res = node.search('get', '/regex-abc/123/comment/abc')
|
|
84
|
+
expect(res).not.toBeNull()
|
|
85
|
+
expect(res.handler).toBe('regexp')
|
|
86
|
+
expect(res.params['id']).toBe('123')
|
|
87
|
+
expect(res.params['comment_id']).toBe('abc')
|
|
88
|
+
})
|
|
89
|
+
it('/regexp-abc/abc', () => {
|
|
90
|
+
res = node.search('get', '/regex-abc/abc')
|
|
91
|
+
expect(res).toBeNull()
|
|
92
|
+
})
|
|
93
|
+
it('/regexp-abc/123/comment/123', () => {
|
|
94
|
+
res = node.search('get', '/regex-abc/123/comment/123')
|
|
95
|
+
expect(res).toBeNull()
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('All', () => {
|
|
100
|
+
node.insert('all', '/all-methods', 'all methods')
|
|
101
|
+
it('/all-methods', () => {
|
|
102
|
+
res = node.search('get', '/all-methods')
|
|
103
|
+
expect(res).not.toBeNull()
|
|
104
|
+
expect(res.handler).toBe('all methods')
|
|
105
|
+
res = node.search('put', '/all-methods')
|
|
106
|
+
expect(res).not.toBeNull()
|
|
107
|
+
expect(res.handler).toBe('all methods')
|
|
108
|
+
})
|
|
109
|
+
})
|
package/src/router.test.js
CHANGED
|
@@ -1,66 +1,76 @@
|
|
|
1
|
-
const Router = require('./
|
|
1
|
+
const Router = require('./hono')
|
|
2
|
+
let router = Router()
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
describe('Basic Usage', () => {
|
|
5
|
+
it('get, post hello', () => {
|
|
6
|
+
router.get('/hello', 'get hello')
|
|
7
|
+
router.post('/hello', 'post hello')
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let match = router.match('/')
|
|
9
|
-
expect(match).not.toBeNull()
|
|
10
|
-
expect(match[0]).toBe('root')
|
|
11
|
-
match = router.match('/foo')
|
|
12
|
-
expect(match).toBeNull()
|
|
13
|
-
})
|
|
9
|
+
let res = router.matchRoute('GET', '/hello')
|
|
10
|
+
expect(res).not.toBeNull()
|
|
11
|
+
expect(res.handler).toBe('get hello')
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
expect(match).toBeNull()
|
|
19
|
-
match = router.match('/hello')
|
|
20
|
-
expect(match[0]).toBe('hello')
|
|
21
|
-
})
|
|
22
|
-
})
|
|
13
|
+
res = router.matchRoute('POST', '/hello')
|
|
14
|
+
expect(res).not.toBeNull()
|
|
15
|
+
expect(res.handler).toBe('post hello')
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
router.add('/entry/:id/:comment', 'entry-id-comment')
|
|
27
|
-
router.add('/year/:year{[0-9]{4}}/:month{[0-9]{2}}', 'date-regex')
|
|
17
|
+
res = router.matchRoute('PUT', '/hello')
|
|
18
|
+
expect(res).toBeNull()
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
expect(match[0]).toBe('entry-id')
|
|
32
|
-
expect(match[1]['id']).toBe('123')
|
|
20
|
+
res = router.matchRoute('GET', '/')
|
|
21
|
+
expect(res).toBeNull()
|
|
33
22
|
})
|
|
23
|
+
})
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
expect(
|
|
25
|
+
describe('Complex', () => {
|
|
26
|
+
it('Named Param', () => {
|
|
27
|
+
router.get('/entry/:id', 'get entry')
|
|
28
|
+
res = router.matchRoute('GET', '/entry/123')
|
|
29
|
+
expect(res).not.toBeNull()
|
|
30
|
+
expect(res.handler).toBe('get entry')
|
|
31
|
+
expect(res.params['id']).toBe('123')
|
|
40
32
|
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
46
|
-
expect(match[1]['month']).toBe('12')
|
|
33
|
+
it('Wildcard', () => {
|
|
34
|
+
router.get('/wild/*/card', 'get wildcard')
|
|
35
|
+
res = router.matchRoute('GET', '/wild/xxx/card')
|
|
36
|
+
expect(res).not.toBeNull()
|
|
37
|
+
expect(res.handler).toBe('get wildcard')
|
|
47
38
|
})
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(
|
|
52
|
-
|
|
53
|
-
expect(
|
|
39
|
+
it('Regexp', () => {
|
|
40
|
+
router.get('/post/:date{[0-9]+}/:title{[a-z]+}', 'get post')
|
|
41
|
+
res = router.matchRoute('GET', '/post/20210101/hello')
|
|
42
|
+
expect(res).not.toBeNull()
|
|
43
|
+
expect(res.handler).toBe('get post')
|
|
44
|
+
expect(res.params['date']).toBe('20210101')
|
|
45
|
+
expect(res.params['title']).toBe('hello')
|
|
46
|
+
res = router.matchRoute('GET', '/post/onetwothree')
|
|
47
|
+
expect(res).toBeNull()
|
|
48
|
+
res = router.matchRoute('GET', '/post/123/123')
|
|
49
|
+
expect(res).toBeNull()
|
|
54
50
|
})
|
|
55
51
|
})
|
|
56
52
|
|
|
57
|
-
describe('
|
|
58
|
-
it('
|
|
59
|
-
router =
|
|
60
|
-
router.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
expect(
|
|
53
|
+
describe('Chained Route', () => {
|
|
54
|
+
it('Return rooter object', () => {
|
|
55
|
+
router = router.patch('/hello', 'patch hello')
|
|
56
|
+
expect(router).not.toBeNull()
|
|
57
|
+
router = router.delete('/hello', 'delete hello')
|
|
58
|
+
res = router.matchRoute('DELETE', '/hello')
|
|
59
|
+
expect(res).not.toBeNull()
|
|
60
|
+
expect(res.handler).toBe('delete hello')
|
|
61
|
+
})
|
|
62
|
+
it('Chain with route method', () => {
|
|
63
|
+
router.route('/api/book').get('get book').post('post book').put('put book')
|
|
64
|
+
res = router.matchRoute('GET', '/api/book')
|
|
65
|
+
expect(res).not.toBeNull()
|
|
66
|
+
expect(res.handler).toBe('get book')
|
|
67
|
+
res = router.matchRoute('POST', '/api/book')
|
|
68
|
+
expect(res).not.toBeNull()
|
|
69
|
+
expect(res.handler).toBe('post book')
|
|
70
|
+
res = router.matchRoute('PUT', '/api/book')
|
|
71
|
+
expect(res).not.toBeNull()
|
|
72
|
+
expect(res.handler).toBe('put book')
|
|
73
|
+
res = router.matchRoute('DELETE', '/api/book')
|
|
74
|
+
expect(res).toBeNull()
|
|
65
75
|
})
|
|
66
76
|
})
|