@databehave/server 0.1.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 +28 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/admin/admin-routes.d.ts +42 -0
- package/dist/admin/admin-routes.d.ts.map +1 -0
- package/dist/admin/admin-routes.js +472 -0
- package/dist/admin/admin-routes.js.map +1 -0
- package/dist/admin/admin-types.d.ts +170 -0
- package/dist/admin/admin-types.d.ts.map +1 -0
- package/dist/admin/admin-types.js +30 -0
- package/dist/admin/admin-types.js.map +1 -0
- package/dist/admin/banner.d.ts +11 -0
- package/dist/admin/banner.d.ts.map +1 -0
- package/dist/admin/banner.js +5 -0
- package/dist/admin/banner.js.map +1 -0
- package/dist/admin/header-parser.d.ts +14 -0
- package/dist/admin/header-parser.d.ts.map +1 -0
- package/dist/admin/header-parser.js +171 -0
- package/dist/admin/header-parser.js.map +1 -0
- package/dist/admin/inject.d.ts +48 -0
- package/dist/admin/inject.d.ts.map +1 -0
- package/dist/admin/inject.js +167 -0
- package/dist/admin/inject.js.map +1 -0
- package/dist/admin/overrides-store.d.ts +18 -0
- package/dist/admin/overrides-store.d.ts.map +1 -0
- package/dist/admin/overrides-store.js +100 -0
- package/dist/admin/overrides-store.js.map +1 -0
- package/dist/admin/resolve-cors.d.ts +22 -0
- package/dist/admin/resolve-cors.d.ts.map +1 -0
- package/dist/admin/resolve-cors.js +28 -0
- package/dist/admin/resolve-cors.js.map +1 -0
- package/dist/admin/scenarios-store.d.ts +15 -0
- package/dist/admin/scenarios-store.d.ts.map +1 -0
- package/dist/admin/scenarios-store.js +109 -0
- package/dist/admin/scenarios-store.js.map +1 -0
- package/dist/admin/ui.css +2 -0
- package/dist/admin/ui.html +13 -0
- package/dist/admin/ui.js +240 -0
- package/dist/bin-helpers.d.ts +46 -0
- package/dist/bin-helpers.d.ts.map +1 -0
- package/dist/bin-helpers.js +61 -0
- package/dist/bin-helpers.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +57 -0
- package/dist/bin.js.map +1 -0
- package/dist/cors.d.ts +57 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +167 -0
- package/dist/cors.js.map +1 -0
- package/dist/define.d.ts +26 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +55 -0
- package/dist/define.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/json-config.d.ts +170 -0
- package/dist/json-config.d.ts.map +1 -0
- package/dist/json-config.js +684 -0
- package/dist/json-config.js.map +1 -0
- package/dist/jsonc.d.ts +24 -0
- package/dist/jsonc.d.ts.map +1 -0
- package/dist/jsonc.js +125 -0
- package/dist/jsonc.js.map +1 -0
- package/dist/log.d.ts +57 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +103 -0
- package/dist/log.js.map +1 -0
- package/dist/middleware/request-validation.d.ts +58 -0
- package/dist/middleware/request-validation.d.ts.map +1 -0
- package/dist/middleware/request-validation.js +328 -0
- package/dist/middleware/request-validation.js.map +1 -0
- package/dist/mock-mode.d.ts +95 -0
- package/dist/mock-mode.d.ts.map +1 -0
- package/dist/mock-mode.js +95 -0
- package/dist/mock-mode.js.map +1 -0
- package/dist/openapi/auto-schema.d.ts +83 -0
- package/dist/openapi/auto-schema.d.ts.map +1 -0
- package/dist/openapi/auto-schema.js +232 -0
- package/dist/openapi/auto-schema.js.map +1 -0
- package/dist/openapi/generate.d.ts +20 -0
- package/dist/openapi/generate.d.ts.map +1 -0
- package/dist/openapi/generate.js +121 -0
- package/dist/openapi/generate.js.map +1 -0
- package/dist/openapi/loader.d.ts +18 -0
- package/dist/openapi/loader.d.ts.map +1 -0
- package/dist/openapi/loader.js +25 -0
- package/dist/openapi/loader.js.map +1 -0
- package/dist/openapi/register.d.ts +58 -0
- package/dist/openapi/register.d.ts.map +1 -0
- package/dist/openapi/register.js +166 -0
- package/dist/openapi/register.js.map +1 -0
- package/dist/openapi/seed.d.ts +16 -0
- package/dist/openapi/seed.d.ts.map +1 -0
- package/dist/openapi/seed.js +58 -0
- package/dist/openapi/seed.js.map +1 -0
- package/dist/openapi/translate.d.ts +55 -0
- package/dist/openapi/translate.d.ts.map +1 -0
- package/dist/openapi/translate.js +319 -0
- package/dist/openapi/translate.js.map +1 -0
- package/dist/openapi/walker.d.ts +44 -0
- package/dist/openapi/walker.d.ts.map +1 -0
- package/dist/openapi/walker.js +15 -0
- package/dist/openapi/walker.js.map +1 -0
- package/dist/request.d.ts +19 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +66 -0
- package/dist/request.js.map +1 -0
- package/dist/response.d.ts +13 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +43 -0
- package/dist/response.js.map +1 -0
- package/dist/route-key.d.ts +60 -0
- package/dist/route-key.d.ts.map +1 -0
- package/dist/route-key.js +105 -0
- package/dist/route-key.js.map +1 -0
- package/dist/run.d.ts +27 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +87 -0
- package/dist/run.js.map +1 -0
- package/dist/server.d.ts +60 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +497 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +274 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/validation/validate.d.ts +38 -0
- package/dist/validation/validate.d.ts.map +1 -0
- package/dist/validation/validate.js +372 -0
- package/dist/validation/validate.js.map +1 -0
- package/docs/admin/error-mode.md +14 -0
- package/docs/admin/headers.md +63 -0
- package/docs/admin/index.md +111 -0
- package/docs/admin/overrides.md +23 -0
- package/docs/admin/rest-api.md +90 -0
- package/docs/admin/scenarios.md +28 -0
- package/docs/cli.md +76 -0
- package/docs/config/admin.md +62 -0
- package/docs/config/cors.md +72 -0
- package/docs/config/endpoints.md +162 -0
- package/docs/config/index.md +107 -0
- package/docs/config/log.md +43 -0
- package/docs/config/mock-mode.md +84 -0
- package/docs/config/openapi.md +8 -0
- package/docs/config/schema.md +34 -0
- package/docs/config/server.md +32 -0
- package/docs/config/validation.md +161 -0
- package/docs/design.md +517 -0
- package/docs/errors.md +192 -0
- package/docs/getting-started.md +146 -0
- package/docs/index.md +23 -0
- package/docs/openapi/auto-schema.md +146 -0
- package/docs/openapi/fallback.md +242 -0
- package/docs/openapi/keywords.md +78 -0
- package/docs/recipes.md +258 -0
- package/docs/stability.md +118 -0
- package/docs/troubleshooting.md +191 -0
- package/package.json +80 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
const DEPTH_CAP = 64;
|
|
2
|
+
const PATTERN_MAX_LENGTH = 1024;
|
|
3
|
+
// Maximum recursion depth for deepEqual when comparing user input against
|
|
4
|
+
// `enum` choices. Beyond this we return false (treat as not-equal — the
|
|
5
|
+
// input fails the enum check and the caller emits a 422). The cap exists
|
|
6
|
+
// to prevent stack overflow on attacker-supplied deeply-nested values.
|
|
7
|
+
const DEEP_EQUAL_DEPTH_CAP = 64;
|
|
8
|
+
const POLLUTED_KEYS = new Set([
|
|
9
|
+
'__proto__', 'constructor', 'prototype',
|
|
10
|
+
]);
|
|
11
|
+
const UNSUPPORTED_KEYWORDS = new Set([
|
|
12
|
+
'if', 'then', 'else',
|
|
13
|
+
'dependentSchemas', 'dependentRequired',
|
|
14
|
+
'unevaluatedProperties', 'unevaluatedItems',
|
|
15
|
+
'contentEncoding', 'contentMediaType',
|
|
16
|
+
'propertyNames', 'patternProperties',
|
|
17
|
+
]);
|
|
18
|
+
const FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
19
|
+
const FORMAT_DATE_TIME = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
|
|
20
|
+
const FORMAT_UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
21
|
+
// Deliberately simple sane-default email regex. RFC 5322 is far stricter
|
|
22
|
+
// and rejects edge cases real users care about; consumers who need
|
|
23
|
+
// RFC 5322 should add a `pattern` to the schema and rely on the walker's
|
|
24
|
+
// `pattern` keyword instead.
|
|
25
|
+
const FORMAT_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
26
|
+
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
27
|
+
const deepEqual = (a, b, depth = 0) => {
|
|
28
|
+
if (depth > DEEP_EQUAL_DEPTH_CAP)
|
|
29
|
+
return false;
|
|
30
|
+
if (a === b)
|
|
31
|
+
return true;
|
|
32
|
+
if (typeof a !== typeof b || a === null || b === null)
|
|
33
|
+
return false;
|
|
34
|
+
if (Array.isArray(a)) {
|
|
35
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
36
|
+
return false;
|
|
37
|
+
for (let i = 0; i < a.length; i++) {
|
|
38
|
+
if (!deepEqual(a[i], b[i], depth + 1))
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (typeof a === 'object') {
|
|
44
|
+
const ao = a;
|
|
45
|
+
const bo = b;
|
|
46
|
+
const ak = Object.keys(ao);
|
|
47
|
+
if (ak.length !== Object.keys(bo).length)
|
|
48
|
+
return false;
|
|
49
|
+
for (const k of ak) {
|
|
50
|
+
if (!Object.hasOwn(bo, k) || !deepEqual(ao[k], bo[k], depth + 1))
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
};
|
|
57
|
+
const buildErr = (ctx, message) => new Error(`@databehave/server/validation: ${message} (at ${ctx.schemaPointer})`);
|
|
58
|
+
const enterPtr = (ctx, segment) => ({
|
|
59
|
+
...ctx,
|
|
60
|
+
schemaPointer: `${ctx.schemaPointer}/${segment}`,
|
|
61
|
+
depth: ctx.depth + 1,
|
|
62
|
+
});
|
|
63
|
+
const resolveRef = (ref, ctx) => {
|
|
64
|
+
if (ref.includes('://'))
|
|
65
|
+
throw buildErr(ctx, `unsupported $ref: ${ref}`);
|
|
66
|
+
const m = /^#\/components\/schemas\/(.+)$/.exec(ref);
|
|
67
|
+
if (!m)
|
|
68
|
+
throw buildErr(ctx, `unsupported $ref: ${ref}`);
|
|
69
|
+
const name = decodeURIComponent(m[1]);
|
|
70
|
+
const target = ctx.doc.components?.schemas?.[name];
|
|
71
|
+
if (!target)
|
|
72
|
+
throw buildErr(ctx, `$ref not found: ${ref}`);
|
|
73
|
+
return target;
|
|
74
|
+
};
|
|
75
|
+
const checkFormat = (f, v) => {
|
|
76
|
+
switch (f) {
|
|
77
|
+
case 'date': return FORMAT_DATE.test(v) && !Number.isNaN(Date.parse(`${v}T00:00:00Z`));
|
|
78
|
+
case 'date-time': return FORMAT_DATE_TIME.test(v) && !Number.isNaN(Date.parse(v));
|
|
79
|
+
case 'email': return FORMAT_EMAIL.test(v);
|
|
80
|
+
case 'uuid': return FORMAT_UUID.test(v);
|
|
81
|
+
case 'uri': try {
|
|
82
|
+
new URL(v);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
default: return true;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const typeOk = (t, value) => {
|
|
92
|
+
switch (t) {
|
|
93
|
+
case 'string': return typeof value === 'string';
|
|
94
|
+
case 'integer': return typeof value === 'number' && Number.isInteger(value);
|
|
95
|
+
case 'number': return typeof value === 'number' && !Number.isNaN(value);
|
|
96
|
+
case 'boolean': return typeof value === 'boolean';
|
|
97
|
+
case 'null': return value === null;
|
|
98
|
+
case 'array': return Array.isArray(value);
|
|
99
|
+
case 'object': return isObj(value);
|
|
100
|
+
default: return true;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const NUMERIC = [
|
|
104
|
+
['minimum', (v, l) => v >= l, 'is less than minimum'],
|
|
105
|
+
['maximum', (v, l) => v <= l, 'is greater than maximum'],
|
|
106
|
+
['exclusiveMinimum', (v, l) => v > l, 'is not greater than exclusiveMinimum'],
|
|
107
|
+
['exclusiveMaximum', (v, l) => v < l, 'is not less than exclusiveMaximum'],
|
|
108
|
+
];
|
|
109
|
+
export const compileValidator = (node, doc, schemaPointer = '#') => build(node, { doc, refStack: [], depth: 0, schemaPointer });
|
|
110
|
+
const build = (node, ctx) => {
|
|
111
|
+
if (ctx.depth > DEPTH_CAP) {
|
|
112
|
+
throw buildErr(ctx, `schema nesting exceeds depth cap of ${DEPTH_CAP}`);
|
|
113
|
+
}
|
|
114
|
+
if (!isObj(node))
|
|
115
|
+
throw buildErr(ctx, 'schema node must be an object');
|
|
116
|
+
for (const key of Object.keys(node)) {
|
|
117
|
+
if (UNSUPPORTED_KEYWORDS.has(key)) {
|
|
118
|
+
throw buildErr(ctx, `unsupported JSON Schema keyword: ${key}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (typeof node.$ref === 'string') {
|
|
122
|
+
if (ctx.refStack.includes(node.$ref)) {
|
|
123
|
+
throw buildErr(ctx, `$ref cycle detected: ${[...ctx.refStack, node.$ref].join(' -> ')}`);
|
|
124
|
+
}
|
|
125
|
+
const target = resolveRef(node.$ref, ctx);
|
|
126
|
+
return build(target, {
|
|
127
|
+
doc: ctx.doc,
|
|
128
|
+
refStack: [...ctx.refStack, node.$ref],
|
|
129
|
+
depth: ctx.depth + 1,
|
|
130
|
+
schemaPointer: node.$ref,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const typeArray = Array.isArray(node.type) ? node.type : null;
|
|
134
|
+
const allowsNull = node.nullable === true ||
|
|
135
|
+
(typeArray !== null && typeArray.includes('null'));
|
|
136
|
+
const checks = [];
|
|
137
|
+
if (typeof node.type === 'string') {
|
|
138
|
+
const t = node.type;
|
|
139
|
+
checks.push((v, p) => (v === null && allowsNull) || typeOk(t, v)
|
|
140
|
+
? []
|
|
141
|
+
: [{ path: p, keyword: 'type', message: `expected ${t}` }]);
|
|
142
|
+
}
|
|
143
|
+
else if (typeArray) {
|
|
144
|
+
const nonNull = typeArray.filter((t) => t !== 'null');
|
|
145
|
+
if (nonNull.length > 0) {
|
|
146
|
+
checks.push((v, p) => {
|
|
147
|
+
if (v === null && allowsNull)
|
|
148
|
+
return [];
|
|
149
|
+
for (const t of nonNull)
|
|
150
|
+
if (typeOk(t, v))
|
|
151
|
+
return [];
|
|
152
|
+
return [{ path: p, keyword: 'type', message: `value did not match any of the allowed types: ${nonNull.join(', ')}` }];
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(node.enum)) {
|
|
157
|
+
const choices = node.enum;
|
|
158
|
+
checks.push((v, p) => choices.some((c) => deepEqual(v, c))
|
|
159
|
+
? []
|
|
160
|
+
: [{ path: p, keyword: 'enum', message: 'value is not one of the allowed enum entries' }]);
|
|
161
|
+
}
|
|
162
|
+
if (typeof node.minLength === 'number') {
|
|
163
|
+
const m = node.minLength;
|
|
164
|
+
checks.push((v, p) => typeof v === 'string' && [...v].length < m
|
|
165
|
+
? [{ path: p, keyword: 'minLength', message: `string is shorter than minLength ${m}` }]
|
|
166
|
+
: []);
|
|
167
|
+
}
|
|
168
|
+
if (typeof node.maxLength === 'number') {
|
|
169
|
+
const m = node.maxLength;
|
|
170
|
+
checks.push((v, p) => typeof v === 'string' && [...v].length > m
|
|
171
|
+
? [{ path: p, keyword: 'maxLength', message: `string is longer than maxLength ${m}` }]
|
|
172
|
+
: []);
|
|
173
|
+
}
|
|
174
|
+
if (typeof node.pattern === 'string') {
|
|
175
|
+
const pattern = node.pattern;
|
|
176
|
+
if (pattern.length > PATTERN_MAX_LENGTH) {
|
|
177
|
+
throw buildErr(ctx, `pattern length ${pattern.length} exceeds cap of ${PATTERN_MAX_LENGTH} (ReDoS guard)`);
|
|
178
|
+
}
|
|
179
|
+
// FAIL LOUD: a `pattern` without an own `maxLength` is a ReDoS
|
|
180
|
+
// footgun — even with `PATTERN_MAX_LENGTH` capping the regex itself,
|
|
181
|
+
// catastrophic-backtracking patterns can still be triggered by
|
|
182
|
+
// unbounded user input. Force schema authors to declare an upper
|
|
183
|
+
// bound at the same node where the `pattern` lives. We deliberately
|
|
184
|
+
// do NOT walk up parent schemas: the cap belongs next to the
|
|
185
|
+
// pattern, not somewhere ambient.
|
|
186
|
+
if (typeof node.maxLength !== 'number') {
|
|
187
|
+
throw buildErr(ctx, `pattern requires maxLength on the same schema (footgun: ReDoS) at ${ctx.schemaPointer}`);
|
|
188
|
+
}
|
|
189
|
+
let re;
|
|
190
|
+
try {
|
|
191
|
+
re = new RegExp(pattern);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
throw buildErr(ctx, `invalid pattern: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
}
|
|
196
|
+
checks.push((v, p) => typeof v === 'string' && !re.test(v)
|
|
197
|
+
? [{ path: p, keyword: 'pattern', message: 'string does not match the required pattern' }]
|
|
198
|
+
: []);
|
|
199
|
+
}
|
|
200
|
+
if (typeof node.format === 'string') {
|
|
201
|
+
const f = node.format;
|
|
202
|
+
if (f === 'date' || f === 'date-time' || f === 'email' || f === 'uuid' || f === 'uri') {
|
|
203
|
+
checks.push((v, p) => typeof v === 'string' && !checkFormat(f, v)
|
|
204
|
+
? [{ path: p, keyword: 'format', message: `must be a valid ${f}` }]
|
|
205
|
+
: []);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const [key, op, label] of NUMERIC) {
|
|
209
|
+
const limit = node[key];
|
|
210
|
+
if (typeof limit === 'number') {
|
|
211
|
+
checks.push((v, p) => typeof v === 'number' && !op(v, limit)
|
|
212
|
+
? [{ path: p, keyword: key, message: `value ${label} ${limit}` }]
|
|
213
|
+
: []);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (typeof node.minItems === 'number') {
|
|
217
|
+
const m = node.minItems;
|
|
218
|
+
checks.push((v, p) => Array.isArray(v) && v.length < m
|
|
219
|
+
? [{ path: p, keyword: 'minItems', message: `array has fewer than ${m} items` }]
|
|
220
|
+
: []);
|
|
221
|
+
}
|
|
222
|
+
if (typeof node.maxItems === 'number') {
|
|
223
|
+
const m = node.maxItems;
|
|
224
|
+
checks.push((v, p) => Array.isArray(v) && v.length > m
|
|
225
|
+
? [{ path: p, keyword: 'maxItems', message: `array has more than ${m} items` }]
|
|
226
|
+
: []);
|
|
227
|
+
}
|
|
228
|
+
if (node.items) {
|
|
229
|
+
const inner = build(node.items, enterPtr(ctx, 'items'));
|
|
230
|
+
checks.push((v, p) => {
|
|
231
|
+
if (!Array.isArray(v))
|
|
232
|
+
return [];
|
|
233
|
+
const out = [];
|
|
234
|
+
for (let i = 0; i < v.length; i++)
|
|
235
|
+
out.push(...inner(v[i], `${p}/${i}`));
|
|
236
|
+
return out;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const required = Array.isArray(node.required) ? node.required : null;
|
|
240
|
+
const properties = isObj(node.properties) ? node.properties : null;
|
|
241
|
+
const additional = node.additionalProperties;
|
|
242
|
+
let propValidators = null;
|
|
243
|
+
if (properties) {
|
|
244
|
+
propValidators = new Map();
|
|
245
|
+
for (const [k, v] of Object.entries(properties)) {
|
|
246
|
+
if (POLLUTED_KEYS.has(k)) {
|
|
247
|
+
throw buildErr(ctx, `forbidden property name in schema.properties: ${k}`);
|
|
248
|
+
}
|
|
249
|
+
propValidators.set(k, build(v, enterPtr(ctx, `properties/${k}`)));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
let additionalValidator = null;
|
|
253
|
+
let additionalAllowed = true;
|
|
254
|
+
if (additional === false)
|
|
255
|
+
additionalAllowed = false;
|
|
256
|
+
else if (isObj(additional)) {
|
|
257
|
+
additionalValidator = build(additional, enterPtr(ctx, 'additionalProperties'));
|
|
258
|
+
}
|
|
259
|
+
if (required || properties || additional !== undefined) {
|
|
260
|
+
checks.push((v, p) => {
|
|
261
|
+
if (!isObj(v))
|
|
262
|
+
return [];
|
|
263
|
+
const out = [];
|
|
264
|
+
const ownKeys = Object.keys(v);
|
|
265
|
+
for (const k of ownKeys) {
|
|
266
|
+
if (POLLUTED_KEYS.has(k)) {
|
|
267
|
+
out.push({ path: `${p}/${k}`, keyword: 'prototypePollution', message: 'forbidden property name (prototype pollution guard)' });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (required) {
|
|
271
|
+
for (const r of required) {
|
|
272
|
+
if (!Object.hasOwn(v, r)) {
|
|
273
|
+
out.push({ path: `${p}/${r}`, keyword: 'required', message: 'missing required property' });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
for (const k of ownKeys) {
|
|
278
|
+
if (POLLUTED_KEYS.has(k))
|
|
279
|
+
continue;
|
|
280
|
+
const child = propValidators?.get(k);
|
|
281
|
+
if (child) {
|
|
282
|
+
out.push(...child(v[k], `${p}/${k}`));
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (!additionalAllowed) {
|
|
286
|
+
out.push({ path: `${p}/${k}`, keyword: 'additionalProperties', message: 'unexpected additional property' });
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (additionalValidator)
|
|
290
|
+
out.push(...additionalValidator(v[k], `${p}/${k}`));
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (Array.isArray(node.allOf)) {
|
|
296
|
+
const subs = node.allOf.map((n, i) => build(n, enterPtr(ctx, `allOf/${i}`)));
|
|
297
|
+
checks.push((v, p) => subs.flatMap((s) => s(v, p)));
|
|
298
|
+
}
|
|
299
|
+
if (Array.isArray(node.anyOf)) {
|
|
300
|
+
const subs = node.anyOf.map((n, i) => build(n, enterPtr(ctx, `anyOf/${i}`)));
|
|
301
|
+
checks.push((v, p) => subs.some((s) => s(v, p).length === 0)
|
|
302
|
+
? []
|
|
303
|
+
: [{ path: p, keyword: 'anyOf', message: 'value did not match any of the anyOf schemas' }]);
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(node.oneOf)) {
|
|
306
|
+
const subs = node.oneOf.map((n, i) => build(n, enterPtr(ctx, `oneOf/${i}`)));
|
|
307
|
+
const disc = node.discriminator;
|
|
308
|
+
if (disc) {
|
|
309
|
+
const { propertyName, mapping } = disc;
|
|
310
|
+
const mapBuilt = new Map();
|
|
311
|
+
if (mapping) {
|
|
312
|
+
for (const [k, ref] of Object.entries(mapping)) {
|
|
313
|
+
const childCtx = enterPtr(ctx, `discriminator/mapping/${k}`);
|
|
314
|
+
// Defensive: any structural $ref cycle is caught at the
|
|
315
|
+
// top-level $ref descent above before the discriminator
|
|
316
|
+
// block runs.
|
|
317
|
+
/* c8 ignore start */
|
|
318
|
+
if (childCtx.refStack.includes(ref)) {
|
|
319
|
+
throw buildErr(childCtx, `$ref cycle detected: ${ref}`);
|
|
320
|
+
}
|
|
321
|
+
/* c8 ignore stop */
|
|
322
|
+
const target = resolveRef(ref, childCtx);
|
|
323
|
+
mapBuilt.set(k, build(target, {
|
|
324
|
+
...childCtx,
|
|
325
|
+
refStack: [...childCtx.refStack, ref],
|
|
326
|
+
schemaPointer: ref,
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
checks.push((v, p) => {
|
|
331
|
+
if (!isObj(v)) {
|
|
332
|
+
return [{ path: p, keyword: 'discriminator', message: `value must be an object to use discriminator '${propertyName}'` }];
|
|
333
|
+
}
|
|
334
|
+
const tag = v[propertyName];
|
|
335
|
+
if (typeof tag !== 'string') {
|
|
336
|
+
return [{ path: `${p}/${propertyName}`, keyword: 'discriminator', message: `discriminator '${propertyName}' missing or not a string` }];
|
|
337
|
+
}
|
|
338
|
+
const sub = mapBuilt.get(tag);
|
|
339
|
+
if (sub)
|
|
340
|
+
return sub(v, p);
|
|
341
|
+
const matches = subs.filter((s) => s(v, p).length === 0);
|
|
342
|
+
if (matches.length === 1)
|
|
343
|
+
return [];
|
|
344
|
+
// Never echo the user-supplied tag — it is unsanitised input
|
|
345
|
+
// and ends up rendered in client error toasts / logs.
|
|
346
|
+
return [{ path: `${p}/${propertyName}`, keyword: 'discriminator', message: `unknown discriminator value '[redacted]'` }];
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
checks.push((v, p) => {
|
|
351
|
+
let passing = 0;
|
|
352
|
+
for (const s of subs)
|
|
353
|
+
if (s(v, p).length === 0)
|
|
354
|
+
passing++;
|
|
355
|
+
if (passing === 1)
|
|
356
|
+
return [];
|
|
357
|
+
if (passing === 0)
|
|
358
|
+
return [{ path: p, keyword: 'oneOf', message: 'value did not match any of the oneOf schemas' }];
|
|
359
|
+
return [{ path: p, keyword: 'oneOf', message: `value matched ${passing} oneOf schemas (must match exactly one)` }];
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return (value, pointer) => {
|
|
364
|
+
if (value === null && allowsNull)
|
|
365
|
+
return [];
|
|
366
|
+
const out = [];
|
|
367
|
+
for (const c of checks)
|
|
368
|
+
out.push(...c(value, pointer));
|
|
369
|
+
return out;
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validation/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# `ErrorMode` reference
|
|
2
|
+
|
|
3
|
+
| Kind | Minimal body | Description |
|
|
4
|
+
| - | - | - |
|
|
5
|
+
| `http-status` | `{"kind":"http-status","status":500}` | Force the response status. Body from OAS schema if any. |
|
|
6
|
+
| `business-failure` | `{"kind":"business-failure","message":"…","extra":{ }}` | 2xx envelope `{success:false, message, …extra}`. |
|
|
7
|
+
| `custom-body` | `{"kind":"custom-body","body":{…},"status":200,"contentType":"application/json"}` | Arbitrary body / status / content-type.` |
|
|
8
|
+
| `empty-body` | `{"kind":"empty-body","status":204}` | Empty response body. |
|
|
9
|
+
| `malformed-json` | `{"kind":"malformed-json","status":200}` | Body is literally `{` — clients see `JSON.parse` failure. |
|
|
10
|
+
| `delay` | `{"kind":"delay","ms":2000,"then":{"kind":"http-status","status":500}}` | Sleep `ms`, then run `then` (or passthrough if omitted). |
|
|
11
|
+
| `hang` | `{"kind":"hang"}` | Never resolve — exercises client timeout / hang detection. |
|
|
12
|
+
| `destroy` | `{"kind":"destroy"}` | Drop the socket. Gated by `allowDestroy: true`. |
|
|
13
|
+
|
|
14
|
+
`delay.then` cannot itself be `delay`, `hang`, or `destroy`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# `x-mock-*` header reference
|
|
2
|
+
|
|
3
|
+
Per-request overrides. A header beats every sticky override and is
|
|
4
|
+
discarded after the request. Conflicting terminal headers, an invalid
|
|
5
|
+
base64 payload, or invalid JSON return **HTTP 400** with
|
|
6
|
+
`{error: "invalid …: <reason>"}` so the typo is loud.
|
|
7
|
+
|
|
8
|
+
| Header | ErrorMode kind | Example value |
|
|
9
|
+
| ------------------------------------------------ | ---------------------- | -------------------------------------- |
|
|
10
|
+
| `x-mock-status` | `http-status` | `500` |
|
|
11
|
+
| `x-mock-business-failure` | `business-failure` | `update conflict` |
|
|
12
|
+
| `x-mock-business-failure-b64` | `business-failure` | `5pON5L2c44GM…` (base64 of UTF-8) |
|
|
13
|
+
| `x-mock-business-failure-extra` | merged into `extra` | base64-of-JSON object |
|
|
14
|
+
| `x-mock-body` (+ `-status`, `-content-type`) | `custom-body` | base64 JSON |
|
|
15
|
+
| `x-mock-empty` (+ `-status`) | `empty-body` | `1` |
|
|
16
|
+
| `x-mock-malformed` (+ `-status`) | `malformed-json` | `1` |
|
|
17
|
+
| `x-mock-delay` | `delay` (wraps others) | `2000` |
|
|
18
|
+
| `x-mock-hang` | `hang` | `1` |
|
|
19
|
+
| `x-mock-destroy` | `destroy` | `1` (rejected with 503 if `allowDestroy:false`) |
|
|
20
|
+
|
|
21
|
+
Base64 is standard (not URL-safe). Non-ASCII messages must use the
|
|
22
|
+
`-b64` variant — HTTP headers reject byte values > 255.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# unicode business-failure
|
|
26
|
+
MSG_B64=$(printf 'cannot copy: record was modified during operation — café' | base64)
|
|
27
|
+
curl -sS -X POST http://127.0.0.1:8000/api/v1/widgets/copy \
|
|
28
|
+
-H "x-mock-business-failure-b64: $MSG_B64" \
|
|
29
|
+
-H 'content-type: application/json' -d '{}'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## `x-mock-injected` response header
|
|
33
|
+
|
|
34
|
+
Whenever the admin inject hook overrides a response — whether from
|
|
35
|
+
an `x-mock-*` request header or a sticky override — the response is
|
|
36
|
+
tagged with `x-mock-injected: <kind>:<source>`:
|
|
37
|
+
|
|
38
|
+
| `kind` | Where it came from |
|
|
39
|
+
| ------ | --- |
|
|
40
|
+
| `http-status`, `business-failure`, `custom-body`, `empty-body`, `malformed-json`, `delay`, `hang`, `destroy` | The ErrorMode that fired. |
|
|
41
|
+
| `header-error` | Header parsing failed; the request was rejected with 400. |
|
|
42
|
+
|
|
43
|
+
| `source` | Meaning |
|
|
44
|
+
| -------- | --- |
|
|
45
|
+
| `header` | The override came from an `x-mock-*` request header. |
|
|
46
|
+
| `sticky` | The override came from a `POST /overrides` sticky entry. |
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
x-mock-injected: http-status:header
|
|
52
|
+
x-mock-injected: business-failure:sticky
|
|
53
|
+
x-mock-injected: header-error
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This is the single observability signal that proves an override
|
|
57
|
+
fired — without it, a 500 from the mock server is indistinguishable
|
|
58
|
+
from a 500 the real handler would have produced. Tests assert on
|
|
59
|
+
this header to verify a mock pathway, and dashboards filter by it
|
|
60
|
+
to separate injected failures from organic ones.
|
|
61
|
+
|
|
62
|
+
`header-error` carries no `:source` suffix because the rejection
|
|
63
|
+
happens before the override taxonomy applies.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Admin mode
|
|
2
|
+
|
|
3
|
+
Opt-in error-injection panel + REST API + per-request `x-mock-*` headers,
|
|
4
|
+
mounted under a configurable base path (default `/databehave`). All routes,
|
|
5
|
+
headers, and behaviours below are served **only** when
|
|
6
|
+
`admin.enabled: true`. With the flag off, the kit behaves exactly as
|
|
7
|
+
the pre-`admin` versions did — no admin routes, no header parsing, no
|
|
8
|
+
overrides store.
|
|
9
|
+
|
|
10
|
+
For the `admin` config schema (`enabled`, `path`, `bind`, `cors`,
|
|
11
|
+
`allowDestroy`, `scenariosDir`) see
|
|
12
|
+
[../config/admin.md](../config/admin.md).
|
|
13
|
+
|
|
14
|
+
## Sub-pages
|
|
15
|
+
|
|
16
|
+
- [rest-api.md](./rest-api.md) — every `__databehave/*` endpoint.
|
|
17
|
+
- [headers.md](./headers.md) — every `x-mock-*` request header.
|
|
18
|
+
- [overrides.md](./overrides.md) — sticky overrides, matchers, resolution priority.
|
|
19
|
+
- [scenarios.md](./scenarios.md) — scenario file format and lifecycle.
|
|
20
|
+
- [error-mode.md](./error-mode.md) — `ErrorMode` reference.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```jsonc
|
|
25
|
+
// databehave.jsonc
|
|
26
|
+
{
|
|
27
|
+
"openapi": "./openapi.json",
|
|
28
|
+
"basePath": "/api/v1",
|
|
29
|
+
"endpoints": { /* ... */ },
|
|
30
|
+
"admin": { "enabled": true }
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx @databehave/server databehave.jsonc
|
|
36
|
+
# stdout: [@databehave/server] admin panel ready at http://127.0.0.1:8000/databehave (dev mock — disable in production)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Point a browser at `http://127.0.0.1:8000/databehave`.
|
|
40
|
+
|
|
41
|
+
Programmatic equivalent:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createServer } from '@databehave/server'
|
|
45
|
+
|
|
46
|
+
const server = createServer({
|
|
47
|
+
routes: { 'GET /api/v1/ping': () => ({ json: { ok: true } }) },
|
|
48
|
+
admin: { enabled: true },
|
|
49
|
+
})
|
|
50
|
+
await server.listen({ port: 8000, host: '127.0.0.1' })
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## UI overview
|
|
54
|
+
|
|
55
|
+
The UI is shipped as `dist/admin/{ui.html, ui.js, ui.css}` and rendered
|
|
56
|
+
with native Web Components — zero runtime framework, no CDN dependency.
|
|
57
|
+
English-only.
|
|
58
|
+
|
|
59
|
+
- **Endpoint list** — populated from `GET {path}/openapi-routes`, grouped by
|
|
60
|
+
OpenAPI tag, filterable. Each row opens an inject panel that pre-fills
|
|
61
|
+
from any existing sticky override targeting the same route.
|
|
62
|
+
- **Global override modal** — top-bar button. Owns `kind: 'global'`
|
|
63
|
+
overrides (the per-endpoint inject panel only offers `exact` and `path`
|
|
64
|
+
scopes).
|
|
65
|
+
- **Active overrides side panel** — right side. Global rows are tagged
|
|
66
|
+
`GLOBAL` and float above `path` / `exact` rows. A floating bottom-right
|
|
67
|
+
pill shows the active count.
|
|
68
|
+
- **Try-it-out** — runs the live request through the dispatcher so the
|
|
69
|
+
injected override actually applies. The response viewer renders the
|
|
70
|
+
body as a collapsible **schema tree** for JSON responses (each
|
|
71
|
+
property is a fold-able row) and falls back to a raw text panel for
|
|
72
|
+
non-JSON bodies.
|
|
73
|
+
- **Scenarios menu** — list, save (snapshot of the current overrides),
|
|
74
|
+
load, and delete named scenarios from disk.
|
|
75
|
+
- **Toasts** — successful saves, deletes, and override-create actions
|
|
76
|
+
fire a top-right toast (`success` / `error` / `info` variants);
|
|
77
|
+
failures show the REST [error envelope](./rest-api.md#error-envelope)
|
|
78
|
+
detail verbatim.
|
|
79
|
+
- **Keyboard** — `/` and `Cmd/Ctrl+K` focus the route filter, `Esc`
|
|
80
|
+
closes modals, `?` shows the help overlay.
|
|
81
|
+
|
|
82
|
+
### Non-default `admin.path`
|
|
83
|
+
|
|
84
|
+
The UI is path-agnostic. When `admin.path` is set to anything
|
|
85
|
+
other than the default `/databehave`, the boot-time HTML injects a
|
|
86
|
+
`window.__DATABEHAVE_BASE__ = '<path>'` shim before `ui.js` runs.
|
|
87
|
+
The UI reads that constant for every REST call so the panel works
|
|
88
|
+
unchanged behind a reverse proxy or a custom mount point.
|
|
89
|
+
|
|
90
|
+
If you load the panel through a build of your own (rare), set
|
|
91
|
+
`window.__DATABEHAVE_BASE__` yourself before importing
|
|
92
|
+
`ui.js` — the script falls back to `/databehave` only when the
|
|
93
|
+
constant is absent.
|
|
94
|
+
|
|
95
|
+
## Security notes
|
|
96
|
+
|
|
97
|
+
`admin` is a developer tool — not a hardened endpoint. Do not expose
|
|
98
|
+
the admin panel to untrusted networks.
|
|
99
|
+
|
|
100
|
+
- **Startup notice.** Every enabled boot writes
|
|
101
|
+
`admin panel ready at http://…/databehave (dev mock — disable in production)` to stdout. Misconfiguration is visible.
|
|
102
|
+
- **`bind: 'loopback-only'` (default).** The CLI exits 1 (and
|
|
103
|
+
`server.listen()` rejects) when the configured host is not a loopback
|
|
104
|
+
address. Set `bind: 'any'` to opt out — only in trusted environments.
|
|
105
|
+
- **`cors: 'auto'` (default).** Returns `Access-Control-Allow-Origin: *`
|
|
106
|
+
on loopback (so a localhost bookmarklet works) and switches to
|
|
107
|
+
`same-origin` (no CORS headers added) when `bind: 'any'`. Override with
|
|
108
|
+
`'any'`, `'same-origin'`, or `{ origin }`.
|
|
109
|
+
- **`allowDestroy: true` (default).** Honours `x-mock-destroy` and the
|
|
110
|
+
matching `destroy` ErrorMode. Set `false` to reject those requests with
|
|
111
|
+
HTTP 503 instead.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Overrides
|
|
2
|
+
|
|
3
|
+
## Matchers
|
|
4
|
+
|
|
5
|
+
```jsonc
|
|
6
|
+
{ "kind": "exact", "method": "POST", "path": "/api/v1/example" } // METHOD + path
|
|
7
|
+
{ "kind": "path", "path": "/api/v1/example" } // any method
|
|
8
|
+
{ "kind": "global" } // every route
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Resolution priority (first match wins): `exact` → `path` → `global`.
|
|
12
|
+
|
|
13
|
+
## Sticky override resolution
|
|
14
|
+
|
|
15
|
+
For each request, the dispatcher consults sources in this order — first
|
|
16
|
+
hit wins:
|
|
17
|
+
|
|
18
|
+
1. `x-mock-*` request headers (per-request, never stored).
|
|
19
|
+
2. Sticky `exact` override (METHOD + path).
|
|
20
|
+
3. Sticky `path` override (any method).
|
|
21
|
+
4. Sticky `global` override.
|
|
22
|
+
5. Existing `mockMode` resolution (see [../config/mock-mode.md](../config/mock-mode.md)).
|
|
23
|
+
6. Passthrough — the real handler runs.
|