hono 2.2.3 → 2.2.5
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/dist/cjs/compose.js +29 -7
- package/dist/cjs/context.js +1 -0
- package/dist/cjs/hono.js +1 -1
- package/dist/cjs/middleware/serve-static/bun.js +1 -1
- package/dist/cjs/middleware/validator/middleware.js +9 -1
- package/dist/cjs/middleware/validator/validator.js +91 -23
- package/dist/cjs/router/reg-exp-router/router.js +10 -10
- package/dist/cjs/router/trie-router/node.js +0 -1
- package/dist/cjs/utils/json.js +24 -13
- package/dist/compose.d.ts +2 -2
- package/dist/compose.js +29 -7
- package/dist/context.d.ts +2 -0
- package/dist/context.js +1 -0
- package/dist/hono.js +1 -1
- package/dist/middleware/serve-static/bun.js +1 -1
- package/dist/middleware/validator/middleware.d.ts +3 -3
- package/dist/middleware/validator/middleware.js +9 -1
- package/dist/middleware/validator/validator.d.ts +19 -1
- package/dist/middleware/validator/validator.js +87 -22
- package/dist/router/reg-exp-router/router.js +10 -10
- package/dist/router/trie-router/node.js +0 -1
- package/dist/utils/json.d.ts +7 -1
- package/dist/utils/json.js +24 -13
- package/package.json +1 -1
package/dist/cjs/compose.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.compose = void 0;
|
|
4
4
|
const context_1 = require("./context");
|
|
5
5
|
// Based on the code in the MIT licensed `koa-compose` package.
|
|
6
|
-
const compose = (middleware, onNotFound) => {
|
|
6
|
+
const compose = (middleware, onNotFound, onError) => {
|
|
7
7
|
const middlewareLength = middleware.length;
|
|
8
8
|
return (context, next) => {
|
|
9
9
|
let index = -1;
|
|
@@ -17,29 +17,51 @@ const compose = (middleware, onNotFound) => {
|
|
|
17
17
|
if (i === middlewareLength && next)
|
|
18
18
|
handler = next;
|
|
19
19
|
let res;
|
|
20
|
+
let isError = false;
|
|
20
21
|
if (!handler) {
|
|
21
22
|
if (context instanceof context_1.HonoContext && context.finalized === false && onNotFound) {
|
|
22
23
|
res = onNotFound(context);
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
else {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
try {
|
|
28
|
+
res = handler(context, () => {
|
|
29
|
+
const dispatchRes = dispatch(i + 1);
|
|
30
|
+
return dispatchRes instanceof Promise ? dispatchRes : Promise.resolve(dispatchRes);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err instanceof Error && context instanceof context_1.HonoContext && onError) {
|
|
35
|
+
context.error = err;
|
|
36
|
+
res = onError(err, context);
|
|
37
|
+
isError = true;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
30
43
|
}
|
|
31
44
|
if (!(res instanceof Promise)) {
|
|
32
|
-
if (res && context.finalized === false) {
|
|
45
|
+
if (res && (context.finalized === false || isError)) {
|
|
33
46
|
context.res = res;
|
|
34
47
|
}
|
|
35
48
|
return context;
|
|
36
49
|
}
|
|
37
50
|
else {
|
|
38
|
-
return res
|
|
51
|
+
return res
|
|
52
|
+
.then((res) => {
|
|
39
53
|
if (res && context.finalized === false) {
|
|
40
54
|
context.res = res;
|
|
41
55
|
}
|
|
42
56
|
return context;
|
|
57
|
+
})
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
if (err instanceof Error && context instanceof context_1.HonoContext && onError) {
|
|
60
|
+
context.error = err;
|
|
61
|
+
context.res = onError(err, context);
|
|
62
|
+
return context;
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
43
65
|
});
|
|
44
66
|
}
|
|
45
67
|
}
|
package/dist/cjs/context.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.HonoContext = void 0;
|
|
|
4
4
|
const cookie_1 = require("./utils/cookie");
|
|
5
5
|
class HonoContext {
|
|
6
6
|
constructor(req, env = undefined, executionCtx = undefined, notFoundHandler = () => new Response()) {
|
|
7
|
+
this.error = undefined;
|
|
7
8
|
this._status = 200;
|
|
8
9
|
this._pretty = false;
|
|
9
10
|
this._prettySpace = 2;
|
package/dist/cjs/hono.js
CHANGED
|
@@ -146,7 +146,7 @@ class Hono extends defineDynamicClass() {
|
|
|
146
146
|
})();
|
|
147
147
|
}
|
|
148
148
|
const handlers = result ? result.handlers : [this.notFoundHandler];
|
|
149
|
-
const composed = (0, compose_1.compose)(handlers, this.notFoundHandler);
|
|
149
|
+
const composed = (0, compose_1.compose)(handlers, this.notFoundHandler, this.errorHandler);
|
|
150
150
|
return (async () => {
|
|
151
151
|
try {
|
|
152
152
|
const tmp = composed(c);
|
|
@@ -12,7 +12,7 @@ const DEFAULT_DOCUMENT = 'index.html';
|
|
|
12
12
|
const serveStatic = (options = { root: '' }) => {
|
|
13
13
|
return async (c, next) => {
|
|
14
14
|
// Do nothing if Response is already set
|
|
15
|
-
if (c.
|
|
15
|
+
if (c.finalized) {
|
|
16
16
|
await next();
|
|
17
17
|
}
|
|
18
18
|
const url = new URL(c.req.url);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validatorMiddleware = void 0;
|
|
4
|
+
const http_status_1 = require("../../utils/http-status");
|
|
4
5
|
const validator_1 = require("./validator");
|
|
5
6
|
const validatorMiddleware = (validationFunction, options) => {
|
|
6
7
|
const v = new validator_1.Validator();
|
|
@@ -12,7 +13,14 @@ const validatorMiddleware = (validationFunction, options) => {
|
|
|
12
13
|
};
|
|
13
14
|
const validatorList = getValidatorList(validationFunction(v, c));
|
|
14
15
|
for (const [keys, validator] of validatorList) {
|
|
15
|
-
|
|
16
|
+
let result;
|
|
17
|
+
try {
|
|
18
|
+
result = await validator.validate(c.req);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
// Invalid JSON request
|
|
22
|
+
return c.text((0, http_status_1.getStatusText)(400), 400);
|
|
23
|
+
}
|
|
16
24
|
if (result.isValid) {
|
|
17
25
|
// Set data on request object
|
|
18
26
|
c.req.valid(keys, result.value);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VObject = exports.VBoolean = exports.VNumber = exports.VString = exports.VBase = exports.Validator = void 0;
|
|
3
|
+
exports.VBooleanArray = exports.VStringArray = exports.VNumberArray = exports.VObject = exports.VBoolean = exports.VNumber = exports.VString = exports.VBase = exports.Validator = void 0;
|
|
4
4
|
const json_1 = require("../../utils/json");
|
|
5
5
|
const rule_1 = require("./rule");
|
|
6
6
|
const sanitizer_1 = require("./sanitizer");
|
|
@@ -25,7 +25,7 @@ class VBase {
|
|
|
25
25
|
};
|
|
26
26
|
this.isRequired = () => {
|
|
27
27
|
return this.addRule((value) => {
|
|
28
|
-
if (value)
|
|
28
|
+
if (value !== undefined && value !== null && value !== '')
|
|
29
29
|
return true;
|
|
30
30
|
return false;
|
|
31
31
|
});
|
|
@@ -71,28 +71,38 @@ class VBase {
|
|
|
71
71
|
value = body[this.key];
|
|
72
72
|
}
|
|
73
73
|
if (this.target === 'json') {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
try {
|
|
75
|
+
const obj = (await req.json());
|
|
76
|
+
value = (0, json_1.JSONPath)(obj, this.key);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
throw new Error('Malformed JSON in request body');
|
|
80
|
+
}
|
|
76
81
|
}
|
|
77
82
|
result.value = value;
|
|
78
83
|
result.isValid = this.validateValue(value);
|
|
79
|
-
if (result.isValid
|
|
84
|
+
if (result.isValid === false) {
|
|
80
85
|
if (this._message) {
|
|
81
86
|
result.message = this._message;
|
|
82
87
|
}
|
|
83
88
|
else {
|
|
89
|
+
const valToStr = Array.isArray(value)
|
|
90
|
+
? `[${value
|
|
91
|
+
.map((val) => val === undefined ? 'undefined' : typeof val === 'string' ? `"${val}"` : val)
|
|
92
|
+
.join(', ')}]`
|
|
93
|
+
: value;
|
|
84
94
|
switch (this.target) {
|
|
85
95
|
case 'query':
|
|
86
|
-
result.message = `Invalid Value: the query parameter "${this.key}" is invalid - ${
|
|
96
|
+
result.message = `Invalid Value: the query parameter "${this.key}" is invalid - ${valToStr}`;
|
|
87
97
|
break;
|
|
88
98
|
case 'header':
|
|
89
|
-
result.message = `Invalid Value: the request header "${this.key}" is invalid - ${
|
|
99
|
+
result.message = `Invalid Value: the request header "${this.key}" is invalid - ${valToStr}`;
|
|
90
100
|
break;
|
|
91
101
|
case 'body':
|
|
92
|
-
result.message = `Invalid Value: the request body "${this.key}" is invalid - ${
|
|
102
|
+
result.message = `Invalid Value: the request body "${this.key}" is invalid - ${valToStr}`;
|
|
93
103
|
break;
|
|
94
104
|
case 'json':
|
|
95
|
-
result.message = `Invalid Value: the JSON body "${this.key}" is invalid - ${
|
|
105
|
+
result.message = `Invalid Value: the JSON body "${this.key}" is invalid - ${valToStr}`;
|
|
96
106
|
break;
|
|
97
107
|
}
|
|
98
108
|
}
|
|
@@ -101,31 +111,59 @@ class VBase {
|
|
|
101
111
|
};
|
|
102
112
|
this.validateValue = (value) => {
|
|
103
113
|
// Check type
|
|
104
|
-
if (
|
|
105
|
-
if (
|
|
106
|
-
// Do nothing.
|
|
107
|
-
// The value is allowed to be `undefined` if it is `optional`
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
114
|
+
if (this.isArray) {
|
|
115
|
+
if (!Array.isArray(value)) {
|
|
110
116
|
return false;
|
|
111
117
|
}
|
|
118
|
+
for (const val of value) {
|
|
119
|
+
if (typeof val !== this.type) {
|
|
120
|
+
// Value is of wrong type here
|
|
121
|
+
// If not optional, or optional and not undefined, return false
|
|
122
|
+
if (!this._optional || typeof val !== 'undefined')
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Sanitize
|
|
127
|
+
for (const sanitizer of this.sanitizers) {
|
|
128
|
+
value = value.map((innerVal) => sanitizer(innerVal));
|
|
129
|
+
}
|
|
130
|
+
for (const rule of this.rules) {
|
|
131
|
+
for (const val of value) {
|
|
132
|
+
if (!rule(val)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
112
138
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
else {
|
|
140
|
+
if (typeof value !== this.type) {
|
|
141
|
+
if (this._optional && typeof value === 'undefined') {
|
|
142
|
+
// Do nothing.
|
|
143
|
+
// The value is allowed to be `undefined` if it is `optional`
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Sanitize
|
|
150
|
+
for (const sanitizer of this.sanitizers) {
|
|
151
|
+
value = sanitizer(value);
|
|
152
|
+
}
|
|
153
|
+
for (const rule of this.rules) {
|
|
154
|
+
if (!rule(value)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
120
157
|
}
|
|
158
|
+
return true;
|
|
121
159
|
}
|
|
122
|
-
return true;
|
|
123
160
|
};
|
|
124
161
|
this.target = options.target;
|
|
125
162
|
this.key = options.key;
|
|
126
163
|
this.type = options.type || 'string';
|
|
127
164
|
this.rules = [];
|
|
128
165
|
this.sanitizers = [];
|
|
166
|
+
this.isArray = options.isArray || false;
|
|
129
167
|
this._optional = false;
|
|
130
168
|
}
|
|
131
169
|
message(value) {
|
|
@@ -137,6 +175,9 @@ exports.VBase = VBase;
|
|
|
137
175
|
class VString extends VBase {
|
|
138
176
|
constructor(options) {
|
|
139
177
|
super(options);
|
|
178
|
+
this.asArray = () => {
|
|
179
|
+
return new VStringArray(this);
|
|
180
|
+
};
|
|
140
181
|
this.isEmpty = (options = { ignore_whitespace: false }) => {
|
|
141
182
|
return this.addRule((value) => rule_1.rule.isEmpty(value, options));
|
|
142
183
|
};
|
|
@@ -171,6 +212,9 @@ exports.VString = VString;
|
|
|
171
212
|
class VNumber extends VBase {
|
|
172
213
|
constructor(options) {
|
|
173
214
|
super(options);
|
|
215
|
+
this.asArray = () => {
|
|
216
|
+
return new VNumberArray(this);
|
|
217
|
+
};
|
|
174
218
|
this.isGte = (min) => {
|
|
175
219
|
return this.addRule((value) => rule_1.rule.isGte(value, min));
|
|
176
220
|
};
|
|
@@ -184,6 +228,9 @@ exports.VNumber = VNumber;
|
|
|
184
228
|
class VBoolean extends VBase {
|
|
185
229
|
constructor(options) {
|
|
186
230
|
super(options);
|
|
231
|
+
this.asArray = () => {
|
|
232
|
+
return new VBooleanArray(this);
|
|
233
|
+
};
|
|
187
234
|
this.isTrue = () => {
|
|
188
235
|
return this.addRule((value) => rule_1.rule.isTrue(value));
|
|
189
236
|
};
|
|
@@ -201,3 +248,24 @@ class VObject extends VBase {
|
|
|
201
248
|
}
|
|
202
249
|
}
|
|
203
250
|
exports.VObject = VObject;
|
|
251
|
+
class VNumberArray extends VNumber {
|
|
252
|
+
constructor(options) {
|
|
253
|
+
super(options);
|
|
254
|
+
this.isArray = true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.VNumberArray = VNumberArray;
|
|
258
|
+
class VStringArray extends VString {
|
|
259
|
+
constructor(options) {
|
|
260
|
+
super(options);
|
|
261
|
+
this.isArray = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
exports.VStringArray = VStringArray;
|
|
265
|
+
class VBooleanArray extends VBoolean {
|
|
266
|
+
constructor(options) {
|
|
267
|
+
super(options);
|
|
268
|
+
this.isArray = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.VBooleanArray = VBooleanArray;
|
|
@@ -60,7 +60,7 @@ class RegExpRouter {
|
|
|
60
60
|
this.routes = { [router_1.METHOD_NAME_ALL]: {} };
|
|
61
61
|
}
|
|
62
62
|
add(method, path, handler) {
|
|
63
|
-
var _a
|
|
63
|
+
var _a;
|
|
64
64
|
const { middleware, routes } = this;
|
|
65
65
|
if (!middleware || !routes) {
|
|
66
66
|
throw new Error('Can not add a route since the matcher is already built.');
|
|
@@ -91,17 +91,17 @@ class RegExpRouter {
|
|
|
91
91
|
for (let i = 0, len = paths.length; i < len; i++) {
|
|
92
92
|
const path = paths[i];
|
|
93
93
|
routes[method] || (routes[method] = {});
|
|
94
|
-
(_b = routes[method])[path] || (_b[path] = [
|
|
95
|
-
...(routes[router_1.METHOD_NAME_ALL][path] ||
|
|
96
|
-
findMiddleware(middleware[method], path) ||
|
|
97
|
-
findMiddleware(middleware[router_1.METHOD_NAME_ALL], path) ||
|
|
98
|
-
[]),
|
|
99
|
-
]);
|
|
100
94
|
Object.keys(routes).forEach((m) => {
|
|
101
|
-
;
|
|
102
|
-
(method === router_1.METHOD_NAME_ALL || method === m)
|
|
103
|
-
routes[m][path]
|
|
95
|
+
var _a;
|
|
96
|
+
if (method === router_1.METHOD_NAME_ALL || method === m) {
|
|
97
|
+
(_a = routes[m])[path] || (_a[path] = [
|
|
98
|
+
...(routes[router_1.METHOD_NAME_ALL][path] ||
|
|
99
|
+
findMiddleware(middleware[method], path) ||
|
|
100
|
+
findMiddleware(middleware[router_1.METHOD_NAME_ALL], path) ||
|
|
101
|
+
[]),
|
|
102
|
+
]);
|
|
104
103
|
routes[m][path].push(handler);
|
|
104
|
+
}
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
}
|
package/dist/cjs/utils/json.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.JSONPath = void 0;
|
|
4
|
-
const
|
|
5
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
-
let val = data;
|
|
7
|
-
const parts = path.split('.');
|
|
4
|
+
const JSONPathInternal = (data, parts) => {
|
|
8
5
|
const length = parts.length;
|
|
9
|
-
for (let i = 0; i < length &&
|
|
6
|
+
for (let i = 0; i < length && data !== undefined; i++) {
|
|
10
7
|
const p = parts[i];
|
|
11
|
-
if (p
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
if (p === '') {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (typeof data !== 'object' || data === null) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
if (p === '*') {
|
|
15
|
+
const restParts = parts.slice(i + 1);
|
|
16
|
+
const values = Object.values(data).map((v) => JSONPathInternal(v, restParts));
|
|
17
|
+
return restParts.indexOf('*') === -1 ? values : values.flat();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
data = data[p]; // `data` may be an array, but accessing it as an object yields the same result.
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
|
-
return
|
|
23
|
+
return data;
|
|
24
|
+
};
|
|
25
|
+
const JSONPath = (data, path) => {
|
|
26
|
+
try {
|
|
27
|
+
return JSONPathInternal(data, path.replace(/\[(.*?)\]/g, '.$1').split(/\./));
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
21
32
|
};
|
|
22
33
|
exports.JSONPath = JSONPath;
|
package/dist/compose.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Environment, NotFoundHandler } from './hono';
|
|
1
|
+
import type { Environment, NotFoundHandler, ErrorHandler } from './hono';
|
|
2
2
|
interface ComposeContext {
|
|
3
3
|
finalized: boolean;
|
|
4
4
|
res: any;
|
|
5
5
|
}
|
|
6
|
-
export declare const compose: <C extends ComposeContext, E extends Partial<Environment> = Environment>(middleware: Function[], onNotFound?: NotFoundHandler<E> | undefined) => (context: C, next?: Function) => C | Promise<C>;
|
|
6
|
+
export declare const compose: <C extends ComposeContext, E extends Partial<Environment> = Environment>(middleware: Function[], onNotFound?: NotFoundHandler<E> | undefined, onError?: ErrorHandler<E> | undefined) => (context: C, next?: Function) => C | Promise<C>;
|
|
7
7
|
export {};
|
package/dist/compose.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HonoContext } from './context';
|
|
2
2
|
// Based on the code in the MIT licensed `koa-compose` package.
|
|
3
|
-
export const compose = (middleware, onNotFound) => {
|
|
3
|
+
export const compose = (middleware, onNotFound, onError) => {
|
|
4
4
|
const middlewareLength = middleware.length;
|
|
5
5
|
return (context, next) => {
|
|
6
6
|
let index = -1;
|
|
@@ -14,29 +14,51 @@ export const compose = (middleware, onNotFound) => {
|
|
|
14
14
|
if (i === middlewareLength && next)
|
|
15
15
|
handler = next;
|
|
16
16
|
let res;
|
|
17
|
+
let isError = false;
|
|
17
18
|
if (!handler) {
|
|
18
19
|
if (context instanceof HonoContext && context.finalized === false && onNotFound) {
|
|
19
20
|
res = onNotFound(context);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
else {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
try {
|
|
25
|
+
res = handler(context, () => {
|
|
26
|
+
const dispatchRes = dispatch(i + 1);
|
|
27
|
+
return dispatchRes instanceof Promise ? dispatchRes : Promise.resolve(dispatchRes);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof Error && context instanceof HonoContext && onError) {
|
|
32
|
+
context.error = err;
|
|
33
|
+
res = onError(err, context);
|
|
34
|
+
isError = true;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
}
|
|
28
41
|
if (!(res instanceof Promise)) {
|
|
29
|
-
if (res && context.finalized === false) {
|
|
42
|
+
if (res && (context.finalized === false || isError)) {
|
|
30
43
|
context.res = res;
|
|
31
44
|
}
|
|
32
45
|
return context;
|
|
33
46
|
}
|
|
34
47
|
else {
|
|
35
|
-
return res
|
|
48
|
+
return res
|
|
49
|
+
.then((res) => {
|
|
36
50
|
if (res && context.finalized === false) {
|
|
37
51
|
context.res = res;
|
|
38
52
|
}
|
|
39
53
|
return context;
|
|
54
|
+
})
|
|
55
|
+
.catch((err) => {
|
|
56
|
+
if (err instanceof Error && context instanceof HonoContext && onError) {
|
|
57
|
+
context.error = err;
|
|
58
|
+
context.res = onError(err, context);
|
|
59
|
+
return context;
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
40
62
|
});
|
|
41
63
|
}
|
|
42
64
|
}
|
package/dist/context.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface Context<RequestParamKeyType extends string = string, E extends
|
|
|
10
10
|
event: FetchEvent;
|
|
11
11
|
executionCtx: ExecutionContext;
|
|
12
12
|
finalized: boolean;
|
|
13
|
+
error: Error | undefined;
|
|
13
14
|
get res(): Response;
|
|
14
15
|
set res(_res: Response);
|
|
15
16
|
header: (name: string, value: string, options?: {
|
|
@@ -40,6 +41,7 @@ export declare class HonoContext<RequestParamKeyType extends string = string, E
|
|
|
40
41
|
req: Request<RequestParamKeyType, D>;
|
|
41
42
|
env: E['Bindings'];
|
|
42
43
|
finalized: boolean;
|
|
44
|
+
error: Error | undefined;
|
|
43
45
|
_status: StatusCode;
|
|
44
46
|
private _executionCtx;
|
|
45
47
|
private _pretty;
|
package/dist/context.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { serialize } from './utils/cookie';
|
|
2
2
|
export class HonoContext {
|
|
3
3
|
constructor(req, env = undefined, executionCtx = undefined, notFoundHandler = () => new Response()) {
|
|
4
|
+
this.error = undefined;
|
|
4
5
|
this._status = 200;
|
|
5
6
|
this._pretty = false;
|
|
6
7
|
this._prettySpace = 2;
|
package/dist/hono.js
CHANGED
|
@@ -143,7 +143,7 @@ export class Hono extends defineDynamicClass() {
|
|
|
143
143
|
})();
|
|
144
144
|
}
|
|
145
145
|
const handlers = result ? result.handlers : [this.notFoundHandler];
|
|
146
|
-
const composed = compose(handlers, this.notFoundHandler);
|
|
146
|
+
const composed = compose(handlers, this.notFoundHandler, this.errorHandler);
|
|
147
147
|
return (async () => {
|
|
148
148
|
try {
|
|
149
149
|
const tmp = composed(c);
|
|
@@ -9,7 +9,7 @@ const DEFAULT_DOCUMENT = 'index.html';
|
|
|
9
9
|
export const serveStatic = (options = { root: '' }) => {
|
|
10
10
|
return async (c, next) => {
|
|
11
11
|
// Do nothing if Response is already set
|
|
12
|
-
if (c.
|
|
12
|
+
if (c.finalized) {
|
|
13
13
|
await next();
|
|
14
14
|
}
|
|
15
15
|
const url = new URL(c.req.url);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Context } from '../../context';
|
|
2
2
|
import type { Environment, Next, ValidatedData } from '../../hono';
|
|
3
3
|
import { Validator } from './validator';
|
|
4
|
-
import type { VString, VNumber, VBoolean, VObject, ValidateResult } from './validator';
|
|
4
|
+
import type { VString, VNumber, VBoolean, VObject, VNumberArray, VStringArray, VBooleanArray, ValidateResult } from './validator';
|
|
5
5
|
declare type Schema = {
|
|
6
|
-
[key: string]: VString | VNumber | VBoolean | VObject | Schema;
|
|
6
|
+
[key: string]: VString | VNumber | VBoolean | VObject | VStringArray | VNumberArray | VBooleanArray | Schema;
|
|
7
7
|
};
|
|
8
8
|
declare type SchemaToProp<T> = {
|
|
9
|
-
[K in keyof T]: T[K] extends VNumber ? number : T[K] extends VBoolean ? boolean : T[K] extends VString ? string : T[K] extends VObject ? object : T[K] extends Schema ? SchemaToProp<T[K]> : never;
|
|
9
|
+
[K in keyof T]: T[K] extends VNumberArray ? number[] : T[K] extends VBooleanArray ? boolean[] : T[K] extends VStringArray ? string[] : T[K] extends VNumber ? number : T[K] extends VBoolean ? boolean : T[K] extends VString ? string : T[K] extends VObject ? object : T[K] extends Schema ? SchemaToProp<T[K]> : never;
|
|
10
10
|
};
|
|
11
11
|
declare type ResultSet = {
|
|
12
12
|
hasError: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getStatusText } from '../../utils/http-status';
|
|
1
2
|
import { VBase, Validator } from './validator';
|
|
2
3
|
export const validatorMiddleware = (validationFunction, options) => {
|
|
3
4
|
const v = new Validator();
|
|
@@ -9,7 +10,14 @@ export const validatorMiddleware = (validationFunction, options) => {
|
|
|
9
10
|
};
|
|
10
11
|
const validatorList = getValidatorList(validationFunction(v, c));
|
|
11
12
|
for (const [keys, validator] of validatorList) {
|
|
12
|
-
|
|
13
|
+
let result;
|
|
14
|
+
try {
|
|
15
|
+
result = await validator.validate(c.req);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
// Invalid JSON request
|
|
19
|
+
return c.text(getStatusText(400), 400);
|
|
20
|
+
}
|
|
13
21
|
if (result.isValid) {
|
|
14
22
|
// Set data on request object
|
|
15
23
|
c.req.valid(keys, result.value);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { JSONObject, JSONPrimitive, JSONArray } from '../../utils/json';
|
|
1
2
|
declare type Target = 'query' | 'header' | 'body' | 'json';
|
|
2
|
-
declare type Type =
|
|
3
|
+
declare type Type = JSONPrimitive | JSONObject | JSONArray | File;
|
|
3
4
|
declare type Rule = (value: Type) => boolean;
|
|
4
5
|
declare type Sanitizer = (value: Type) => Type;
|
|
5
6
|
export declare class Validator {
|
|
@@ -19,6 +20,7 @@ declare type VOptions = {
|
|
|
19
20
|
target: Target;
|
|
20
21
|
key: string;
|
|
21
22
|
type?: 'string' | 'number' | 'boolean' | 'object';
|
|
23
|
+
isArray?: boolean;
|
|
22
24
|
};
|
|
23
25
|
export declare abstract class VBase {
|
|
24
26
|
type: 'string' | 'number' | 'boolean' | 'object';
|
|
@@ -26,6 +28,7 @@ export declare abstract class VBase {
|
|
|
26
28
|
key: string;
|
|
27
29
|
rules: Rule[];
|
|
28
30
|
sanitizers: Sanitizer[];
|
|
31
|
+
isArray: boolean;
|
|
29
32
|
private _message;
|
|
30
33
|
private _optional;
|
|
31
34
|
constructor(options: VOptions);
|
|
@@ -43,6 +46,7 @@ export declare abstract class VBase {
|
|
|
43
46
|
}
|
|
44
47
|
export declare class VString extends VBase {
|
|
45
48
|
constructor(options: VOptions);
|
|
49
|
+
asArray: () => VStringArray;
|
|
46
50
|
isEmpty: (options?: {
|
|
47
51
|
ignore_whitespace: boolean;
|
|
48
52
|
}) => this;
|
|
@@ -62,15 +66,29 @@ export declare class VString extends VBase {
|
|
|
62
66
|
}
|
|
63
67
|
export declare class VNumber extends VBase {
|
|
64
68
|
constructor(options: VOptions);
|
|
69
|
+
asArray: () => VNumberArray;
|
|
65
70
|
isGte: (min: number) => this;
|
|
66
71
|
isLte: (min: number) => this;
|
|
67
72
|
}
|
|
68
73
|
export declare class VBoolean extends VBase {
|
|
69
74
|
constructor(options: VOptions);
|
|
75
|
+
asArray: () => VBooleanArray;
|
|
70
76
|
isTrue: () => this;
|
|
71
77
|
isFalse: () => this;
|
|
72
78
|
}
|
|
73
79
|
export declare class VObject extends VBase {
|
|
74
80
|
constructor(options: VOptions);
|
|
75
81
|
}
|
|
82
|
+
export declare class VNumberArray extends VNumber {
|
|
83
|
+
isArray: true;
|
|
84
|
+
constructor(options: VOptions);
|
|
85
|
+
}
|
|
86
|
+
export declare class VStringArray extends VString {
|
|
87
|
+
isArray: true;
|
|
88
|
+
constructor(options: VOptions);
|
|
89
|
+
}
|
|
90
|
+
export declare class VBooleanArray extends VBoolean {
|
|
91
|
+
isArray: true;
|
|
92
|
+
constructor(options: VOptions);
|
|
93
|
+
}
|
|
76
94
|
export {};
|
|
@@ -21,7 +21,7 @@ export class VBase {
|
|
|
21
21
|
};
|
|
22
22
|
this.isRequired = () => {
|
|
23
23
|
return this.addRule((value) => {
|
|
24
|
-
if (value)
|
|
24
|
+
if (value !== undefined && value !== null && value !== '')
|
|
25
25
|
return true;
|
|
26
26
|
return false;
|
|
27
27
|
});
|
|
@@ -67,28 +67,38 @@ export class VBase {
|
|
|
67
67
|
value = body[this.key];
|
|
68
68
|
}
|
|
69
69
|
if (this.target === 'json') {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
try {
|
|
71
|
+
const obj = (await req.json());
|
|
72
|
+
value = JSONPath(obj, this.key);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
throw new Error('Malformed JSON in request body');
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
78
|
result.value = value;
|
|
74
79
|
result.isValid = this.validateValue(value);
|
|
75
|
-
if (result.isValid
|
|
80
|
+
if (result.isValid === false) {
|
|
76
81
|
if (this._message) {
|
|
77
82
|
result.message = this._message;
|
|
78
83
|
}
|
|
79
84
|
else {
|
|
85
|
+
const valToStr = Array.isArray(value)
|
|
86
|
+
? `[${value
|
|
87
|
+
.map((val) => val === undefined ? 'undefined' : typeof val === 'string' ? `"${val}"` : val)
|
|
88
|
+
.join(', ')}]`
|
|
89
|
+
: value;
|
|
80
90
|
switch (this.target) {
|
|
81
91
|
case 'query':
|
|
82
|
-
result.message = `Invalid Value: the query parameter "${this.key}" is invalid - ${
|
|
92
|
+
result.message = `Invalid Value: the query parameter "${this.key}" is invalid - ${valToStr}`;
|
|
83
93
|
break;
|
|
84
94
|
case 'header':
|
|
85
|
-
result.message = `Invalid Value: the request header "${this.key}" is invalid - ${
|
|
95
|
+
result.message = `Invalid Value: the request header "${this.key}" is invalid - ${valToStr}`;
|
|
86
96
|
break;
|
|
87
97
|
case 'body':
|
|
88
|
-
result.message = `Invalid Value: the request body "${this.key}" is invalid - ${
|
|
98
|
+
result.message = `Invalid Value: the request body "${this.key}" is invalid - ${valToStr}`;
|
|
89
99
|
break;
|
|
90
100
|
case 'json':
|
|
91
|
-
result.message = `Invalid Value: the JSON body "${this.key}" is invalid - ${
|
|
101
|
+
result.message = `Invalid Value: the JSON body "${this.key}" is invalid - ${valToStr}`;
|
|
92
102
|
break;
|
|
93
103
|
}
|
|
94
104
|
}
|
|
@@ -97,31 +107,59 @@ export class VBase {
|
|
|
97
107
|
};
|
|
98
108
|
this.validateValue = (value) => {
|
|
99
109
|
// Check type
|
|
100
|
-
if (
|
|
101
|
-
if (
|
|
102
|
-
// Do nothing.
|
|
103
|
-
// The value is allowed to be `undefined` if it is `optional`
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
110
|
+
if (this.isArray) {
|
|
111
|
+
if (!Array.isArray(value)) {
|
|
106
112
|
return false;
|
|
107
113
|
}
|
|
114
|
+
for (const val of value) {
|
|
115
|
+
if (typeof val !== this.type) {
|
|
116
|
+
// Value is of wrong type here
|
|
117
|
+
// If not optional, or optional and not undefined, return false
|
|
118
|
+
if (!this._optional || typeof val !== 'undefined')
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Sanitize
|
|
123
|
+
for (const sanitizer of this.sanitizers) {
|
|
124
|
+
value = value.map((innerVal) => sanitizer(innerVal));
|
|
125
|
+
}
|
|
126
|
+
for (const rule of this.rules) {
|
|
127
|
+
for (const val of value) {
|
|
128
|
+
if (!rule(val)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
108
134
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
else {
|
|
136
|
+
if (typeof value !== this.type) {
|
|
137
|
+
if (this._optional && typeof value === 'undefined') {
|
|
138
|
+
// Do nothing.
|
|
139
|
+
// The value is allowed to be `undefined` if it is `optional`
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Sanitize
|
|
146
|
+
for (const sanitizer of this.sanitizers) {
|
|
147
|
+
value = sanitizer(value);
|
|
148
|
+
}
|
|
149
|
+
for (const rule of this.rules) {
|
|
150
|
+
if (!rule(value)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
116
153
|
}
|
|
154
|
+
return true;
|
|
117
155
|
}
|
|
118
|
-
return true;
|
|
119
156
|
};
|
|
120
157
|
this.target = options.target;
|
|
121
158
|
this.key = options.key;
|
|
122
159
|
this.type = options.type || 'string';
|
|
123
160
|
this.rules = [];
|
|
124
161
|
this.sanitizers = [];
|
|
162
|
+
this.isArray = options.isArray || false;
|
|
125
163
|
this._optional = false;
|
|
126
164
|
}
|
|
127
165
|
message(value) {
|
|
@@ -132,6 +170,9 @@ export class VBase {
|
|
|
132
170
|
export class VString extends VBase {
|
|
133
171
|
constructor(options) {
|
|
134
172
|
super(options);
|
|
173
|
+
this.asArray = () => {
|
|
174
|
+
return new VStringArray(this);
|
|
175
|
+
};
|
|
135
176
|
this.isEmpty = (options = { ignore_whitespace: false }) => {
|
|
136
177
|
return this.addRule((value) => rule.isEmpty(value, options));
|
|
137
178
|
};
|
|
@@ -165,6 +206,9 @@ export class VString extends VBase {
|
|
|
165
206
|
export class VNumber extends VBase {
|
|
166
207
|
constructor(options) {
|
|
167
208
|
super(options);
|
|
209
|
+
this.asArray = () => {
|
|
210
|
+
return new VNumberArray(this);
|
|
211
|
+
};
|
|
168
212
|
this.isGte = (min) => {
|
|
169
213
|
return this.addRule((value) => rule.isGte(value, min));
|
|
170
214
|
};
|
|
@@ -177,6 +221,9 @@ export class VNumber extends VBase {
|
|
|
177
221
|
export class VBoolean extends VBase {
|
|
178
222
|
constructor(options) {
|
|
179
223
|
super(options);
|
|
224
|
+
this.asArray = () => {
|
|
225
|
+
return new VBooleanArray(this);
|
|
226
|
+
};
|
|
180
227
|
this.isTrue = () => {
|
|
181
228
|
return this.addRule((value) => rule.isTrue(value));
|
|
182
229
|
};
|
|
@@ -192,3 +239,21 @@ export class VObject extends VBase {
|
|
|
192
239
|
this.type = 'object';
|
|
193
240
|
}
|
|
194
241
|
}
|
|
242
|
+
export class VNumberArray extends VNumber {
|
|
243
|
+
constructor(options) {
|
|
244
|
+
super(options);
|
|
245
|
+
this.isArray = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
export class VStringArray extends VString {
|
|
249
|
+
constructor(options) {
|
|
250
|
+
super(options);
|
|
251
|
+
this.isArray = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export class VBooleanArray extends VBoolean {
|
|
255
|
+
constructor(options) {
|
|
256
|
+
super(options);
|
|
257
|
+
this.isArray = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -57,7 +57,7 @@ export class RegExpRouter {
|
|
|
57
57
|
this.routes = { [METHOD_NAME_ALL]: {} };
|
|
58
58
|
}
|
|
59
59
|
add(method, path, handler) {
|
|
60
|
-
var _a
|
|
60
|
+
var _a;
|
|
61
61
|
const { middleware, routes } = this;
|
|
62
62
|
if (!middleware || !routes) {
|
|
63
63
|
throw new Error('Can not add a route since the matcher is already built.');
|
|
@@ -88,17 +88,17 @@ export class RegExpRouter {
|
|
|
88
88
|
for (let i = 0, len = paths.length; i < len; i++) {
|
|
89
89
|
const path = paths[i];
|
|
90
90
|
routes[method] || (routes[method] = {});
|
|
91
|
-
(_b = routes[method])[path] || (_b[path] = [
|
|
92
|
-
...(routes[METHOD_NAME_ALL][path] ||
|
|
93
|
-
findMiddleware(middleware[method], path) ||
|
|
94
|
-
findMiddleware(middleware[METHOD_NAME_ALL], path) ||
|
|
95
|
-
[]),
|
|
96
|
-
]);
|
|
97
91
|
Object.keys(routes).forEach((m) => {
|
|
98
|
-
;
|
|
99
|
-
(method === METHOD_NAME_ALL || method === m)
|
|
100
|
-
routes[m][path]
|
|
92
|
+
var _a;
|
|
93
|
+
if (method === METHOD_NAME_ALL || method === m) {
|
|
94
|
+
(_a = routes[m])[path] || (_a[path] = [
|
|
95
|
+
...(routes[METHOD_NAME_ALL][path] ||
|
|
96
|
+
findMiddleware(middleware[method], path) ||
|
|
97
|
+
findMiddleware(middleware[METHOD_NAME_ALL], path) ||
|
|
98
|
+
[]),
|
|
99
|
+
]);
|
|
101
100
|
routes[m][path].push(handler);
|
|
101
|
+
}
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
}
|
package/dist/utils/json.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare type JSONPrimitive = string | boolean | number | null | undefined;
|
|
2
|
+
export declare type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[];
|
|
3
|
+
export declare type JSONObject = {
|
|
4
|
+
[key: string]: JSONPrimitive | JSONArray | JSONObject;
|
|
5
|
+
};
|
|
6
|
+
export declare type JSONValue = JSONObject | JSONArray | JSONPrimitive;
|
|
7
|
+
export declare const JSONPath: (data: JSONObject, path: string) => JSONValue;
|
package/dist/utils/json.js
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
-
let val = data;
|
|
4
|
-
const parts = path.split('.');
|
|
1
|
+
const JSONPathInternal = (data, parts) => {
|
|
5
2
|
const length = parts.length;
|
|
6
|
-
for (let i = 0; i < length &&
|
|
3
|
+
for (let i = 0; i < length && data !== undefined; i++) {
|
|
7
4
|
const p = parts[i];
|
|
8
|
-
if (p
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
if (p === '') {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
if (typeof data !== 'object' || data === null) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
if (p === '*') {
|
|
12
|
+
const restParts = parts.slice(i + 1);
|
|
13
|
+
const values = Object.values(data).map((v) => JSONPathInternal(v, restParts));
|
|
14
|
+
return restParts.indexOf('*') === -1 ? values : values.flat();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
data = data[p]; // `data` may be an array, but accessing it as an object yields the same result.
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
|
-
return
|
|
20
|
+
return data;
|
|
21
|
+
};
|
|
22
|
+
export const JSONPath = (data, path) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSONPathInternal(data, path.replace(/\[(.*?)\]/g, '.$1').split(/\./));
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
18
29
|
};
|