@cfworker/web 5.1.1 → 6.0.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 CHANGED
@@ -81,6 +81,45 @@ new Application()
81
81
  .listen();
82
82
  ```
83
83
 
84
+ ## Repeated validation values
85
+
86
+ By default, `validate` converts headers, query parameters, and form fields into
87
+ objects before applying their schemas. A value that occurs once is stored
88
+ directly. Repeated values are stored as an array in encounter order, including
89
+ empty values:
90
+
91
+ ```ts
92
+ // ?tag=a&tag=&tag=b
93
+ const query = { tag: ['a', '', 'b'] };
94
+ ```
95
+
96
+ For query parameters and URL-encoded forms, values are strings. Multipart form
97
+ values can also be `File` objects. `Headers` supplies normalized values and
98
+ usually combines repeated header lines into one string before validation.
99
+
100
+ The array shape makes a scalar schema reject duplicate query parameters or form
101
+ fields rather than validating a different value from the one application code
102
+ might read. To accept both single and repeated values, declare both shapes
103
+ explicitly:
104
+
105
+ ```ts
106
+ validate({
107
+ search: {
108
+ properties: {
109
+ tag: {
110
+ oneOf: [
111
+ { type: 'string' },
112
+ { type: 'array', items: { type: 'string' } }
113
+ ]
114
+ }
115
+ }
116
+ }
117
+ });
118
+ ```
119
+
120
+ A custom `RequestParser` continues to receive the original `URLSearchParams` or
121
+ `FormData` input and controls its resulting shape.
122
+
84
123
  [To run this example](/packages/examples/web/index.ts):
85
124
 
86
125
  ```
@@ -0,0 +1,4 @@
1
+ type ParsedEntries<T> = Record<string, T | T[]>;
2
+ export declare function parseEntries<T>(input: Iterable<readonly [string, T]>): ParsedEntries<T>;
3
+ export {};
4
+ //# sourceMappingURL=parse-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-entries.d.ts","sourceRoot":"","sources":["../../src/parse-entries.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAEhD,wBAAgB,YAAY,CAAC,CAAC,EAC5B,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GACpC,aAAa,CAAC,CAAC,CAAC,CAkBlB"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseEntries = parseEntries;
4
+ function parseEntries(input) {
5
+ const entries = new Map();
6
+ for (const [name, value] of input) {
7
+ const values = entries.get(name);
8
+ if (values === undefined) {
9
+ entries.set(name, [value]);
10
+ }
11
+ else {
12
+ values.push(value);
13
+ }
14
+ }
15
+ return Object.fromEntries(Array.from(entries, ([name, values]) => [
16
+ name,
17
+ values.length === 1 ? values[0] : values
18
+ ]));
19
+ }
20
+ //# sourceMappingURL=parse-entries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-entries.js","sourceRoot":"","sources":["../../src/parse-entries.ts"],"names":[],"mappings":";;;AAEA,sBACE,KAAqC;IAErC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IAEvC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,WAAW,CACvB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI;QACJ,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,MAAM;KAC1C,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["type ParsedEntries<T> = Record<string, T | T[]>;\n\nexport function parseEntries<T>(\n input: Iterable<readonly [string, T]>\n): ParsedEntries<T> {\n const entries = new Map<string, T[]>();\n\n for (const [name, value] of input) {\n const values = entries.get(name);\n if (values === undefined) {\n entries.set(name, [value]);\n } else {\n values.push(value);\n }\n }\n\n return Object.fromEntries(\n Array.from(entries, ([name, values]) => [\n name,\n values.length === 1 ? values[0]! : values\n ])\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,MAAM,EAEP,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,KAAK,GAAG,CAAC;AAWtE,iBAAS,iBAAiB,CACxB,OAAO,EAAE,cAAc,EACvB,MAAM,GAAE,aAAkC,EAC1C,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAuB,GAC7D,UAAU,CAkDZ;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAKhD,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD,eAAO,MAAM,QAAQ,EAAE,OAAO,iBAAqC,CAAC"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,MAAM,EAEP,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,KAAK,GAAG,CAAC;AAWtE,iBAAS,iBAAiB,CACxB,OAAO,EAAE,cAAc,EACvB,MAAM,GAAE,aAA4B,EACpC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAuB,GAC7D,UAAU,CAkDZ;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAKhD,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD,eAAO,MAAM,QAAQ,EAAE,OAAO,iBAAqC,CAAC"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = exports.messages = void 0;
4
4
  const json_schema_1 = require("@cfworker/json-schema");
5
5
  const http_error_js_1 = require("./http-error.js");
6
+ const parse_entries_js_1 = require("./parse-entries.js");
6
7
  const draft = '2019-09';
7
8
  const hasBody = {
8
9
  POST: true,
@@ -10,7 +11,7 @@ const hasBody = {
10
11
  DELETE: true,
11
12
  PATCH: true
12
13
  };
13
- function middlewareFactory(schemas, parser = Object.fromEntries, lookup = Object.create(null)) {
14
+ function middlewareFactory(schemas, parser = parse_entries_js_1.parseEntries, lookup = Object.create(null)) {
14
15
  const { headers: $headers, search: $search, params: $params, body: $body } = schemas;
15
16
  if ($headers) {
16
17
  (0, json_schema_1.dereference)($headers, { ...lookup });
@@ -29,7 +30,7 @@ function middlewareFactory(schemas, parser = Object.fromEntries, lookup = Object
29
30
  validateRequestPart('params', req.params, $params, lookup);
30
31
  }
31
32
  if ($headers) {
32
- const headers = Object.fromEntries(req.headers);
33
+ const headers = (0, parse_entries_js_1.parseEntries)(req.headers);
33
34
  validateRequestPart('headers', headers, $headers, lookup);
34
35
  }
35
36
  if ($search) {
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n dereference,\n OutputUnit,\n Schema,\n validate as schemaValidate\n} from '@cfworker/json-schema';\nimport { HttpError } from './http-error.js';\nimport { Middleware } from './middleware.js';\n\nexport interface RequestSchemas {\n body?: Schema;\n headers?: Schema;\n params?: Schema;\n search?: Schema;\n}\n\nexport type RequestPart = keyof RequestSchemas;\n\nexport type RequestParser = (data: URLSearchParams | FormData) => any;\n\nconst draft = '2019-09';\n\nconst hasBody: Record<string, true> = {\n POST: true,\n PUT: true,\n DELETE: true,\n PATCH: true\n};\n\nfunction middlewareFactory(\n schemas: RequestSchemas,\n parser: RequestParser = Object.fromEntries,\n lookup: Record<string, Schema | boolean> = Object.create(null)\n): Middleware {\n const {\n headers: $headers,\n search: $search,\n params: $params,\n body: $body\n } = schemas;\n\n if ($headers) {\n dereference($headers, { ...lookup });\n }\n if ($search) {\n dereference($search, { ...lookup });\n }\n if ($params) {\n dereference($params, { ...lookup });\n }\n if ($body) {\n dereference($body, { ...lookup });\n }\n\n return async ({ req }, next) => {\n if ($params) {\n validateRequestPart('params', req.params, $params, lookup);\n }\n if ($headers) {\n const headers = Object.fromEntries(req.headers);\n validateRequestPart('headers', headers, $headers, lookup);\n }\n if ($search) {\n const search = parser(req.url.searchParams);\n validateRequestPart('search', search, $search, lookup);\n }\n if ($body && hasBody[req.method]) {\n let body: any;\n const contentType = req.headers.get('content-type') || '';\n if (\n contentType === 'application/x-www-form-urlencoded' ||\n contentType.startsWith('application/x-www-form-urlencoded;') ||\n contentType.startsWith('multipart/form-data;')\n ) {\n const form = await req.body.formData();\n body = parser(form);\n } else {\n body = await req.body.json();\n }\n validateRequestPart('body', body, $body, lookup);\n }\n await next();\n };\n}\n\nexport const messages: Record<RequestPart, string> = {\n params: 'Parameters in request path are invalid.',\n headers: 'Headers are invalid.',\n search: 'Query string parameters are invalid.',\n body: 'Body is invalid.'\n};\n\nexport interface RequestValidationErrorBody {\n status: 400;\n part: RequestPart;\n message: string;\n errors: OutputUnit[];\n schema: Schema;\n}\n\nfunction validateRequestPart(\n part: RequestPart,\n instance: any,\n schema: Schema,\n lookup: Record<string, boolean | Schema>\n) {\n const result = schemaValidate(instance, schema, draft, lookup);\n if (result.valid) {\n return;\n }\n const errorBody: RequestValidationErrorBody = {\n status: 400,\n part,\n message: messages[part],\n errors: result.errors,\n schema\n };\n throw new HttpError(400, errorBody);\n}\n\nexport const validate: typeof middlewareFactory = middlewareFactory;\n"]}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n dereference,\n OutputUnit,\n Schema,\n validate as schemaValidate\n} from '@cfworker/json-schema';\nimport { HttpError } from './http-error.js';\nimport { Middleware } from './middleware.js';\nimport { parseEntries } from './parse-entries.js';\n\nexport interface RequestSchemas {\n body?: Schema;\n headers?: Schema;\n params?: Schema;\n search?: Schema;\n}\n\nexport type RequestPart = keyof RequestSchemas;\n\nexport type RequestParser = (data: URLSearchParams | FormData) => any;\n\nconst draft = '2019-09';\n\nconst hasBody: Record<string, true> = {\n POST: true,\n PUT: true,\n DELETE: true,\n PATCH: true\n};\n\nfunction middlewareFactory(\n schemas: RequestSchemas,\n parser: RequestParser = parseEntries,\n lookup: Record<string, Schema | boolean> = Object.create(null)\n): Middleware {\n const {\n headers: $headers,\n search: $search,\n params: $params,\n body: $body\n } = schemas;\n\n if ($headers) {\n dereference($headers, { ...lookup });\n }\n if ($search) {\n dereference($search, { ...lookup });\n }\n if ($params) {\n dereference($params, { ...lookup });\n }\n if ($body) {\n dereference($body, { ...lookup });\n }\n\n return async ({ req }, next) => {\n if ($params) {\n validateRequestPart('params', req.params, $params, lookup);\n }\n if ($headers) {\n const headers = parseEntries(req.headers);\n validateRequestPart('headers', headers, $headers, lookup);\n }\n if ($search) {\n const search = parser(req.url.searchParams);\n validateRequestPart('search', search, $search, lookup);\n }\n if ($body && hasBody[req.method]) {\n let body: any;\n const contentType = req.headers.get('content-type') || '';\n if (\n contentType === 'application/x-www-form-urlencoded' ||\n contentType.startsWith('application/x-www-form-urlencoded;') ||\n contentType.startsWith('multipart/form-data;')\n ) {\n const form = await req.body.formData();\n body = parser(form);\n } else {\n body = await req.body.json();\n }\n validateRequestPart('body', body, $body, lookup);\n }\n await next();\n };\n}\n\nexport const messages: Record<RequestPart, string> = {\n params: 'Parameters in request path are invalid.',\n headers: 'Headers are invalid.',\n search: 'Query string parameters are invalid.',\n body: 'Body is invalid.'\n};\n\nexport interface RequestValidationErrorBody {\n status: 400;\n part: RequestPart;\n message: string;\n errors: OutputUnit[];\n schema: Schema;\n}\n\nfunction validateRequestPart(\n part: RequestPart,\n instance: any,\n schema: Schema,\n lookup: Record<string, boolean | Schema>\n) {\n const result = schemaValidate(instance, schema, draft, lookup);\n if (result.valid) {\n return;\n }\n const errorBody: RequestValidationErrorBody = {\n status: 400,\n part,\n message: messages[part],\n errors: result.errors,\n schema\n };\n throw new HttpError(400, errorBody);\n}\n\nexport const validate: typeof middlewareFactory = middlewareFactory;\n"]}
@@ -0,0 +1,4 @@
1
+ type ParsedEntries<T> = Record<string, T | T[]>;
2
+ export declare function parseEntries<T>(input: Iterable<readonly [string, T]>): ParsedEntries<T>;
3
+ export {};
4
+ //# sourceMappingURL=parse-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-entries.d.ts","sourceRoot":"","sources":["../../src/parse-entries.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAEhD,wBAAgB,YAAY,CAAC,CAAC,EAC5B,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GACpC,aAAa,CAAC,CAAC,CAAC,CAkBlB"}
@@ -0,0 +1,17 @@
1
+ export function parseEntries(input) {
2
+ const entries = new Map();
3
+ for (const [name, value] of input) {
4
+ const values = entries.get(name);
5
+ if (values === undefined) {
6
+ entries.set(name, [value]);
7
+ }
8
+ else {
9
+ values.push(value);
10
+ }
11
+ }
12
+ return Object.fromEntries(Array.from(entries, ([name, values]) => [
13
+ name,
14
+ values.length === 1 ? values[0] : values
15
+ ]));
16
+ }
17
+ //# sourceMappingURL=parse-entries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-entries.js","sourceRoot":"","sources":["../../src/parse-entries.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAC1B,KAAqC;IAErC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IAEvC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,WAAW,CACvB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI;QACJ,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,MAAM;KAC1C,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["type ParsedEntries<T> = Record<string, T | T[]>;\n\nexport function parseEntries<T>(\n input: Iterable<readonly [string, T]>\n): ParsedEntries<T> {\n const entries = new Map<string, T[]>();\n\n for (const [name, value] of input) {\n const values = entries.get(name);\n if (values === undefined) {\n entries.set(name, [value]);\n } else {\n values.push(value);\n }\n }\n\n return Object.fromEntries(\n Array.from(entries, ([name, values]) => [\n name,\n values.length === 1 ? values[0]! : values\n ])\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,MAAM,EAEP,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,KAAK,GAAG,CAAC;AAWtE,iBAAS,iBAAiB,CACxB,OAAO,EAAE,cAAc,EACvB,MAAM,GAAE,aAAkC,EAC1C,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAuB,GAC7D,UAAU,CAkDZ;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAKhD,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD,eAAO,MAAM,QAAQ,EAAE,OAAO,iBAAqC,CAAC"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,MAAM,EAEP,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,KAAK,GAAG,CAAC;AAWtE,iBAAS,iBAAiB,CACxB,OAAO,EAAE,cAAc,EACvB,MAAM,GAAE,aAA4B,EACpC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAuB,GAC7D,UAAU,CAkDZ;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAKhD,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD,eAAO,MAAM,QAAQ,EAAE,OAAO,iBAAqC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { dereference, validate as schemaValidate } from '@cfworker/json-schema';
2
2
  import { HttpError } from './http-error.js';
3
+ import { parseEntries } from './parse-entries.js';
3
4
  const draft = '2019-09';
4
5
  const hasBody = {
5
6
  POST: true,
@@ -7,7 +8,7 @@ const hasBody = {
7
8
  DELETE: true,
8
9
  PATCH: true
9
10
  };
10
- function middlewareFactory(schemas, parser = Object.fromEntries, lookup = Object.create(null)) {
11
+ function middlewareFactory(schemas, parser = parseEntries, lookup = Object.create(null)) {
11
12
  const { headers: $headers, search: $search, params: $params, body: $body } = schemas;
12
13
  if ($headers) {
13
14
  dereference($headers, { ...lookup });
@@ -26,7 +27,7 @@ function middlewareFactory(schemas, parser = Object.fromEntries, lookup = Object
26
27
  validateRequestPart('params', req.params, $params, lookup);
27
28
  }
28
29
  if ($headers) {
29
- const headers = Object.fromEntries(req.headers);
30
+ const headers = parseEntries(req.headers);
30
31
  validateRequestPart('headers', headers, $headers, lookup);
31
32
  }
32
33
  if ($search) {
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n dereference,\n OutputUnit,\n Schema,\n validate as schemaValidate\n} from '@cfworker/json-schema';\nimport { HttpError } from './http-error.js';\nimport { Middleware } from './middleware.js';\n\nexport interface RequestSchemas {\n body?: Schema;\n headers?: Schema;\n params?: Schema;\n search?: Schema;\n}\n\nexport type RequestPart = keyof RequestSchemas;\n\nexport type RequestParser = (data: URLSearchParams | FormData) => any;\n\nconst draft = '2019-09';\n\nconst hasBody: Record<string, true> = {\n POST: true,\n PUT: true,\n DELETE: true,\n PATCH: true\n};\n\nfunction middlewareFactory(\n schemas: RequestSchemas,\n parser: RequestParser = Object.fromEntries,\n lookup: Record<string, Schema | boolean> = Object.create(null)\n): Middleware {\n const {\n headers: $headers,\n search: $search,\n params: $params,\n body: $body\n } = schemas;\n\n if ($headers) {\n dereference($headers, { ...lookup });\n }\n if ($search) {\n dereference($search, { ...lookup });\n }\n if ($params) {\n dereference($params, { ...lookup });\n }\n if ($body) {\n dereference($body, { ...lookup });\n }\n\n return async ({ req }, next) => {\n if ($params) {\n validateRequestPart('params', req.params, $params, lookup);\n }\n if ($headers) {\n const headers = Object.fromEntries(req.headers);\n validateRequestPart('headers', headers, $headers, lookup);\n }\n if ($search) {\n const search = parser(req.url.searchParams);\n validateRequestPart('search', search, $search, lookup);\n }\n if ($body && hasBody[req.method]) {\n let body: any;\n const contentType = req.headers.get('content-type') || '';\n if (\n contentType === 'application/x-www-form-urlencoded' ||\n contentType.startsWith('application/x-www-form-urlencoded;') ||\n contentType.startsWith('multipart/form-data;')\n ) {\n const form = await req.body.formData();\n body = parser(form);\n } else {\n body = await req.body.json();\n }\n validateRequestPart('body', body, $body, lookup);\n }\n await next();\n };\n}\n\nexport const messages: Record<RequestPart, string> = {\n params: 'Parameters in request path are invalid.',\n headers: 'Headers are invalid.',\n search: 'Query string parameters are invalid.',\n body: 'Body is invalid.'\n};\n\nexport interface RequestValidationErrorBody {\n status: 400;\n part: RequestPart;\n message: string;\n errors: OutputUnit[];\n schema: Schema;\n}\n\nfunction validateRequestPart(\n part: RequestPart,\n instance: any,\n schema: Schema,\n lookup: Record<string, boolean | Schema>\n) {\n const result = schemaValidate(instance, schema, draft, lookup);\n if (result.valid) {\n return;\n }\n const errorBody: RequestValidationErrorBody = {\n status: 400,\n part,\n message: messages[part],\n errors: result.errors,\n schema\n };\n throw new HttpError(400, errorBody);\n}\n\nexport const validate: typeof middlewareFactory = middlewareFactory;\n"]}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n dereference,\n OutputUnit,\n Schema,\n validate as schemaValidate\n} from '@cfworker/json-schema';\nimport { HttpError } from './http-error.js';\nimport { Middleware } from './middleware.js';\nimport { parseEntries } from './parse-entries.js';\n\nexport interface RequestSchemas {\n body?: Schema;\n headers?: Schema;\n params?: Schema;\n search?: Schema;\n}\n\nexport type RequestPart = keyof RequestSchemas;\n\nexport type RequestParser = (data: URLSearchParams | FormData) => any;\n\nconst draft = '2019-09';\n\nconst hasBody: Record<string, true> = {\n POST: true,\n PUT: true,\n DELETE: true,\n PATCH: true\n};\n\nfunction middlewareFactory(\n schemas: RequestSchemas,\n parser: RequestParser = parseEntries,\n lookup: Record<string, Schema | boolean> = Object.create(null)\n): Middleware {\n const {\n headers: $headers,\n search: $search,\n params: $params,\n body: $body\n } = schemas;\n\n if ($headers) {\n dereference($headers, { ...lookup });\n }\n if ($search) {\n dereference($search, { ...lookup });\n }\n if ($params) {\n dereference($params, { ...lookup });\n }\n if ($body) {\n dereference($body, { ...lookup });\n }\n\n return async ({ req }, next) => {\n if ($params) {\n validateRequestPart('params', req.params, $params, lookup);\n }\n if ($headers) {\n const headers = parseEntries(req.headers);\n validateRequestPart('headers', headers, $headers, lookup);\n }\n if ($search) {\n const search = parser(req.url.searchParams);\n validateRequestPart('search', search, $search, lookup);\n }\n if ($body && hasBody[req.method]) {\n let body: any;\n const contentType = req.headers.get('content-type') || '';\n if (\n contentType === 'application/x-www-form-urlencoded' ||\n contentType.startsWith('application/x-www-form-urlencoded;') ||\n contentType.startsWith('multipart/form-data;')\n ) {\n const form = await req.body.formData();\n body = parser(form);\n } else {\n body = await req.body.json();\n }\n validateRequestPart('body', body, $body, lookup);\n }\n await next();\n };\n}\n\nexport const messages: Record<RequestPart, string> = {\n params: 'Parameters in request path are invalid.',\n headers: 'Headers are invalid.',\n search: 'Query string parameters are invalid.',\n body: 'Body is invalid.'\n};\n\nexport interface RequestValidationErrorBody {\n status: 400;\n part: RequestPart;\n message: string;\n errors: OutputUnit[];\n schema: Schema;\n}\n\nfunction validateRequestPart(\n part: RequestPart,\n instance: any,\n schema: Schema,\n lookup: Record<string, boolean | Schema>\n) {\n const result = schemaValidate(instance, schema, draft, lookup);\n if (result.valid) {\n return;\n }\n const errorBody: RequestValidationErrorBody = {\n status: 400,\n part,\n message: messages[part],\n errors: result.errors,\n schema\n };\n throw new HttpError(400, errorBody);\n}\n\nexport const validate: typeof middlewareFactory = middlewareFactory;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfworker/web",
3
- "version": "5.1.1",
3
+ "version": "6.0.0",
4
4
  "description": "Web framework for Cloudflare Workers and service workers, inspired by Koa and fastify",
5
5
  "keywords": [
6
6
  "middleware",
@@ -46,7 +46,7 @@
46
46
  "test": "node ../../test.mjs"
47
47
  },
48
48
  "dependencies": {
49
- "@cfworker/json-schema": "^4.1.3",
49
+ "@cfworker/json-schema": "^4.1.4",
50
50
  "@hapi/bourne": "^4.0.1",
51
51
  "@types/statuses": "^2.0.6",
52
52
  "cookie": "^2.0.1",