gruber 0.2.0 → 0.4.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/CHANGELOG.md +29 -0
- package/README.md +156 -16
- package/core/configuration.d.ts +157 -0
- package/core/configuration.js +222 -96
- package/core/configuration.test.d.ts +1 -0
- package/core/configuration.test.js +242 -53
- package/{types/core → core}/fetch-router.d.ts +0 -1
- package/core/fetch-router.test.d.ts +1 -0
- package/{types/core → core}/http.d.ts +30 -12
- package/core/http.js +42 -17
- package/core/http.test.d.ts +1 -0
- package/core/http.test.js +57 -35
- package/{types/core → core}/migrator.d.ts +0 -1
- package/core/migrator.test.d.ts +1 -0
- package/{types/core → core}/mod.d.ts +1 -1
- package/core/mod.js +1 -0
- package/{types/core → core}/postgres.d.ts +0 -1
- package/core/structures.d.ts +91 -0
- package/core/structures.js +260 -0
- package/core/structures.test.d.ts +1 -0
- package/core/structures.test.js +474 -0
- package/core/test-deps.d.ts +1 -0
- package/core/test-deps.js +1 -1
- package/{types/core → core}/types.d.ts +0 -1
- package/{types/core → core}/utilities.d.ts +0 -1
- package/core/utilities.test.d.ts +1 -0
- package/package.json +4 -5
- package/{types/source → source}/configuration.d.ts +4 -9
- package/source/configuration.js +0 -2
- package/source/core.d.ts +1 -0
- package/{types/source → source}/express-router.d.ts +2 -3
- package/source/express-router.js +1 -1
- package/{types/source → source}/koa-router.d.ts +0 -1
- package/{types/source → source}/mod.d.ts +0 -3
- package/source/mod.js +2 -2
- package/{types/source → source}/node-router.d.ts +8 -8
- package/source/node-router.js +1 -1
- package/source/package-lock.json +3 -9
- package/source/package.json +0 -2
- package/source/polyfill.d.ts +1 -0
- package/{types/source → source}/postgres.d.ts +0 -1
- package/tsconfig.json +0 -2
- package/types/core/configuration.d.ts +0 -57
- package/types/core/configuration.d.ts.map +0 -1
- package/types/core/configuration.test.d.ts +0 -2
- package/types/core/configuration.test.d.ts.map +0 -1
- package/types/core/fetch-router.d.ts.map +0 -1
- package/types/core/fetch-router.test.d.ts +0 -2
- package/types/core/fetch-router.test.d.ts.map +0 -1
- package/types/core/http.d.ts.map +0 -1
- package/types/core/http.test.d.ts +0 -2
- package/types/core/http.test.d.ts.map +0 -1
- package/types/core/migrator.d.ts.map +0 -1
- package/types/core/migrator.test.d.ts +0 -2
- package/types/core/migrator.test.d.ts.map +0 -1
- package/types/core/mod.d.ts.map +0 -1
- package/types/core/postgres.d.ts.map +0 -1
- package/types/core/test-deps.d.ts +0 -2
- package/types/core/test-deps.d.ts.map +0 -1
- package/types/core/types.d.ts.map +0 -1
- package/types/core/utilities.d.ts.map +0 -1
- package/types/core/utilities.test.d.ts +0 -2
- package/types/core/utilities.test.d.ts.map +0 -1
- package/types/source/configuration.d.ts.map +0 -1
- package/types/source/core.d.ts +0 -2
- package/types/source/core.d.ts.map +0 -1
- package/types/source/express-router.d.ts.map +0 -1
- package/types/source/koa-router.d.ts.map +0 -1
- package/types/source/mod.d.ts.map +0 -1
- package/types/source/node-router.d.ts.map +0 -1
- package/types/source/polyfill.d.ts +0 -2
- package/types/source/polyfill.d.ts.map +0 -1
- package/types/source/postgres.d.ts.map +0 -1
package/core/http.test.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { assertInstanceOf, assertEquals } from "./test-deps.js";
|
|
1
|
+
import { assertInstanceOf, assertEquals, describe, it } from "./test-deps.js";
|
|
2
2
|
import { defineRoute, HTTPError } from "./http.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
describe("defineRoute", () => {
|
|
5
|
+
it("sets the method", () => {
|
|
6
6
|
const result = defineRoute({
|
|
7
7
|
method: "GET",
|
|
8
8
|
pathname: "/",
|
|
@@ -11,7 +11,7 @@ Deno.test("defineRoute", async (t) => {
|
|
|
11
11
|
assertEquals(result.method, "GET");
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
it("creates a URLPattern", () => {
|
|
15
15
|
const result = defineRoute({
|
|
16
16
|
method: "GET",
|
|
17
17
|
pathname: "/hello/:name",
|
|
@@ -22,47 +22,69 @@ Deno.test("defineRoute", async (t) => {
|
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
describe("HTTPError", () => {
|
|
26
|
+
describe("constructor", () => {
|
|
27
|
+
it("creates the error", () => {
|
|
28
|
+
const result = new HTTPError(418, "I'm a teapot");
|
|
29
|
+
assertEquals(result.status, 418);
|
|
30
|
+
assertEquals(result.statusText, "I'm a teapot");
|
|
31
|
+
assertEquals(result.name, "HTTPError");
|
|
32
|
+
});
|
|
31
33
|
});
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
describe("toResponse", () => {
|
|
36
|
+
it("creates the error", () => {
|
|
37
|
+
const result = new HTTPError(200, "OK").toResponse();
|
|
38
|
+
assertEquals(result.status, 200);
|
|
39
|
+
assertEquals(result.statusText, "OK");
|
|
40
|
+
});
|
|
41
|
+
it("sets headers", () => {
|
|
42
|
+
const error = new HTTPError(200, "OK", null, {
|
|
43
|
+
"X-HOTEL-BAR": "Hotel Bar?",
|
|
44
|
+
});
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
assertEquals(result.statusText, "Bad Request");
|
|
46
|
+
const result = error.toResponse();
|
|
47
|
+
assertEquals(result.headers.get("X-HOTEL-BAR"), "Hotel Bar?");
|
|
48
|
+
});
|
|
43
49
|
});
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
describe("badRequest", () => {
|
|
52
|
+
it("creates the error", () => {
|
|
53
|
+
const result = HTTPError.badRequest("body");
|
|
54
|
+
assertEquals(result.status, 400);
|
|
55
|
+
assertEquals(result.statusText, "Bad Request");
|
|
56
|
+
});
|
|
49
57
|
});
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
describe("unauthorized", () => {
|
|
60
|
+
it("creates the error", () => {
|
|
61
|
+
const result = HTTPError.unauthorized();
|
|
62
|
+
assertEquals(result.status, 401);
|
|
63
|
+
assertEquals(result.statusText, "Unauthorized");
|
|
64
|
+
});
|
|
55
65
|
});
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
describe("notFound", () => {
|
|
68
|
+
it("creates the error", () => {
|
|
69
|
+
const result = HTTPError.notFound();
|
|
70
|
+
assertEquals(result.status, 404);
|
|
71
|
+
assertEquals(result.statusText, "Not Found");
|
|
72
|
+
});
|
|
61
73
|
});
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
describe("internalServerError", () => {
|
|
76
|
+
it("creates the error", () => {
|
|
77
|
+
const result = HTTPError.internalServerError();
|
|
78
|
+
assertEquals(result.status, 500);
|
|
79
|
+
assertEquals(result.statusText, "Internal Server Error");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("notImplemented", () => {
|
|
83
|
+
it("creates the error", () => {
|
|
84
|
+
const result = HTTPError.notImplemented();
|
|
85
|
+
assertEquals(result.status, 501);
|
|
86
|
+
assertEquals(result.statusText, "Not Implemented");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
67
89
|
});
|
|
68
90
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/core/mod.js
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export class StructError extends Error {
|
|
2
|
+
/**
|
|
3
|
+
* @param {unknown} error
|
|
4
|
+
* @param {StructContext} context
|
|
5
|
+
* @returns {StructError}
|
|
6
|
+
*/
|
|
7
|
+
static chain(error: unknown, context?: StructContext): StructError;
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} message
|
|
10
|
+
* @param {string[]} path
|
|
11
|
+
* @param {StructError[]} children
|
|
12
|
+
*/
|
|
13
|
+
constructor(message: string, path?: string[], children?: StructError[]);
|
|
14
|
+
path: string[];
|
|
15
|
+
children: StructError[];
|
|
16
|
+
getOneLiner(): string;
|
|
17
|
+
toFriendlyString(): string;
|
|
18
|
+
/** @returns {Iterator<StructError>} */
|
|
19
|
+
[Symbol.iterator](): Iterator<StructError>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Record<string,unknown>} Schema
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @template T
|
|
26
|
+
* @typedef {(input?: unknown, context?: StructContext) => T} StructExec
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* @template T
|
|
30
|
+
* @typedef {T extends Structure<infer U> ? U : never} Infer
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @template T
|
|
34
|
+
*/
|
|
35
|
+
export class Structure<T> {
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} [fallback]
|
|
38
|
+
* @returns {Structure<string>}
|
|
39
|
+
*/
|
|
40
|
+
static string(fallback?: string): Structure<string>;
|
|
41
|
+
/**
|
|
42
|
+
* @param {number} [fallback]
|
|
43
|
+
* @returns {Structure<number>}
|
|
44
|
+
*/
|
|
45
|
+
static number(fallback?: number): Structure<number>;
|
|
46
|
+
/**
|
|
47
|
+
* @param {boolean} fallback
|
|
48
|
+
* @returns {Structure<boolean>}
|
|
49
|
+
*/
|
|
50
|
+
static boolean(fallback: boolean): Structure<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* @param {string | URL} [fallback]
|
|
53
|
+
* @returns {Structure<URL>}
|
|
54
|
+
*/
|
|
55
|
+
static url(fallback?: string | URL): Structure<URL>;
|
|
56
|
+
/**
|
|
57
|
+
* @template {Record<string, Structure<any>>} U
|
|
58
|
+
* @param {U} fields
|
|
59
|
+
* @returns {Structure<{ [K in keyof U]: Infer<U[K]> }>}
|
|
60
|
+
*/
|
|
61
|
+
static object<U extends Record<string, Structure<any>>>(fields: U): Structure<{ [K in keyof U]: Infer<U[K]>; }>;
|
|
62
|
+
/**
|
|
63
|
+
* **UNSTABLE** use at your own risk
|
|
64
|
+
*
|
|
65
|
+
* @template {Structure<any>} U
|
|
66
|
+
* @param {U} struct
|
|
67
|
+
* @returns {Structure<Array<Infer<U>>>}
|
|
68
|
+
*/
|
|
69
|
+
static array<U_1 extends Structure<any>>(struct: U_1): Structure<Infer<U_1>[]>;
|
|
70
|
+
/**
|
|
71
|
+
* @param {Schema} schema
|
|
72
|
+
* @param {StructExec<T>} process
|
|
73
|
+
*/
|
|
74
|
+
constructor(schema: Schema, process: StructExec<T>);
|
|
75
|
+
schema: Schema;
|
|
76
|
+
process: StructExec<T>;
|
|
77
|
+
getSchema(): {
|
|
78
|
+
$schema: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export type StructContext = {
|
|
82
|
+
path: string[];
|
|
83
|
+
};
|
|
84
|
+
export type StructOptions<Type, Schema> = {
|
|
85
|
+
type: 'object' | 'string' | 'url';
|
|
86
|
+
schema: Schema;
|
|
87
|
+
fn: (value: unknown, ctx: StructContext) => Type;
|
|
88
|
+
};
|
|
89
|
+
export type Schema = Record<string, unknown>;
|
|
90
|
+
export type StructExec<T> = (input?: unknown, context?: StructContext) => T;
|
|
91
|
+
export type Infer<T> = T extends Structure<infer U> ? U : never;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/** @typedef {{ path: string[] }} StructContext */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template Type
|
|
5
|
+
* @template Schema
|
|
6
|
+
* @typedef {object} StructOptions
|
|
7
|
+
* @property {'object' | 'string' | 'url'} type
|
|
8
|
+
* @property {Schema} schema
|
|
9
|
+
* @property {(value: unknown, ctx: StructContext) => Type} fn
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CONTEXT = { path: [] };
|
|
13
|
+
|
|
14
|
+
export class StructError extends Error {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} message
|
|
17
|
+
* @param {string[]} path
|
|
18
|
+
* @param {StructError[]} children
|
|
19
|
+
*/
|
|
20
|
+
constructor(message, path = [], children = []) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.path = path;
|
|
23
|
+
this.children = children;
|
|
24
|
+
this.name = "StructError";
|
|
25
|
+
Error.captureStackTrace(this, StructError);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {unknown} error
|
|
30
|
+
* @param {StructContext} context
|
|
31
|
+
* @returns {StructError}
|
|
32
|
+
*/
|
|
33
|
+
static chain(error, context = DEFAULT_CONTEXT) {
|
|
34
|
+
let chained;
|
|
35
|
+
if (error instanceof StructError) {
|
|
36
|
+
return error;
|
|
37
|
+
} else if (error instanceof Error) {
|
|
38
|
+
chained = new StructError(error.message, context.path);
|
|
39
|
+
} else {
|
|
40
|
+
chained = new StructError("Unknown error", context.path);
|
|
41
|
+
}
|
|
42
|
+
Error.captureStackTrace(chained, StructError.chain);
|
|
43
|
+
return chained;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getOneLiner() {
|
|
47
|
+
return (this.path.join(".") || ".") + " — " + this.message;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @returns {Iterator<StructError>} */
|
|
51
|
+
*[Symbol.iterator]() {
|
|
52
|
+
if (this.children.length === 0) {
|
|
53
|
+
yield this;
|
|
54
|
+
} else {
|
|
55
|
+
for (const child of this.children) {
|
|
56
|
+
yield* child;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
toFriendlyString() {
|
|
62
|
+
const messages = [];
|
|
63
|
+
for (const child of this) {
|
|
64
|
+
messages.push(" " + child.getOneLiner());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
this.message,
|
|
69
|
+
...messages.toSorted((a, b) => a.localeCompare(b)),
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @typedef {Record<string,unknown>} Schema
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @template T
|
|
80
|
+
* @typedef {(input?: unknown, context?: StructContext) => T} StructExec
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @template T
|
|
85
|
+
* @typedef {T extends Structure<infer U> ? U : never} Infer
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @template T
|
|
90
|
+
*/
|
|
91
|
+
export class Structure {
|
|
92
|
+
/**
|
|
93
|
+
* @param {Schema} schema
|
|
94
|
+
* @param {StructExec<T>} process
|
|
95
|
+
*/
|
|
96
|
+
constructor(schema, process) {
|
|
97
|
+
this.schema = schema;
|
|
98
|
+
this.process = process;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getSchema() {
|
|
102
|
+
return {
|
|
103
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
104
|
+
...this.schema,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} [fallback]
|
|
110
|
+
* @returns {Structure<string>}
|
|
111
|
+
*/
|
|
112
|
+
static string(fallback = undefined) {
|
|
113
|
+
const schema = { type: "string" };
|
|
114
|
+
if (fallback !== undefined) schema.default = fallback;
|
|
115
|
+
|
|
116
|
+
return new Structure(schema, (input = fallback, context = undefined) => {
|
|
117
|
+
if (input === undefined) {
|
|
118
|
+
throw new StructError("Missing value", context?.path);
|
|
119
|
+
}
|
|
120
|
+
if (typeof input !== "string") {
|
|
121
|
+
throw new StructError("Expected a string", context?.path);
|
|
122
|
+
}
|
|
123
|
+
return input;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {number} [fallback]
|
|
129
|
+
* @returns {Structure<number>}
|
|
130
|
+
*/
|
|
131
|
+
static number(fallback) {
|
|
132
|
+
const schema = { type: "number" };
|
|
133
|
+
if (fallback !== undefined) schema.default = fallback;
|
|
134
|
+
|
|
135
|
+
return new Structure(schema, (input = fallback, context = undefined) => {
|
|
136
|
+
if (input === undefined) {
|
|
137
|
+
throw new StructError("Missing value", context?.path);
|
|
138
|
+
}
|
|
139
|
+
// if (typeof input === "string") {
|
|
140
|
+
// const parsed = Number.parseFloat(input);
|
|
141
|
+
// if (!Number.isNaN(parsed)) return parsed;
|
|
142
|
+
// }
|
|
143
|
+
if (typeof input !== "number") {
|
|
144
|
+
throw new StructError("Expected a number", context?.path);
|
|
145
|
+
}
|
|
146
|
+
return input;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {boolean} fallback
|
|
152
|
+
* @returns {Structure<boolean>}
|
|
153
|
+
*/
|
|
154
|
+
static boolean(fallback) {
|
|
155
|
+
const schema = { type: "boolean" };
|
|
156
|
+
if (fallback !== undefined) schema.default = fallback;
|
|
157
|
+
|
|
158
|
+
return new Structure(schema, (input, context) => {
|
|
159
|
+
if (input === undefined) return fallback;
|
|
160
|
+
if (typeof input !== "boolean") {
|
|
161
|
+
throw new StructError("Not a boolean", context?.path);
|
|
162
|
+
}
|
|
163
|
+
return input;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {string | URL} [fallback]
|
|
169
|
+
* @returns {Structure<URL>}
|
|
170
|
+
*/
|
|
171
|
+
static url(fallback) {
|
|
172
|
+
const schema = { type: "string", format: "uri" };
|
|
173
|
+
if (fallback !== undefined) schema.default = fallback.toString();
|
|
174
|
+
|
|
175
|
+
// ~ make sure the fallback is valid ~
|
|
176
|
+
const url = fallback ? new URL(fallback) : undefined;
|
|
177
|
+
return new Structure(schema, (input = url, context = undefined) => {
|
|
178
|
+
if (input === undefined) {
|
|
179
|
+
throw new StructError("Missing value", context?.path);
|
|
180
|
+
}
|
|
181
|
+
if (input instanceof URL) return input;
|
|
182
|
+
if (typeof input !== "string") {
|
|
183
|
+
throw new StructError("Not a string or URL", context?.path);
|
|
184
|
+
}
|
|
185
|
+
return new URL(input);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @template {Record<string, Structure<any>>} U
|
|
191
|
+
* @param {U} fields
|
|
192
|
+
* @returns {Structure<{ [K in keyof U]: Infer<U[K]> }>}
|
|
193
|
+
*/
|
|
194
|
+
static object(fields) {
|
|
195
|
+
const schema = {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {},
|
|
198
|
+
default: {},
|
|
199
|
+
additionalProperties: false,
|
|
200
|
+
};
|
|
201
|
+
for (const [key, struct] of Object.entries(fields)) {
|
|
202
|
+
schema.properties[key] = struct.schema;
|
|
203
|
+
}
|
|
204
|
+
return new Structure(schema, (input = {}, context = undefined) => {
|
|
205
|
+
const path = context?.path ?? [];
|
|
206
|
+
if (input && typeof input !== "object") {
|
|
207
|
+
throw new StructError("Expected an object", path);
|
|
208
|
+
}
|
|
209
|
+
const output = {};
|
|
210
|
+
const errors = [];
|
|
211
|
+
for (const key in fields) {
|
|
212
|
+
const ctx = { path: [...path, key] };
|
|
213
|
+
try {
|
|
214
|
+
output[key] = fields[key].process(input[key], ctx);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
errors.push(StructError.chain(error, ctx));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (errors.length > 0) {
|
|
220
|
+
throw new StructError("Object does not match schema", path, errors);
|
|
221
|
+
}
|
|
222
|
+
return output;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* **UNSTABLE** use at your own risk
|
|
228
|
+
*
|
|
229
|
+
* @template {Structure<any>} U
|
|
230
|
+
* @param {U} struct
|
|
231
|
+
* @returns {Structure<Array<Infer<U>>>}
|
|
232
|
+
*/
|
|
233
|
+
static array(struct) {
|
|
234
|
+
const schema = {
|
|
235
|
+
type: "array",
|
|
236
|
+
items: struct.schema,
|
|
237
|
+
default: [],
|
|
238
|
+
};
|
|
239
|
+
return new Structure(schema, (input = [], context = undefined) => {
|
|
240
|
+
const path = context?.path ?? [];
|
|
241
|
+
if (!Array.isArray(input)) {
|
|
242
|
+
throw new StructError("Expected an array", path);
|
|
243
|
+
}
|
|
244
|
+
const output = [];
|
|
245
|
+
const errors = [];
|
|
246
|
+
for (let i = 0; i < input.length; i++) {
|
|
247
|
+
const ctx = { path: [...path, `${i}`] };
|
|
248
|
+
try {
|
|
249
|
+
output.push(struct.process(input[i], ctx));
|
|
250
|
+
} catch (error) {
|
|
251
|
+
errors.push(StructError.chain(error, ctx));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (errors.length > 0) {
|
|
255
|
+
throw new StructError("Array item does not match schema", path, errors);
|
|
256
|
+
}
|
|
257
|
+
return output;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|