@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,328 @@
|
|
|
1
|
+
import { matchPattern, parseRoutePattern } from '../route-key.js';
|
|
2
|
+
import { compileValidator } from '../validation/validate.js';
|
|
3
|
+
const PROBLEM_TYPE_BASE = 'https://github.com/ochairo/databehave/blob/main/docs/errors';
|
|
4
|
+
const PROBLEM_TYPE_SLUG = {
|
|
5
|
+
400: 'malformed-body',
|
|
6
|
+
401: 'unauthorized',
|
|
7
|
+
413: 'payload-too-large',
|
|
8
|
+
415: 'unsupported-media-type',
|
|
9
|
+
422: 'request-validation',
|
|
10
|
+
};
|
|
11
|
+
const problemType = (status) => `${PROBLEM_TYPE_BASE}/${PROBLEM_TYPE_SLUG[status] ?? 'request-validation'}.md`;
|
|
12
|
+
const HTTP_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace']);
|
|
13
|
+
// Default cap on request-body size (bytes). Matches the express
|
|
14
|
+
// `body-parser` default; raise via `validation.maxBodyBytes` only if
|
|
15
|
+
// the spec genuinely demands large bodies.
|
|
16
|
+
const DEFAULT_MAX_BODY_BYTES = 102400;
|
|
17
|
+
const STATUS_TITLES = {
|
|
18
|
+
400: 'Malformed request',
|
|
19
|
+
401: 'Unauthorized',
|
|
20
|
+
413: 'Payload too large',
|
|
21
|
+
415: 'Unsupported media type',
|
|
22
|
+
422: 'Request validation failed',
|
|
23
|
+
};
|
|
24
|
+
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
25
|
+
const oasPathToInternal = (p) => p
|
|
26
|
+
.split('/')
|
|
27
|
+
.map((s) => (s.startsWith('{') && s.endsWith('}') ? `:${s.slice(1, -1)}` : s))
|
|
28
|
+
.join('/');
|
|
29
|
+
const resolveParam = (raw, doc) => {
|
|
30
|
+
if (!isObj(raw))
|
|
31
|
+
return null;
|
|
32
|
+
let node = raw;
|
|
33
|
+
if (typeof node.$ref === 'string') {
|
|
34
|
+
const m = /^#\/components\/parameters\/(.+)$/.exec(node.$ref);
|
|
35
|
+
if (!m)
|
|
36
|
+
return null;
|
|
37
|
+
const docAny = doc;
|
|
38
|
+
const target = docAny.components?.parameters?.[decodeURIComponent(m[1])];
|
|
39
|
+
if (!isObj(target))
|
|
40
|
+
return null;
|
|
41
|
+
node = target;
|
|
42
|
+
}
|
|
43
|
+
if (typeof node.in !== 'string' || typeof node.name !== 'string')
|
|
44
|
+
return null;
|
|
45
|
+
return node;
|
|
46
|
+
};
|
|
47
|
+
export const createRequestValidationContext = (oas, options = {}) => {
|
|
48
|
+
const doc = oas;
|
|
49
|
+
const schemes = new Map();
|
|
50
|
+
for (const [name, scheme] of Object.entries(doc.components?.securitySchemes ?? {})) {
|
|
51
|
+
if (isObj(scheme) && typeof scheme.type === 'string') {
|
|
52
|
+
schemes.set(name, scheme);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const globalSecurity = doc.security ?? [];
|
|
56
|
+
const routes = [];
|
|
57
|
+
for (const [oasPath, methods] of Object.entries(doc.paths ?? {})) {
|
|
58
|
+
if (!isObj(methods))
|
|
59
|
+
continue;
|
|
60
|
+
let pattern;
|
|
61
|
+
try {
|
|
62
|
+
pattern = parseRoutePattern(oasPathToInternal(oasPath));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const pathLevelParams = Array.isArray(methods.parameters)
|
|
68
|
+
? methods.parameters
|
|
69
|
+
: [];
|
|
70
|
+
for (const [methodName, op] of Object.entries(methods)) {
|
|
71
|
+
if (methodName === 'parameters' || !HTTP_METHODS.has(methodName))
|
|
72
|
+
continue;
|
|
73
|
+
if (!isObj(op))
|
|
74
|
+
continue;
|
|
75
|
+
const opNode = op;
|
|
76
|
+
const params = [];
|
|
77
|
+
const rawParams = [...pathLevelParams, ...(opNode.parameters ?? [])];
|
|
78
|
+
for (const raw of rawParams) {
|
|
79
|
+
const p = resolveParam(raw, doc);
|
|
80
|
+
if (!p || p.in === 'cookie')
|
|
81
|
+
continue;
|
|
82
|
+
const schema = (p.schema ?? {});
|
|
83
|
+
const validator = compileValidator(schema, doc, `#/paths/${oasPath}/${methodName}/parameters/${p.name}`);
|
|
84
|
+
params.push({
|
|
85
|
+
name: p.name,
|
|
86
|
+
in: p.in,
|
|
87
|
+
required: p.required === true || p.in === 'path',
|
|
88
|
+
type: typeof schema.type === 'string' ? schema.type : null,
|
|
89
|
+
validator,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const requestBody = opNode.requestBody;
|
|
93
|
+
const content = requestBody?.content;
|
|
94
|
+
const declaredTypes = content ? Object.keys(content) : [];
|
|
95
|
+
const hasJsonBody = declaredTypes.length > 0;
|
|
96
|
+
const bodyOnlyJson = hasJsonBody &&
|
|
97
|
+
declaredTypes.every((c) => /^application\/json(\b|;)/i.test(c));
|
|
98
|
+
const jsonSchema = content?.['application/json']?.schema;
|
|
99
|
+
const bodyValidator = jsonSchema
|
|
100
|
+
? compileValidator(jsonSchema, doc, `#/paths/${oasPath}/${methodName}/requestBody`)
|
|
101
|
+
: null;
|
|
102
|
+
routes.push({
|
|
103
|
+
method: methodName.toUpperCase(),
|
|
104
|
+
pattern,
|
|
105
|
+
hasJsonBody,
|
|
106
|
+
bodyOnlyJson,
|
|
107
|
+
bodyRequired: requestBody?.required === true,
|
|
108
|
+
bodyValidator,
|
|
109
|
+
params,
|
|
110
|
+
security: opNode.security ?? globalSecurity,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
routes,
|
|
116
|
+
schemes,
|
|
117
|
+
maxBodyBytes: typeof options.maxBodyBytes === 'number' && options.maxBodyBytes > 0
|
|
118
|
+
? options.maxBodyBytes
|
|
119
|
+
: DEFAULT_MAX_BODY_BYTES,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const coerce = (raw, type) => {
|
|
123
|
+
if (type === 'integer') {
|
|
124
|
+
if (!/^-?\d+$/.test(raw))
|
|
125
|
+
return raw;
|
|
126
|
+
const n = Number(raw);
|
|
127
|
+
return Number.isFinite(n) ? n : raw;
|
|
128
|
+
}
|
|
129
|
+
if (type === 'number') {
|
|
130
|
+
if (!/^-?\d+(?:\.\d+)?$/.test(raw))
|
|
131
|
+
return raw;
|
|
132
|
+
const n = Number(raw);
|
|
133
|
+
return Number.isFinite(n) ? n : raw;
|
|
134
|
+
}
|
|
135
|
+
if (type === 'boolean') {
|
|
136
|
+
if (raw === 'true')
|
|
137
|
+
return true;
|
|
138
|
+
if (raw === 'false')
|
|
139
|
+
return false;
|
|
140
|
+
return raw;
|
|
141
|
+
}
|
|
142
|
+
return raw;
|
|
143
|
+
};
|
|
144
|
+
const isSecuritySatisfied = (req, ctx, headers, url) => {
|
|
145
|
+
const names = Object.keys(req);
|
|
146
|
+
if (names.length === 0)
|
|
147
|
+
return true;
|
|
148
|
+
return names.every((name) => {
|
|
149
|
+
const scheme = ctx.schemes.get(name);
|
|
150
|
+
if (!scheme)
|
|
151
|
+
return false;
|
|
152
|
+
if (scheme.type === 'http') {
|
|
153
|
+
const auth = headers.get('authorization') ?? '';
|
|
154
|
+
const want = (scheme.scheme ?? 'bearer').toLowerCase();
|
|
155
|
+
return new RegExp(`^${want}\\s+`, 'i').test(auth);
|
|
156
|
+
}
|
|
157
|
+
if (scheme.type === 'apiKey') {
|
|
158
|
+
if (scheme.in === 'header' && scheme.name) {
|
|
159
|
+
return headers.has(scheme.name.toLowerCase());
|
|
160
|
+
}
|
|
161
|
+
if (scheme.in === 'query' && scheme.name) {
|
|
162
|
+
return url.searchParams.has(scheme.name);
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
// oauth2 / openIdConnect: presence-only check; full token validation
|
|
167
|
+
// (introspection, signature verification) is out of scope for a
|
|
168
|
+
// zero-dep walker. Consumers needing it should layer it via
|
|
169
|
+
// `hooks.onRequest`.
|
|
170
|
+
return headers.has('authorization');
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
const buildAuthChallenge = (reqs, ctx) => {
|
|
174
|
+
for (const r of reqs) {
|
|
175
|
+
for (const name of Object.keys(r)) {
|
|
176
|
+
const scheme = ctx.schemes.get(name);
|
|
177
|
+
if (scheme?.type === 'http') {
|
|
178
|
+
const s = (scheme.scheme ?? 'bearer').replace(/^./, (c) => c.toUpperCase());
|
|
179
|
+
return { 'www-authenticate': `${s} realm="api"` };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { 'www-authenticate': 'Bearer realm="api"' };
|
|
184
|
+
};
|
|
185
|
+
const problem = (status, detail, violations = [], extraHeaders = {}) => {
|
|
186
|
+
const body = {
|
|
187
|
+
type: problemType(status),
|
|
188
|
+
title: STATUS_TITLES[status] ?? 'Request validation failed',
|
|
189
|
+
status,
|
|
190
|
+
detail,
|
|
191
|
+
};
|
|
192
|
+
if (violations.length > 0)
|
|
193
|
+
body.violations = violations;
|
|
194
|
+
return new Response(JSON.stringify(body), {
|
|
195
|
+
status,
|
|
196
|
+
headers: {
|
|
197
|
+
'content-type': 'application/problem+json',
|
|
198
|
+
...extraHeaders,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
export const validateRequest = async (req, ctx) => {
|
|
203
|
+
const url = new URL(req.url);
|
|
204
|
+
const method = req.method.toUpperCase();
|
|
205
|
+
let matched = null;
|
|
206
|
+
for (const r of ctx.routes) {
|
|
207
|
+
if (r.method !== method)
|
|
208
|
+
continue;
|
|
209
|
+
const params = matchPattern(r.pattern, url.pathname);
|
|
210
|
+
if (params) {
|
|
211
|
+
matched = { route: r, params };
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Unmatched paths fall through — they are either hand-written routes
|
|
216
|
+
// outside the OAS document or genuine 404s. Either way the validator
|
|
217
|
+
// has no contract to enforce.
|
|
218
|
+
if (!matched)
|
|
219
|
+
return null;
|
|
220
|
+
const { route, params } = matched;
|
|
221
|
+
const headers = new Map();
|
|
222
|
+
req.headers.forEach((v, k) => headers.set(k.toLowerCase(), v));
|
|
223
|
+
// 1) Security gate.
|
|
224
|
+
if (route.security.length > 0) {
|
|
225
|
+
const ok = route.security.some((sr) => isSecuritySatisfied(sr, ctx, headers, url));
|
|
226
|
+
if (!ok) {
|
|
227
|
+
return problem(401, 'missing or invalid credentials', [], buildAuthChallenge(route.security, ctx));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const hasBodyMethod = method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';
|
|
231
|
+
const contentType = (headers.get('content-type') ?? '').split(';')[0]?.trim().toLowerCase() ?? '';
|
|
232
|
+
// 2) Content-Type guard for routes that declare a JSON-only requestBody.
|
|
233
|
+
if (hasBodyMethod && route.bodyOnlyJson && contentType && contentType !== 'application/json') {
|
|
234
|
+
return problem(415, `unsupported media type '${contentType}' (expected application/json)`);
|
|
235
|
+
}
|
|
236
|
+
const violations = [];
|
|
237
|
+
// 3) Body parse + validate.
|
|
238
|
+
if (hasBodyMethod && route.bodyValidator) {
|
|
239
|
+
let raw = '';
|
|
240
|
+
try {
|
|
241
|
+
raw = await req.clone().text();
|
|
242
|
+
/* c8 ignore start — `Request#clone().text()` only rejects on stream
|
|
243
|
+
I/O errors which the in-process unit harness cannot trigger. */
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
raw = '';
|
|
247
|
+
}
|
|
248
|
+
/* c8 ignore stop */
|
|
249
|
+
if (raw.length === 0) {
|
|
250
|
+
if (route.bodyRequired) {
|
|
251
|
+
return problem(422, 'request body is required', [
|
|
252
|
+
{ path: '/body', keyword: 'required', message: 'missing required body' },
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// Cap pre-parse body size (UTF-8 byte length, not JS string length).
|
|
258
|
+
// Default matches express body-parser; raise via
|
|
259
|
+
// `validation.maxBodyBytes` only if the spec demands large bodies.
|
|
260
|
+
const byteLen = Buffer.byteLength(raw, 'utf8');
|
|
261
|
+
if (byteLen > ctx.maxBodyBytes) {
|
|
262
|
+
return problem(413, `request body exceeds maxBodyBytes (${ctx.maxBodyBytes})`);
|
|
263
|
+
}
|
|
264
|
+
let parsed;
|
|
265
|
+
try {
|
|
266
|
+
parsed = JSON.parse(raw);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return problem(400, 'malformed JSON body');
|
|
270
|
+
}
|
|
271
|
+
violations.push(...route.bodyValidator(parsed, '/body'));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// 4) Param validation (with type coercion for query/path/header).
|
|
275
|
+
for (const p of route.params) {
|
|
276
|
+
if (p.in === 'query') {
|
|
277
|
+
const raw = url.searchParams.get(p.name);
|
|
278
|
+
if (raw === null) {
|
|
279
|
+
if (p.required) {
|
|
280
|
+
violations.push({
|
|
281
|
+
path: `/query/${p.name}`,
|
|
282
|
+
keyword: 'required',
|
|
283
|
+
message: 'missing required query parameter',
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
violations.push(...p.validator(coerce(raw, p.type), `/query/${p.name}`));
|
|
289
|
+
}
|
|
290
|
+
else if (p.in === 'path') {
|
|
291
|
+
const raw = params[p.name];
|
|
292
|
+
// Defensive: `matchPattern` always populates every named path
|
|
293
|
+
// segment when the route matches, so a missing path param is
|
|
294
|
+
// unreachable in practice. Kept as a guard against future
|
|
295
|
+
// refactors that loosen the matcher contract.
|
|
296
|
+
/* c8 ignore start */
|
|
297
|
+
if (raw === undefined) {
|
|
298
|
+
violations.push({
|
|
299
|
+
path: `/path/${p.name}`,
|
|
300
|
+
keyword: 'required',
|
|
301
|
+
message: 'missing required path parameter',
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
/* c8 ignore stop */
|
|
306
|
+
violations.push(...p.validator(coerce(raw, p.type), `/path/${p.name}`));
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
const raw = headers.get(p.name.toLowerCase());
|
|
310
|
+
if (raw === undefined) {
|
|
311
|
+
if (p.required) {
|
|
312
|
+
violations.push({
|
|
313
|
+
path: `/header/${p.name}`,
|
|
314
|
+
keyword: 'required',
|
|
315
|
+
message: 'missing required header',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
violations.push(...p.validator(coerce(raw, p.type), `/header/${p.name}`));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (violations.length > 0) {
|
|
324
|
+
return problem(422, `request validation failed (${violations.length} violation${violations.length === 1 ? '' : 's'})`, violations);
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
};
|
|
328
|
+
//# sourceMappingURL=request-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-validation.js","sourceRoot":"","sources":["../../src/middleware/request-validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in mock-mode middleware.
|
|
3
|
+
*
|
|
4
|
+
* Selects an HTTP *status* per request and either short-circuits the
|
|
5
|
+
* handler with a synthesized response of that status or lets it
|
|
6
|
+
* through. All configuration is declarative — driven entirely by
|
|
7
|
+
* `mockMode` in `databehave.json`.
|
|
8
|
+
*
|
|
9
|
+
* Resolution order per request (first hit wins):
|
|
10
|
+
* 1. `pathOverrides["<METHOD> <path>"]`
|
|
11
|
+
* 2. `pathOverrides["<path>"]`
|
|
12
|
+
* 3. `defaultStatus`
|
|
13
|
+
* 4. passthrough (real handler runs)
|
|
14
|
+
*
|
|
15
|
+
* Body resolution for a non-passthrough status N:
|
|
16
|
+
* 1. status === 204 → empty body
|
|
17
|
+
* 2. `bodyResolver(method, path, N)` returns a body
|
|
18
|
+
* (typically OpenAPI `responses[N]` schema mock)
|
|
19
|
+
* 3. fallback envelope `{ error: true, status: N }`
|
|
20
|
+
*
|
|
21
|
+
* The header `x-mock-status` is always tagged on the final response
|
|
22
|
+
* (configurable via `header`). Health paths bypass everything.
|
|
23
|
+
*/
|
|
24
|
+
import type { MockRequest, MockResponse } from './types.js';
|
|
25
|
+
export type BodyResolver = (method: string, path: string, status: number) => unknown | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Minimal logger interface (compatible with the host server's logger
|
|
28
|
+
* and with `console`). Only `warn` is required by mock-mode today; the
|
|
29
|
+
* shape is kept narrow so callers can pass an existing logger without
|
|
30
|
+
* adapter glue.
|
|
31
|
+
*/
|
|
32
|
+
export interface MockModeLogger {
|
|
33
|
+
warn(message: string): void;
|
|
34
|
+
}
|
|
35
|
+
export interface MockModeConfig {
|
|
36
|
+
/** Master switch. When `false` or absent, the feature is disabled. */
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/** MockResponse header that tags every response with the resolved status. */
|
|
39
|
+
header?: string;
|
|
40
|
+
/**
|
|
41
|
+
* When `true`, an incoming request may force the mocked status by
|
|
42
|
+
* sending the same header (default `x-mock-status: 500`). Disabled
|
|
43
|
+
* by default so production-style mock servers don't expose status
|
|
44
|
+
* injection to arbitrary clients. Resolution becomes:
|
|
45
|
+
* 1. request `header` value (when `allowHeaderOverride === true`)
|
|
46
|
+
* 2. `pathOverrides["<METHOD> <path>"]`
|
|
47
|
+
* 3. `pathOverrides["<path>"]`
|
|
48
|
+
* 4. `defaultStatus`
|
|
49
|
+
* 5. passthrough
|
|
50
|
+
*/
|
|
51
|
+
allowHeaderOverride?: boolean;
|
|
52
|
+
/** Paths exempt from status injection (typically health probes). */
|
|
53
|
+
healthPaths?: readonly string[];
|
|
54
|
+
/**
|
|
55
|
+
* Status applied when no `pathOverrides` entry matches. Omit (or set
|
|
56
|
+
* `undefined`) for passthrough (real handler runs). Set to an error
|
|
57
|
+
* code (e.g. `500`) to force every route to that status by default.
|
|
58
|
+
*/
|
|
59
|
+
defaultStatus?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Per-route status overrides. Keys use one of:
|
|
62
|
+
* - `"<METHOD> <path>"` (e.g. `"GET /api/v1/x"`) — method-scoped
|
|
63
|
+
* - `"<path>"` (e.g. `"/api/v1/x"`) — any method
|
|
64
|
+
* Paths must be absolute (start with `/`). Values are HTTP status codes.
|
|
65
|
+
*/
|
|
66
|
+
pathOverrides?: Record<string, number>;
|
|
67
|
+
/**
|
|
68
|
+
* Logger used for non-fatal diagnostics (e.g. out-of-range header
|
|
69
|
+
* overrides). Defaults to the global `console`. Pass the host
|
|
70
|
+
* application's logger to keep mock-server output in one place
|
|
71
|
+
* instead of leaking to stdout/stderr.
|
|
72
|
+
*/
|
|
73
|
+
logger?: MockModeLogger;
|
|
74
|
+
}
|
|
75
|
+
export interface MockModeHooks {
|
|
76
|
+
onRequest: (req: MockRequest) => MockResponse | undefined;
|
|
77
|
+
onResponse: (req: MockRequest, res: MockResponse) => MockResponse;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the status code for a request using only the supplied config.
|
|
81
|
+
* Returns `undefined` when the request should pass through.
|
|
82
|
+
* Exported for unit-testing; callers normally use `buildMockModeHooks`.
|
|
83
|
+
*/
|
|
84
|
+
export declare const resolveStatus: (method: string, path: string, config: MockModeConfig) => number | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Build the onRequest / onResponse hook pair. Returns `undefined`
|
|
87
|
+
* when the feature is disabled (caller may skip wiring entirely).
|
|
88
|
+
*
|
|
89
|
+
* `bodyResolver` is optional. When provided, @databehave/server asks it for a
|
|
90
|
+
* body for the configured status (typically a generator backed by
|
|
91
|
+
* the OpenAPI `responses[N]` schema). When it returns `undefined`,
|
|
92
|
+
* the minimal envelope `{ error: true, status: N }` is used instead.
|
|
93
|
+
*/
|
|
94
|
+
export declare const buildMockModeHooks: (config: MockModeConfig, bodyResolver?: BodyResolver) => MockModeHooks | undefined;
|
|
95
|
+
//# sourceMappingURL=mock-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-mode.d.ts","sourceRoot":"","sources":["../src/mock-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE3D,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,KACX,OAAO,GAAG,SAAS,CAAA;AAExB;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,oEAAoE;IACpE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,YAAY,GAAG,SAAS,CAAA;IACzD,UAAU,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,YAAY,CAAA;CAClE;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,QAAQ,cAAc,KACrB,MAAM,GAAG,SAQX,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,cAAc,EACtB,eAAe,YAAY,KAC1B,aAAa,GAAG,SA4ElB,CAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the status code for a request using only the supplied config.
|
|
3
|
+
* Returns `undefined` when the request should pass through.
|
|
4
|
+
* Exported for unit-testing; callers normally use `buildMockModeHooks`.
|
|
5
|
+
*/
|
|
6
|
+
export const resolveStatus = (method, path, config) => {
|
|
7
|
+
const overrides = config.pathOverrides ?? {};
|
|
8
|
+
const m = method.toUpperCase();
|
|
9
|
+
const byMethodPath = overrides[`${m} ${path}`];
|
|
10
|
+
if (byMethodPath !== undefined)
|
|
11
|
+
return byMethodPath;
|
|
12
|
+
const byPath = overrides[path];
|
|
13
|
+
if (byPath !== undefined)
|
|
14
|
+
return byPath;
|
|
15
|
+
return config.defaultStatus;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Build the onRequest / onResponse hook pair. Returns `undefined`
|
|
19
|
+
* when the feature is disabled (caller may skip wiring entirely).
|
|
20
|
+
*
|
|
21
|
+
* `bodyResolver` is optional. When provided, @databehave/server asks it for a
|
|
22
|
+
* body for the configured status (typically a generator backed by
|
|
23
|
+
* the OpenAPI `responses[N]` schema). When it returns `undefined`,
|
|
24
|
+
* the minimal envelope `{ error: true, status: N }` is used instead.
|
|
25
|
+
*/
|
|
26
|
+
export const buildMockModeHooks = (config, bodyResolver) => {
|
|
27
|
+
if (config.enabled !== true)
|
|
28
|
+
return undefined;
|
|
29
|
+
const header = config.header ?? 'x-mock-status';
|
|
30
|
+
const healthPaths = new Set(config.healthPaths ?? []);
|
|
31
|
+
const isHealthPath = (p) => healthPaths.has(p);
|
|
32
|
+
const logger = config.logger ?? console;
|
|
33
|
+
const synthesize = (method, path, status) => {
|
|
34
|
+
const headers = { [header]: String(status) };
|
|
35
|
+
if (status === 204) {
|
|
36
|
+
return { status, empty: true, headers };
|
|
37
|
+
}
|
|
38
|
+
const body = bodyResolver?.(method, path, status) ?? {
|
|
39
|
+
error: true,
|
|
40
|
+
status,
|
|
41
|
+
};
|
|
42
|
+
return { status, json: body, headers };
|
|
43
|
+
};
|
|
44
|
+
const onRequest = (req) => {
|
|
45
|
+
if (isHealthPath(req.path))
|
|
46
|
+
return undefined;
|
|
47
|
+
// Header override (opt-in). The header name is the same one used
|
|
48
|
+
// to tag responses so a single curl flag flips both directions.
|
|
49
|
+
// 1xx (informational) responses are rejected: Node's HTTP layer
|
|
50
|
+
// treats them specially and a mock server has no use for them.
|
|
51
|
+
if (config.allowHeaderOverride === true) {
|
|
52
|
+
const raw = req.headers[header.toLowerCase()];
|
|
53
|
+
if (raw !== undefined) {
|
|
54
|
+
const n = Number.parseInt(raw, 10);
|
|
55
|
+
if (Number.isFinite(n) && n >= 200 && n < 600) {
|
|
56
|
+
return synthesize(req.method, req.path, n);
|
|
57
|
+
}
|
|
58
|
+
// Out-of-range or non-numeric values were previously dropped on
|
|
59
|
+
// the floor, which made debugging "why doesn't my override
|
|
60
|
+
// work?" needlessly hard. Emit a single warn line so the typo
|
|
61
|
+
// is visible without escalating to a hard failure (which would
|
|
62
|
+
// break legitimate clients that ship the header for other
|
|
63
|
+
// reasons).
|
|
64
|
+
logger.warn(`@databehave/server/mock-mode: ignored ${header}=${JSON.stringify(raw)} ` +
|
|
65
|
+
`(must be an integer in [200, 600))`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const status = resolveStatus(req.method, req.path, config);
|
|
69
|
+
if (status === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
return synthesize(req.method, req.path, status);
|
|
72
|
+
};
|
|
73
|
+
const onResponse = (req, res) => {
|
|
74
|
+
if (isHealthPath(req.path))
|
|
75
|
+
return res;
|
|
76
|
+
// Skip when the response (synthesised or handler-supplied) already
|
|
77
|
+
// carries the tag header. Header lookup is case-insensitive so
|
|
78
|
+
// `'X-Mock-Status'` from a handler is recognised against the
|
|
79
|
+
// configured (typically lowercase) `header` name.
|
|
80
|
+
if (res.headers) {
|
|
81
|
+
const wanted = header.toLowerCase();
|
|
82
|
+
for (const k of Object.keys(res.headers)) {
|
|
83
|
+
if (k.toLowerCase() === wanted)
|
|
84
|
+
return res;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const tag = String(res.status ?? 200);
|
|
88
|
+
return {
|
|
89
|
+
...res,
|
|
90
|
+
headers: { ...(res.headers ?? {}), [header]: tag },
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
return { onRequest, onResponse };
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=mock-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-mode.js","sourceRoot":"","sources":["../src/mock-mode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-schema dispatch wiring (Phase 2C / brief item #11).
|
|
3
|
+
*
|
|
4
|
+
* When the JSONC config sets a top-level `schema:` field, this module:
|
|
5
|
+
*
|
|
6
|
+
* 1. Resolves the optional peer `@databehave/schema` via a single
|
|
7
|
+
* `await import('@databehave/schema')` (string literal — never a
|
|
8
|
+
* variable, never a helper). On `ERR_MODULE_NOT_FOUND` / similar
|
|
9
|
+
* "Cannot find module" errors, the native error is suppressed
|
|
10
|
+
* and replaced with the friendly multi-line install-hint
|
|
11
|
+
* template (see `INSTALL_HINT`). Throws fail-fast at server
|
|
12
|
+
* creation; never lazily on first request.
|
|
13
|
+
* 2. Pre-translates every OAS path/method (whose 200 / first 2xx
|
|
14
|
+
* response carries a JSON schema) into a `@databehave/schema`
|
|
15
|
+
* IR node via `translateOasToIR`. Translation errors throw at
|
|
16
|
+
* server-creation time so OAS gaps surface fail-loud — same
|
|
17
|
+
* philosophy as the validator and the OAS-only generator.
|
|
18
|
+
* 3. Synthesizes a per-route handler closing over the cached
|
|
19
|
+
* schema module + IR. At request time the handler derives a
|
|
20
|
+
* seed (per `config.schema.seed`), invokes `mock(ir, { seed })`,
|
|
21
|
+
* and returns `{ json: ... }`.
|
|
22
|
+
*
|
|
23
|
+
* Hand-written `endpoints` always win — auto-mode only fills routes
|
|
24
|
+
* absent from the user's `endpoints` map. Genuine-separation
|
|
25
|
+
* invariant: this file MUST NOT statically value-import
|
|
26
|
+
* `@databehave/schema`. Type-only imports are erased at compile time
|
|
27
|
+
* and are fine.
|
|
28
|
+
*/
|
|
29
|
+
import type { Handler, RouteKey } from '../types.js';
|
|
30
|
+
import type { OasDoc } from './walker.js';
|
|
31
|
+
/** Fully-resolved schema-mode config (after JSONC validation). */
|
|
32
|
+
export interface ResolvedSchemaConfig {
|
|
33
|
+
readonly seed: number | 'stable' | 'random';
|
|
34
|
+
readonly locale?: string;
|
|
35
|
+
readonly arrayCount?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Friendly install-hint emitted when `config.schema.enabled === true`
|
|
39
|
+
* but the `@databehave/schema` peer is not installed. Verbatim from
|
|
40
|
+
* the brief — names the package, the field, the file, all three
|
|
41
|
+
* install commands, the README anchor, and the opt-out instruction.
|
|
42
|
+
*/
|
|
43
|
+
export declare const INSTALL_HINT: string;
|
|
44
|
+
/**
|
|
45
|
+
* Validate the raw JSONC `schema:` value and normalise to the
|
|
46
|
+
* resolved shape. The value MUST be an object with an explicit
|
|
47
|
+
* `enabled: boolean`. The boolean shorthand (`schema: true` /
|
|
48
|
+
* `schema: false`) was removed — callers receive a migration
|
|
49
|
+
* error pointing at the new shape. Returns `null` when
|
|
50
|
+
* `enabled === false` so the caller can short-circuit auto-mode.
|
|
51
|
+
* Unknown keys throw with a message pointing at the offending key.
|
|
52
|
+
*/
|
|
53
|
+
export declare const normaliseSchemaConfig: (raw: unknown) => ResolvedSchemaConfig | null;
|
|
54
|
+
/**
|
|
55
|
+
* Load the optional peer `@databehave/schema` package. On
|
|
56
|
+
* "Cannot find module" failures the native Node error is suppressed
|
|
57
|
+
* and replaced with the friendly install hint above. Other errors
|
|
58
|
+
* (e.g. broken install) re-throw verbatim — those are real bugs.
|
|
59
|
+
*/
|
|
60
|
+
export declare const loadSchemaModule: () => Promise<typeof import("@databehave/schema")>;
|
|
61
|
+
/**
|
|
62
|
+
* FNV-1a 32-bit hash. Tiny, zero-dep, stable across machines —
|
|
63
|
+
* exactly what the per-request `seed: 'stable'` derivation needs.
|
|
64
|
+
*
|
|
65
|
+
* Input format: `${METHOD} ${endpointPath}|${sortedQuery}|${sortedPathParams}`,
|
|
66
|
+
* where sorted means lex-sorted keys joined by `&` as `key=value`.
|
|
67
|
+
*
|
|
68
|
+
* Example input → output (uint32):
|
|
69
|
+
* `GET /users/:id|page=2|id=42` → 0x9b1ed8c5
|
|
70
|
+
*/
|
|
71
|
+
export declare const hashSeed: (input: string) => number;
|
|
72
|
+
/**
|
|
73
|
+
* Build a route-key → handler map for every OAS path/method whose
|
|
74
|
+
* response carries a usable JSON schema and is NOT already covered
|
|
75
|
+
* by hand-written `endpoints`. Each handler is closed over the
|
|
76
|
+
* cached schema module and pre-translated IR.
|
|
77
|
+
*
|
|
78
|
+
* `declaredKeys` is the set of route keys the user already declared
|
|
79
|
+
* via `endpoints`; auto-mode skips those so hand-written routes
|
|
80
|
+
* always win.
|
|
81
|
+
*/
|
|
82
|
+
export declare const buildAutoSchemaRoutes: (doc: OasDoc, cfg: ResolvedSchemaConfig, schemaModule: typeof import("@databehave/schema"), declaredKeys: ReadonlySet<RouteKey>) => Promise<Map<RouteKey, Handler>>;
|
|
83
|
+
//# sourceMappingURL=auto-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-schema.d.ts","sourceRoot":"","sources":["../../src/openapi/auto-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAuB,QAAQ,EAAE,MAAM,aAAa,CAAA;AAGzE,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,aAAa,CAAA;AAElD,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAaD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAY6D,CAAA;AAEtF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,OAAO,KAAG,oBAAoB,GAAG,IA+D3E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,cAAc,oBAAoB,CAAC,CAmBpF,CAAA;AAiBD;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,KAAG,MAOxC,CAAA;AA+CD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAChC,KAAK,MAAM,EACX,KAAK,oBAAoB,EACzB,cAAc,cAAc,oBAAoB,CAAC,EACjD,cAAc,WAAW,CAAC,QAAQ,CAAC,KAClC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAgChC,CAAA"}
|