-
Notifications
You must be signed in to change notification settings - Fork 4
/
Router.js
134 lines (119 loc) · 5 KB
/
Router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { isBasicObject } from 'source/common/check'
import { createTreeDepthFirstSearch } from 'source/common/data/Tree'
import {
parseRouteToMap,
findRouteFromMap,
getRouteParamAny as getRouteMapParamAny,
getRouteParam as getRouteMapParam
} from 'source/common/module/RouteMap'
import { BASIC_EXTENSION_MAP } from 'source/common/module/MIME'
import { COMMON_LAYOUT, COMMON_STYLE } from 'source/node/server/commonHTML'
import { responderSendBufferCompress, prepareBufferData } from './Send'
const METHOD_MAP = {
GET: '/GET',
POST: '/POST',
PUT: '/PUT',
PATCH: '/PATCH',
DELETE: '/DELETE',
HEAD: '/HEAD',
OPTIONS: '/OPTIONS',
CONNECT: '/CONNECT',
TRACE: '/TRACE'
}
const appendRouteMap = (routeMap, route = '/', method = 'GET', routeResponder) => {
if (Array.isArray(route)) return route.reduce((o, v) => appendRouteMap(routeMap, v, method, routeResponder), routeMap)
if (Array.isArray(method)) return method.reduce((o, v) => appendRouteMap(routeMap, route, v, routeResponder), routeMap)
const { routeNode, paramNameList } = parseRouteToMap(routeMap, route)
const methodTag = METHOD_MAP[ method ]
if (!methodTag) throw new Error(`invalid method [${method}] for: ${route}`)
if (routeNode[ methodTag ]) throw new Error(`duplicate method [${method}] for: ${route}`)
if (typeof (routeResponder) !== 'function') throw new Error(`invalid responder for: ${route}`)
routeNode[ methodTag ] = { route, paramNameList, routeResponder }
return routeMap
}
const createRouteMap = (configList) => configList.reduce((o, [ route, method, routeResponder ]) => appendRouteMap(o, route, method, routeResponder), {})
const createResponderRouter = ({
routeMap,
baseUrl = '', // NOTE: normally just rest spread the server option here
getMethodUrl = createGetMethodUrl(new URL(baseUrl))
}) => (store) => {
const { method, url } = getMethodUrl(store)
const methodTag = METHOD_MAP[ method ]
if (methodTag === undefined) return // throw new Error(`invalid method [${method}] from: ${url.href}`)
const routeDate = findRouteFromMap(routeMap, url.pathname)
if (routeDate === undefined) return // throw new Error(`no method [${method}] for: ${url.pathname}`)
const { routeNode, paramValueList } = routeDate
if (routeNode[ methodTag ] === undefined) return // throw new Error(`no method [${method}] for: ${url.pathname}`)
const { route, paramNameList, routeResponder } = routeNode[ methodTag ]
const paramMap = paramNameList.reduce((o, paramName, index) => {
o[ paramName ] = paramValueList[ index ]
return o
}, {})
return routeResponder(store, store.setState({ method, url, route, paramMap }))
}
const createGetMethodUrl = (baseUrl) => ({ request: { method, url } }) => ({ method, url: new URL(url.replace(REGEXP_URL_REPLACE, '/'), baseUrl) })
const REGEXP_URL_REPLACE = /\/\//g // NOTE: check for `new URL('//a/list/', new URL('http://0.0.0.0/'))`
const getRouteParamAny = (store) => getRouteMapParamAny(store.getState())
const getRouteParam = (store, paramName) => getRouteMapParam(store.getState(), paramName)
const describeRouteMap = (routeMap) => {
const routeInfoList = [ /* { method, route } */ ]
const methodValueSet = new Set(Object.values(METHOD_MAP))
routeMapDepthFirstSearch(
[ 'ROOT', routeMap ],
([ key, { route } ]) => { methodValueSet.has(key) && routeInfoList.push({ method: key, route }) }
)
return routeInfoList
}
const routeMapDepthFirstSearch = createTreeDepthFirstSearch(
([ key, routeNode ]) => isBasicObject(routeNode) && Object.entries(routeNode)
)
// const SAMPLE_ROUTE_RESPONDER = (store, { url, route, method, paramMap }) => {}
// const SAMPLE_RESULT_ROUTE_MAP = {
// '': {
// // GET /users/list
// // GET /users/
// // GET /users
// users: {
// list: { '/GET': DEFAULT_ROUTE_PROCESSOR },
// '': { '/GET': DEFAULT_ROUTE_PROCESSOR },
// '/GET': DEFAULT_ROUTE_PROCESSOR
// },
// // GET '/'
// '': { '/GET': DEFAULT_ROUTE_PROCESSOR },
// // GET '/static/*'
// static: { '/*': { '/GET': DEFAULT_ROUTE_PROCESSOR } },
// // DELETE '/user/:userId'
// user: { '/:PARAM': { '/DELETE': DEFAULT_ROUTE_PROCESSOR } }
// }
// }
const createResponderRouteListHTML = ({
getRouteMap, // () => routeMap
extraBodyList
}) => {
let bufferData
return async (store) => {
if (bufferData === undefined) bufferData = await prepareBufferData(Buffer.from(getRouteListHTML(getRouteMap(), extraBodyList)), BASIC_EXTENSION_MAP.html)
return responderSendBufferCompress(store, bufferData)
}
}
const getRouteListHTML = (routeMap, extraBodyList = []) => COMMON_LAYOUT([
COMMON_STYLE(),
'<style>body { align-items: start; }</style>'
], [
'<h2>Route List</h2>',
'<table>',
...describeRouteMap(routeMap)
.map(({ method, route }) => `<tr><td><b>${method}</b></td><td>${method === '/GET' ? `<a href="${route}">${route}</a>` : route}</td></tr>`),
'</table>',
...extraBodyList
])
export {
METHOD_MAP,
createRouteMap,
createResponderRouter,
appendRouteMap,
getRouteParamAny,
getRouteParam,
describeRouteMap,
createResponderRouteListHTML
}