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.
@@ -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
- res = handler(context, () => {
27
- const dispatchRes = dispatch(i + 1);
28
- return dispatchRes instanceof Promise ? dispatchRes : Promise.resolve(dispatchRes);
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.then((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
  }
@@ -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.res && c.finalized) {
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
- const result = await validator.validate(c.req);
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
- const obj = await req.json();
75
- value = (0, json_1.JSONPath)(obj, this.key);
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 == false) {
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 - ${value}`;
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 - ${value}`;
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 - ${value}`;
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 - ${value}`;
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 (typeof value !== this.type) {
105
- if (this._optional && typeof value === 'undefined') {
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
- // Sanitize
114
- for (const sanitizer of this.sanitizers) {
115
- value = sanitizer(value);
116
- }
117
- for (const rule of this.rules) {
118
- if (!rule(value)) {
119
- return false;
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, _b;
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
  }
@@ -151,7 +151,6 @@ class Node {
151
151
  if (typeof name === 'string' && !matched) {
152
152
  params[name] = part;
153
153
  }
154
- break;
155
154
  }
156
155
  }
157
156
  }
@@ -1,22 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.JSONPath = void 0;
4
- const JSONPath = (data, path) => {
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 && val !== undefined; i++) {
6
+ for (let i = 0; i < length && data !== undefined; i++) {
10
7
  const p = parts[i];
11
- if (p !== '') {
12
- if (typeof val === 'object') {
13
- val = val[p];
14
- }
15
- else {
16
- val = undefined;
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 val;
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
- res = handler(context, () => {
24
- const dispatchRes = dispatch(i + 1);
25
- return dispatchRes instanceof Promise ? dispatchRes : Promise.resolve(dispatchRes);
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.then((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.res && c.finalized) {
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
- const result = await validator.validate(c.req);
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 = string | number | boolean | object | undefined;
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
- const obj = await req.json();
71
- value = JSONPath(obj, this.key);
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 == false) {
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 - ${value}`;
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 - ${value}`;
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 - ${value}`;
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 - ${value}`;
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 (typeof value !== this.type) {
101
- if (this._optional && typeof value === 'undefined') {
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
- // Sanitize
110
- for (const sanitizer of this.sanitizers) {
111
- value = sanitizer(value);
112
- }
113
- for (const rule of this.rules) {
114
- if (!rule(value)) {
115
- return false;
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, _b;
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
  }
@@ -148,7 +148,6 @@ export class Node {
148
148
  if (typeof name === 'string' && !matched) {
149
149
  params[name] = part;
150
150
  }
151
- break;
152
151
  }
153
152
  }
154
153
  }
@@ -1 +1,7 @@
1
- export declare const JSONPath: (data: object, path: string) => any;
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;
@@ -1,18 +1,29 @@
1
- export const JSONPath = (data, path) => {
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 && val !== undefined; i++) {
3
+ for (let i = 0; i < length && data !== undefined; i++) {
7
4
  const p = parts[i];
8
- if (p !== '') {
9
- if (typeof val === 'object') {
10
- val = val[p];
11
- }
12
- else {
13
- val = undefined;
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 val;
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",