sleepy-serv 0.6.2 → 0.7.0
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 +1 -1
- package/dist/errors.d.ts +173 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/messages.d.ts +63 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/middleware.d.ts +19 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/socket.d.ts +50 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/utils.d.ts +125 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +17 -3
- package/src/{errors.js → errors.ts} +171 -80
- package/src/{index.js → index.ts} +180 -42
- package/src/messages.ts +237 -0
- package/src/middleware.ts +209 -0
- package/src/{socket.js → socket.ts} +247 -86
- package/src/utils.ts +204 -0
- package/src/messages.js +0 -164
- package/src/meta.js +0 -65
- package/src/middleware.js +0 -138
- package/src/utils.js +0 -49
package/src/utils.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { ErrorObject } from 'ajv'
|
|
2
|
+
import type { BunRequest, Server as BunServer } from 'bun'
|
|
3
|
+
|
|
4
|
+
export const HttpMethod = {
|
|
5
|
+
Head: 'HEAD',
|
|
6
|
+
Get: 'GET',
|
|
7
|
+
Post: 'POST',
|
|
8
|
+
Put: 'PUT',
|
|
9
|
+
Patch: 'PATCH',
|
|
10
|
+
Delete: 'DELETE',
|
|
11
|
+
} as const
|
|
12
|
+
|
|
13
|
+
export type HttpMethod = typeof HttpMethod[keyof typeof HttpMethod]
|
|
14
|
+
|
|
15
|
+
export const StatusCode = {
|
|
16
|
+
Continue: 100,
|
|
17
|
+
SwitchingProtocols: 101,
|
|
18
|
+
Processing: 102,
|
|
19
|
+
EarlyHints: 103,
|
|
20
|
+
|
|
21
|
+
Ok: 200,
|
|
22
|
+
Created: 201,
|
|
23
|
+
Accepted: 202,
|
|
24
|
+
NonAuthoritativeInformation: 203,
|
|
25
|
+
NoContent: 204,
|
|
26
|
+
ResetContent: 205,
|
|
27
|
+
PartialContent: 206,
|
|
28
|
+
MultiStatus: 207,
|
|
29
|
+
AlreadyReported: 208,
|
|
30
|
+
ImUsed: 226,
|
|
31
|
+
|
|
32
|
+
MultipleChoices: 300,
|
|
33
|
+
MovedPermanently: 301,
|
|
34
|
+
Found: 302,
|
|
35
|
+
SeeOther: 303,
|
|
36
|
+
NotModified: 304,
|
|
37
|
+
UseProxy: 305,
|
|
38
|
+
TemporaryRedirect: 307,
|
|
39
|
+
PermanentRedirect: 308,
|
|
40
|
+
|
|
41
|
+
BadRequest: 400,
|
|
42
|
+
Unauthorized: 401,
|
|
43
|
+
PaymentRequired: 402,
|
|
44
|
+
Forbidden: 403,
|
|
45
|
+
NotFound: 404,
|
|
46
|
+
MethodNotAllowed: 405,
|
|
47
|
+
NotAcceptable: 406,
|
|
48
|
+
ProxyAuthenticationRequired: 407,
|
|
49
|
+
RequestTimeout: 408,
|
|
50
|
+
Conflict: 409,
|
|
51
|
+
Gone: 410,
|
|
52
|
+
LengthRequired: 411,
|
|
53
|
+
PreconditionFailed: 412,
|
|
54
|
+
PayloadTooLarge: 413,
|
|
55
|
+
UriTooLong: 414,
|
|
56
|
+
UnsupportedMediaType: 415,
|
|
57
|
+
RangeNotSatisfiable: 416,
|
|
58
|
+
ExpectationFailed: 417,
|
|
59
|
+
ImATeapot: 418,
|
|
60
|
+
MisdirectedRequest: 421,
|
|
61
|
+
UnprocessableContent: 422,
|
|
62
|
+
Locked: 423,
|
|
63
|
+
FailedDependency: 424,
|
|
64
|
+
TooEarly: 425,
|
|
65
|
+
UpgradeRequired: 426,
|
|
66
|
+
PreconditionRequired: 428,
|
|
67
|
+
TooManyRequests: 429,
|
|
68
|
+
RequestHeaderFieldsTooLarge: 431,
|
|
69
|
+
UnavailableForLegalReasons: 451,
|
|
70
|
+
|
|
71
|
+
InternalServerError: 500,
|
|
72
|
+
NotImplemented: 501,
|
|
73
|
+
BadGateway: 502,
|
|
74
|
+
ServiceUnavailable: 503,
|
|
75
|
+
GatewayTimeout: 504,
|
|
76
|
+
HTTPVersionNotSupported: 505,
|
|
77
|
+
VariantAlsoNegotiates: 506,
|
|
78
|
+
InsufficientStorage: 507,
|
|
79
|
+
LoopDetected: 508,
|
|
80
|
+
NotExtended: 510,
|
|
81
|
+
NetworkAuthenticationRequired: 511,
|
|
82
|
+
} as const
|
|
83
|
+
|
|
84
|
+
export type StatusCode = typeof StatusCode[keyof typeof StatusCode]
|
|
85
|
+
|
|
86
|
+
export type ValidationError = Partial<ErrorObject>
|
|
87
|
+
|
|
88
|
+
export type FormattedError = {
|
|
89
|
+
path: string
|
|
90
|
+
message: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type NextFn = (data?: unknown) => Response | Promise<Response>
|
|
94
|
+
|
|
95
|
+
export type BaseRequest = {
|
|
96
|
+
method: HttpMethod
|
|
97
|
+
route: string
|
|
98
|
+
headers: Headers
|
|
99
|
+
params: Record<string, string>
|
|
100
|
+
query: Record<string, unknown>
|
|
101
|
+
json: () => Promise<unknown>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type EndpointRequest = BaseRequest & {
|
|
105
|
+
raw: BunRequest
|
|
106
|
+
server: Server
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type WebSocketRequest = BaseRequest & {
|
|
110
|
+
id: string
|
|
111
|
+
clientId: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type Request = EndpointRequest | WebSocketRequest
|
|
115
|
+
|
|
116
|
+
export type Middleware = (
|
|
117
|
+
req: Request,
|
|
118
|
+
res: unknown,
|
|
119
|
+
next: NextFn | null,
|
|
120
|
+
) => unknown
|
|
121
|
+
|
|
122
|
+
export type SocketOptions = {
|
|
123
|
+
disconnectThreshold?: number
|
|
124
|
+
heartbeatInterval?: number
|
|
125
|
+
maxTickets?: number
|
|
126
|
+
reclaimTtl?: number
|
|
127
|
+
ticketTtl?: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type SocketData = {
|
|
131
|
+
clientId: string
|
|
132
|
+
superseded: boolean
|
|
133
|
+
reaped: boolean
|
|
134
|
+
reaperHandle: ReturnType<typeof setTimeout> | null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type Server = BunServer<SocketData>
|
|
138
|
+
|
|
139
|
+
export type AppOptions = {
|
|
140
|
+
hostname?: string
|
|
141
|
+
mountPath?: string
|
|
142
|
+
middleware?: Middleware[]
|
|
143
|
+
ws?: SocketOptions
|
|
144
|
+
onClose?: () => Promise<void> | void
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function toSegments (pathString: string): string[] {
|
|
148
|
+
const [pathname] = String(pathString).split('?')
|
|
149
|
+
const segments = pathname.split('/')
|
|
150
|
+
|
|
151
|
+
if (pathname.startsWith('/')) {
|
|
152
|
+
segments.shift()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (pathname.endsWith('/')) {
|
|
156
|
+
segments.pop()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return segments
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function formatError (
|
|
163
|
+
prefix: string,
|
|
164
|
+
input: ValidationError,
|
|
165
|
+
): FormattedError {
|
|
166
|
+
const fixedPath = input.instancePath || '/'
|
|
167
|
+
const suffixPath = fixedPath.replace(/\//g, '.').replace('.', '')
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
path: [prefix, suffixPath].filter(item => item).join('.'),
|
|
171
|
+
message: input.message ?? '',
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function executeMiddlewareChain (
|
|
176
|
+
req: Request,
|
|
177
|
+
chain: Middleware[],
|
|
178
|
+
): Promise<Response> {
|
|
179
|
+
if (!chain.length) {
|
|
180
|
+
throw new RangeError('Middleware chain is empty')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const executeMiddleware = async (
|
|
184
|
+
index: number,
|
|
185
|
+
res: unknown,
|
|
186
|
+
): Promise<Response> => {
|
|
187
|
+
const currentMiddleware = chain[index]
|
|
188
|
+
const isLastMiddleware = index === chain.length - 1
|
|
189
|
+
|
|
190
|
+
const next = !isLastMiddleware ?
|
|
191
|
+
(data?: unknown) => executeMiddleware(index + 1, data)
|
|
192
|
+
: null
|
|
193
|
+
|
|
194
|
+
const result = await currentMiddleware(req, res, next)
|
|
195
|
+
|
|
196
|
+
if (result instanceof Response) {
|
|
197
|
+
return result
|
|
198
|
+
} else {
|
|
199
|
+
throw new TypeError('Handler does not return a Response object')
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return executeMiddleware(0, null)
|
|
204
|
+
}
|
package/src/messages.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import Ajv from 'ajv'
|
|
2
|
-
import addFormats from 'ajv-formats'
|
|
3
|
-
import crypto from 'node:crypto'
|
|
4
|
-
import { formatError } from './utils'
|
|
5
|
-
import { UnprocessableContentError } from './errors'
|
|
6
|
-
|
|
7
|
-
export const TYPES = {
|
|
8
|
-
REQUEST: 'request',
|
|
9
|
-
RESPONSE: 'response',
|
|
10
|
-
WELCOME: 'welcome',
|
|
11
|
-
HEARTBEAT: 'heartbeat',
|
|
12
|
-
NOTIFICATION: 'notification',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const TYPES_RECEIVED = [
|
|
16
|
-
TYPES.HEARTBEAT,
|
|
17
|
-
TYPES.REQUEST,
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
const ajv = new Ajv({
|
|
21
|
-
allErrors: true,
|
|
22
|
-
removeAdditional: 'all',
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
addFormats(ajv)
|
|
26
|
-
|
|
27
|
-
const SCHEMA_BASE = {
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: {
|
|
30
|
-
id: {
|
|
31
|
-
type: 'string',
|
|
32
|
-
format: 'uuid',
|
|
33
|
-
},
|
|
34
|
-
clientId: {
|
|
35
|
-
type: 'string',
|
|
36
|
-
format: 'uuid',
|
|
37
|
-
},
|
|
38
|
-
type: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
enum: TYPES_RECEIVED,
|
|
41
|
-
},
|
|
42
|
-
timestamp: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
format: 'date-time',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
required: [
|
|
48
|
-
'id',
|
|
49
|
-
'clientId',
|
|
50
|
-
'type',
|
|
51
|
-
'timestamp',
|
|
52
|
-
],
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const validateHeartbeat = ajv.compile({
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {
|
|
58
|
-
...SCHEMA_BASE.properties,
|
|
59
|
-
type: {
|
|
60
|
-
type: 'string',
|
|
61
|
-
const: TYPES.HEARTBEAT,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
required: SCHEMA_BASE.required,
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const validateRequest = ajv.compile({
|
|
68
|
-
type: 'object',
|
|
69
|
-
properties: {
|
|
70
|
-
...SCHEMA_BASE.properties,
|
|
71
|
-
type: {
|
|
72
|
-
type: 'string',
|
|
73
|
-
const: TYPES.REQUEST,
|
|
74
|
-
},
|
|
75
|
-
method: {
|
|
76
|
-
type: 'string',
|
|
77
|
-
enum: [
|
|
78
|
-
'HEAD',
|
|
79
|
-
'GET',
|
|
80
|
-
'PUT',
|
|
81
|
-
'POST',
|
|
82
|
-
'PATCH',
|
|
83
|
-
'DELETE',
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
route: {
|
|
87
|
-
type: 'string',
|
|
88
|
-
format: 'uri-reference',
|
|
89
|
-
},
|
|
90
|
-
headers: {
|
|
91
|
-
type: 'object',
|
|
92
|
-
},
|
|
93
|
-
query: {
|
|
94
|
-
type: 'object',
|
|
95
|
-
},
|
|
96
|
-
body: {
|
|
97
|
-
type: [
|
|
98
|
-
'boolean',
|
|
99
|
-
'number',
|
|
100
|
-
'string',
|
|
101
|
-
'object',
|
|
102
|
-
'array',
|
|
103
|
-
'null',
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
required: [
|
|
108
|
-
...SCHEMA_BASE.required,
|
|
109
|
-
'method',
|
|
110
|
-
'route',
|
|
111
|
-
'headers',
|
|
112
|
-
'query',
|
|
113
|
-
'body',
|
|
114
|
-
],
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const TYPE_VALIDATORS = {
|
|
118
|
-
[TYPES.HEARTBEAT]: validateHeartbeat,
|
|
119
|
-
[TYPES.REQUEST]: validateRequest,
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function createMessage (clientId, type, opts = {}) {
|
|
123
|
-
const timestamp = new Date().toISOString()
|
|
124
|
-
|
|
125
|
-
const base = {
|
|
126
|
-
id: opts.id ?? crypto.randomUUID(),
|
|
127
|
-
clientId,
|
|
128
|
-
type,
|
|
129
|
-
timestamp,
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
...opts,
|
|
134
|
-
...base,
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function validateMessage (message) {
|
|
139
|
-
const validate = TYPE_VALIDATORS[message.type]
|
|
140
|
-
|
|
141
|
-
if (message.type === undefined) {
|
|
142
|
-
throw new UnprocessableContentError([
|
|
143
|
-
{
|
|
144
|
-
path: '',
|
|
145
|
-
message: `must have required property 'type'`,
|
|
146
|
-
},
|
|
147
|
-
])
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!TYPES_RECEIVED.includes(message.type)) {
|
|
151
|
-
throw new UnprocessableContentError([
|
|
152
|
-
{
|
|
153
|
-
path: 'type',
|
|
154
|
-
message: `must be one of: ${TYPES_RECEIVED}`,
|
|
155
|
-
},
|
|
156
|
-
])
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!validate(message)) {
|
|
160
|
-
const errors = validate.errors.map(item => formatError('', item))
|
|
161
|
-
|
|
162
|
-
throw new UnprocessableContentError(errors)
|
|
163
|
-
}
|
|
164
|
-
}
|
package/src/meta.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export function range (count, startIndex = 0) {
|
|
2
|
-
return new Array(count).fill(0).map((_, index) => index + startIndex)
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function traverse (obj, onKey, includeRoot = false) {
|
|
6
|
-
const path = ['']
|
|
7
|
-
|
|
8
|
-
const fn = target => {
|
|
9
|
-
Object.entries(target).forEach(([k, v]) => {
|
|
10
|
-
path[path.length - 1] = k
|
|
11
|
-
|
|
12
|
-
const dateType = v instanceof Date
|
|
13
|
-
const clip = onKey([...path], v) === false
|
|
14
|
-
const updatedVal = getValueByPath(obj, path)
|
|
15
|
-
const nonNullObj = updatedVal !== null && typeof updatedVal === 'object'
|
|
16
|
-
|
|
17
|
-
if (!clip && !dateType && nonNullObj) {
|
|
18
|
-
path.push('')
|
|
19
|
-
fn(updatedVal)
|
|
20
|
-
path.pop()
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (includeRoot) {
|
|
26
|
-
onKey([], obj)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
fn(obj)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function map (obj, onKey) {
|
|
33
|
-
const result = Array.isArray(obj) ? [] : {}
|
|
34
|
-
|
|
35
|
-
traverse(obj, (keyPath, value) => {
|
|
36
|
-
const dateType = value instanceof Date
|
|
37
|
-
|
|
38
|
-
if (!dateType && value !== null && typeof value === 'object') {
|
|
39
|
-
setValueByPath(result, keyPath, Array.isArray(value) ? [] : {})
|
|
40
|
-
} else {
|
|
41
|
-
setValueByPath(result, keyPath, onKey(keyPath, value))
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
return result
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function deepCopy (obj) {
|
|
49
|
-
return map(obj, (_, value) => value)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function setValueByPath (obj, keyPath, value) {
|
|
53
|
-
keyPath.reduce((subObj, key, index) => {
|
|
54
|
-
if (index === keyPath.length - 1) {
|
|
55
|
-
subObj[key] = value
|
|
56
|
-
} else {
|
|
57
|
-
return subObj[key]
|
|
58
|
-
}
|
|
59
|
-
}, obj)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function getValueByPath (obj, keyPath) {
|
|
63
|
-
return keyPath.reduce((obj, key) =>
|
|
64
|
-
(typeof obj !== 'undefined' ? obj[key] : undefined), obj)
|
|
65
|
-
}
|
package/src/middleware.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import Ajv from 'ajv'
|
|
2
|
-
import addFormats from 'ajv-formats'
|
|
3
|
-
|
|
4
|
-
import { formatError } from './utils'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
BadRequestError,
|
|
8
|
-
UnsupportedMediaTypeError,
|
|
9
|
-
UnprocessableContentError,
|
|
10
|
-
} from './errors'
|
|
11
|
-
|
|
12
|
-
let _schemasCompiled = false
|
|
13
|
-
let _customFormats = null
|
|
14
|
-
|
|
15
|
-
async function parseBody (req) {
|
|
16
|
-
try {
|
|
17
|
-
const result = await req.json()
|
|
18
|
-
|
|
19
|
-
return result
|
|
20
|
-
} catch {
|
|
21
|
-
throw new BadRequestError('Invalid JSON')
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function buildFormatterSchema (schema) {
|
|
26
|
-
const properties = Object
|
|
27
|
-
.entries(schema)
|
|
28
|
-
.map(([key, config]) => [
|
|
29
|
-
key,
|
|
30
|
-
{
|
|
31
|
-
type: 'string',
|
|
32
|
-
[config.type]: config.value,
|
|
33
|
-
},
|
|
34
|
-
])
|
|
35
|
-
.reduce((accum, [key, value]) => ({
|
|
36
|
-
...accum,
|
|
37
|
-
[key]: value,
|
|
38
|
-
}), {})
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
type: 'object',
|
|
42
|
-
properties,
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function compileSchemas (schemas) {
|
|
47
|
-
return Object
|
|
48
|
-
.entries(schemas)
|
|
49
|
-
.reduce((accum, [key, schema]) => {
|
|
50
|
-
const formattedSchema = key !== 'body'
|
|
51
|
-
? buildFormatterSchema(schema)
|
|
52
|
-
: schema
|
|
53
|
-
|
|
54
|
-
return [
|
|
55
|
-
...accum,
|
|
56
|
-
[key, formattedSchema],
|
|
57
|
-
]
|
|
58
|
-
}, [])
|
|
59
|
-
.map(([key, schema]) => {
|
|
60
|
-
const ajv = new Ajv({
|
|
61
|
-
allErrors: true,
|
|
62
|
-
removeAdditional: 'all',
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
addFormats(ajv)
|
|
66
|
-
|
|
67
|
-
Object
|
|
68
|
-
.entries(_customFormats ?? [])
|
|
69
|
-
.forEach(([k, v]) => ajv.addFormat(k, v))
|
|
70
|
-
|
|
71
|
-
const validator = ajv.compile(schema)
|
|
72
|
-
|
|
73
|
-
return [key, validator]
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function parseJsonBody () {
|
|
78
|
-
return async (req, res, next) => {
|
|
79
|
-
const contentType = req.headers.get('content-type')
|
|
80
|
-
|
|
81
|
-
if (!contentType) {
|
|
82
|
-
return next(res)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!contentType.startsWith('application/json')) {
|
|
86
|
-
throw new UnsupportedMediaTypeError('content-type')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const body = await parseBody(req)
|
|
90
|
-
|
|
91
|
-
return next(body)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function setValidationFormats (formats) {
|
|
96
|
-
if (_customFormats) {
|
|
97
|
-
console.warn('setValidationFormats() - already initialized')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (_schemasCompiled) {
|
|
101
|
-
console.warn('setValidationFormats() - called after compilation')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_customFormats = formats
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* only for testing purposes */
|
|
108
|
-
|
|
109
|
-
export function resetValidationFormatsState () {
|
|
110
|
-
_customFormats = null
|
|
111
|
-
_schemasCompiled = false
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function validateSchemas (schemas) {
|
|
115
|
-
const entries = compileSchemas(schemas)
|
|
116
|
-
|
|
117
|
-
_schemasCompiled = true
|
|
118
|
-
|
|
119
|
-
return (req, res, next) => {
|
|
120
|
-
const errors = entries.reduce((accum, [key, validator]) => {
|
|
121
|
-
const data = key === 'body' ? res : req[key]
|
|
122
|
-
const valid = validator(data)
|
|
123
|
-
|
|
124
|
-
return !valid
|
|
125
|
-
? [
|
|
126
|
-
...accum,
|
|
127
|
-
...validator.errors.map(item => formatError(key, item)),
|
|
128
|
-
]
|
|
129
|
-
: accum
|
|
130
|
-
}, [])
|
|
131
|
-
|
|
132
|
-
if (errors.length > 0) {
|
|
133
|
-
throw new UnprocessableContentError(errors)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return next(res)
|
|
137
|
-
}
|
|
138
|
-
}
|
package/src/utils.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export function toSegments (pathString) {
|
|
2
|
-
const [pathname] = String(pathString).split('?')
|
|
3
|
-
const segments = pathname.split('/')
|
|
4
|
-
|
|
5
|
-
if (pathname.startsWith('/')) {
|
|
6
|
-
segments.shift()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
if (pathname.endsWith('/')) {
|
|
10
|
-
segments.pop()
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return segments
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function formatError (prefix, input) {
|
|
17
|
-
const fixedPath = input.instancePath || '/'
|
|
18
|
-
const suffixPath = fixedPath.replace(/\//g, '.').replace('.', '')
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
path: [prefix, suffixPath].filter(item => item).join('.'),
|
|
22
|
-
message: input.message,
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function executeMiddlewareChain (req, chain) {
|
|
27
|
-
if (!chain.length) {
|
|
28
|
-
throw new RangeError('Middleware chain is empty')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const executeMiddleware = async (index, res) => {
|
|
32
|
-
const currentMiddleware = chain[index]
|
|
33
|
-
const isLastMiddleware = index === chain.length - 1
|
|
34
|
-
|
|
35
|
-
const next = !isLastMiddleware ?
|
|
36
|
-
(data) => executeMiddleware(index + 1, data)
|
|
37
|
-
: null
|
|
38
|
-
|
|
39
|
-
const result = await currentMiddleware(req, res, next)
|
|
40
|
-
|
|
41
|
-
if (result instanceof Response) {
|
|
42
|
-
return result
|
|
43
|
-
} else {
|
|
44
|
-
throw new TypeError('Handler does not return a Response object')
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return executeMiddleware(0, null)
|
|
49
|
-
}
|