@amritk/generate-examples 0.5.2 → 0.5.4
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/AI.md +37 -0
- package/dist/generators/build-schema.js +23 -50
- package/dist/generators/collect-example-imports.js +64 -94
- package/dist/generators/derive-example.js +507 -680
- package/dist/generators/find-schema-cycles.js +91 -115
- package/dist/generators/generate-arbitrary.js +316 -471
- package/dist/generators/generate-files.js +28 -45
- package/dist/generators/schema-validation.js +55 -78
- package/dist/index.js +10 -3
- package/package.json +6 -5
|
@@ -1,740 +1,567 @@
|
|
|
1
|
-
import { getMjstInstanceOf, getMjstPrimitive } from
|
|
2
|
-
import { resolveRef } from
|
|
3
|
-
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDefault, hasDependentRequired, hasDependentSchemas, hasEnum, hasExamples, hasExclusiveMaximum, hasExclusiveMinimum, hasFormat, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasPatternProperties, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject
|
|
4
|
-
import { makeInstanceCheck, needsValidationFilter } from
|
|
5
|
-
/** Lowercases the first character of a name. e.g. "User" → "user" */
|
|
1
|
+
import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
|
|
2
|
+
import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
3
|
+
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDefault, hasDependentRequired, hasDependentSchemas, hasEnum, hasExamples, hasExclusiveMaximum, hasExclusiveMinimum, hasFormat, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasPatternProperties, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
4
|
+
import { makeInstanceCheck, needsValidationFilter } from "./schema-validation.js";
|
|
6
5
|
const lowerFirst = (name) => name.charAt(0).toLowerCase() + name.slice(1);
|
|
7
|
-
/** Derives the example const name from a type name. e.g. "User" → "userExample" */
|
|
8
6
|
const exampleName = (typeName) => `${lowerFirst(typeName)}Example`;
|
|
9
|
-
/** A representative character for a single regex atom (class body / escape). */
|
|
10
7
|
const charForClass = (inner) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
if (/a-z/.test(inner))
|
|
9
|
+
return "a";
|
|
10
|
+
if (/A-Z/.test(inner))
|
|
11
|
+
return "A";
|
|
12
|
+
if (/0-9|\\d/.test(inner))
|
|
13
|
+
return "5";
|
|
14
|
+
const first = inner.replace(/^\^/, "")[0];
|
|
15
|
+
return first && first !== "\\" ? first : "a";
|
|
19
16
|
};
|
|
20
17
|
const charForEscape = (esc) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
if (esc === "\\d")
|
|
19
|
+
return "5";
|
|
20
|
+
if (esc === "\\w")
|
|
21
|
+
return "a";
|
|
22
|
+
if (esc === "\\s")
|
|
23
|
+
return " ";
|
|
24
|
+
return esc[1] ?? "a";
|
|
28
25
|
};
|
|
29
|
-
/** Splits `s` on top-level `|`, respecting `[...]` and `(...)` nesting. */
|
|
30
26
|
const topLevelAlternatives = (s) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
27
|
+
const parts = [];
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let inClass = false;
|
|
30
|
+
let cur = "";
|
|
31
|
+
for (let i = 0; i < s.length; i++) {
|
|
32
|
+
const c = s[i];
|
|
33
|
+
if (c === "\\") {
|
|
34
|
+
cur += c + (s[i + 1] ?? "");
|
|
35
|
+
i++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (inClass) {
|
|
39
|
+
cur += c;
|
|
40
|
+
if (c === "]")
|
|
41
|
+
inClass = false;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (c === "[")
|
|
45
|
+
inClass = true;
|
|
46
|
+
else if (c === "(")
|
|
47
|
+
depth++;
|
|
48
|
+
else if (c === ")")
|
|
49
|
+
depth--;
|
|
50
|
+
else if (c === "|" && depth === 0) {
|
|
51
|
+
parts.push(cur);
|
|
52
|
+
cur = "";
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
cur += c;
|
|
56
|
+
}
|
|
57
|
+
parts.push(cur);
|
|
58
|
+
return parts;
|
|
59
|
+
};
|
|
60
|
+
const sampleFromPattern = (pattern, minLength) => {
|
|
61
|
+
let body = pattern;
|
|
62
|
+
if (body.startsWith("^"))
|
|
63
|
+
body = body.slice(1);
|
|
64
|
+
if (body.endsWith("$") && !body.endsWith("\\$"))
|
|
65
|
+
body = body.slice(0, -1);
|
|
66
|
+
const sampleAlt = (s) => {
|
|
67
|
+
for (const alt of topLevelAlternatives(s)) {
|
|
68
|
+
const r = sampleSeq(alt);
|
|
69
|
+
if (r !== void 0)
|
|
70
|
+
return r;
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
};
|
|
74
|
+
const sampleSeq = (seq) => {
|
|
75
|
+
let out = "";
|
|
76
|
+
let i = 0;
|
|
77
|
+
while (i < seq.length) {
|
|
78
|
+
let unit;
|
|
79
|
+
const c = seq[i];
|
|
80
|
+
if (c === "(") {
|
|
81
|
+
let depth = 1;
|
|
82
|
+
let j = i + 1;
|
|
83
|
+
for (; j < seq.length && depth > 0; j++) {
|
|
84
|
+
const cj = seq[j];
|
|
85
|
+
if (cj === "\\") {
|
|
86
|
+
j++;
|
|
46
87
|
continue;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
inClass = true;
|
|
50
|
-
else if (c === '(')
|
|
88
|
+
}
|
|
89
|
+
if (cj === "(")
|
|
51
90
|
depth++;
|
|
52
|
-
|
|
91
|
+
else if (cj === ")")
|
|
53
92
|
depth--;
|
|
54
|
-
else if (c === '|' && depth === 0) {
|
|
55
|
-
parts.push(cur);
|
|
56
|
-
cur = '';
|
|
57
|
-
continue;
|
|
58
93
|
}
|
|
59
|
-
|
|
94
|
+
if (depth !== 0)
|
|
95
|
+
return void 0;
|
|
96
|
+
let inner = seq.slice(i + 1, j - 1);
|
|
97
|
+
if (/^\?[=!]/.test(inner) || /^\?<[=!]/.test(inner))
|
|
98
|
+
return void 0;
|
|
99
|
+
inner = inner.replace(/^\?:/, "").replace(/^\?<[^>]*>/, "");
|
|
100
|
+
unit = sampleAlt(inner);
|
|
101
|
+
if (unit === void 0)
|
|
102
|
+
return void 0;
|
|
103
|
+
i = j;
|
|
104
|
+
} else if (c === "[") {
|
|
105
|
+
const end = seq.indexOf("]", i + 1);
|
|
106
|
+
if (end === -1)
|
|
107
|
+
return void 0;
|
|
108
|
+
unit = charForClass(seq.slice(i + 1, end));
|
|
109
|
+
i = end + 1;
|
|
110
|
+
} else if (c === "\\") {
|
|
111
|
+
const esc = seq.slice(i, i + 2);
|
|
112
|
+
if (/\d/.test(esc[1] ?? ""))
|
|
113
|
+
return void 0;
|
|
114
|
+
unit = charForEscape(esc);
|
|
115
|
+
i += 2;
|
|
116
|
+
} else if (c === ".") {
|
|
117
|
+
unit = "a";
|
|
118
|
+
i++;
|
|
119
|
+
} else {
|
|
120
|
+
unit = c;
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
let reps = 1;
|
|
124
|
+
const q = seq[i];
|
|
125
|
+
if (q === "+") {
|
|
126
|
+
reps = Math.max(1, minLength);
|
|
127
|
+
i++;
|
|
128
|
+
} else if (q === "*") {
|
|
129
|
+
reps = Math.max(0, minLength);
|
|
130
|
+
i++;
|
|
131
|
+
} else if (q === "?") {
|
|
132
|
+
reps = 1;
|
|
133
|
+
i++;
|
|
134
|
+
} else if (q === "{") {
|
|
135
|
+
const end = seq.indexOf("}", i + 1);
|
|
136
|
+
if (end === -1)
|
|
137
|
+
return void 0;
|
|
138
|
+
reps = Number.parseInt(seq.slice(i + 1, end), 10) || 0;
|
|
139
|
+
i = end + 1;
|
|
140
|
+
}
|
|
141
|
+
out += unit.repeat(reps);
|
|
60
142
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Best-effort generator of a string matching a `pattern`, via recursive descent:
|
|
66
|
-
* anchors, literals, `.`, escapes (`\d`/`\w`/`\s`), character classes, groups
|
|
67
|
-
* (capturing / non-capturing / named), alternation (`a|b` — picks the first
|
|
68
|
-
* usable branch), and the `+`/`*`/`?`/`{n}`/`{n,m}` quantifiers. Lookarounds and
|
|
69
|
-
* backreferences fall through to `undefined`. The caller verifies the result
|
|
70
|
-
* against the real regex and only uses it on a match, so a partial sampler never
|
|
71
|
-
* makes the example worse — it just upgrades the cases it understands.
|
|
72
|
-
*/
|
|
73
|
-
const sampleFromPattern = (pattern, minLength) => {
|
|
74
|
-
let body = pattern;
|
|
75
|
-
if (body.startsWith('^'))
|
|
76
|
-
body = body.slice(1);
|
|
77
|
-
if (body.endsWith('$') && !body.endsWith('\\$'))
|
|
78
|
-
body = body.slice(0, -1);
|
|
79
|
-
// Samples one alternation, preferring the first branch that samples cleanly.
|
|
80
|
-
const sampleAlt = (s) => {
|
|
81
|
-
for (const alt of topLevelAlternatives(s)) {
|
|
82
|
-
const r = sampleSeq(alt);
|
|
83
|
-
if (r !== undefined)
|
|
84
|
-
return r;
|
|
85
|
-
}
|
|
86
|
-
return undefined;
|
|
87
|
-
};
|
|
88
|
-
// Samples one concatenation (no top-level `|`).
|
|
89
|
-
const sampleSeq = (seq) => {
|
|
90
|
-
let out = '';
|
|
91
|
-
let i = 0;
|
|
92
|
-
while (i < seq.length) {
|
|
93
|
-
let unit;
|
|
94
|
-
const c = seq[i];
|
|
95
|
-
if (c === '(') {
|
|
96
|
-
// Find the matching close paren.
|
|
97
|
-
let depth = 1;
|
|
98
|
-
let j = i + 1;
|
|
99
|
-
for (; j < seq.length && depth > 0; j++) {
|
|
100
|
-
const cj = seq[j];
|
|
101
|
-
if (cj === '\\') {
|
|
102
|
-
j++;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (cj === '(')
|
|
106
|
-
depth++;
|
|
107
|
-
else if (cj === ')')
|
|
108
|
-
depth--;
|
|
109
|
-
}
|
|
110
|
-
if (depth !== 0)
|
|
111
|
-
return undefined;
|
|
112
|
-
let inner = seq.slice(i + 1, j - 1);
|
|
113
|
-
if (/^\?[=!]/.test(inner) || /^\?<[=!]/.test(inner))
|
|
114
|
-
return undefined; // lookaround
|
|
115
|
-
inner = inner.replace(/^\?:/, '').replace(/^\?<[^>]*>/, '');
|
|
116
|
-
unit = sampleAlt(inner);
|
|
117
|
-
if (unit === undefined)
|
|
118
|
-
return undefined;
|
|
119
|
-
i = j;
|
|
120
|
-
}
|
|
121
|
-
else if (c === '[') {
|
|
122
|
-
const end = seq.indexOf(']', i + 1);
|
|
123
|
-
if (end === -1)
|
|
124
|
-
return undefined;
|
|
125
|
-
unit = charForClass(seq.slice(i + 1, end));
|
|
126
|
-
i = end + 1;
|
|
127
|
-
}
|
|
128
|
-
else if (c === '\\') {
|
|
129
|
-
const esc = seq.slice(i, i + 2);
|
|
130
|
-
if (/\d/.test(esc[1] ?? ''))
|
|
131
|
-
return undefined; // backreference
|
|
132
|
-
unit = charForEscape(esc);
|
|
133
|
-
i += 2;
|
|
134
|
-
}
|
|
135
|
-
else if (c === '.') {
|
|
136
|
-
unit = 'a';
|
|
137
|
-
i++;
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
unit = c;
|
|
141
|
-
i++;
|
|
142
|
-
}
|
|
143
|
-
// Optional quantifier.
|
|
144
|
-
let reps = 1;
|
|
145
|
-
const q = seq[i];
|
|
146
|
-
if (q === '+') {
|
|
147
|
-
reps = Math.max(1, minLength);
|
|
148
|
-
i++;
|
|
149
|
-
}
|
|
150
|
-
else if (q === '*') {
|
|
151
|
-
reps = Math.max(0, minLength);
|
|
152
|
-
i++;
|
|
153
|
-
}
|
|
154
|
-
else if (q === '?') {
|
|
155
|
-
reps = 1;
|
|
156
|
-
i++;
|
|
157
|
-
}
|
|
158
|
-
else if (q === '{') {
|
|
159
|
-
const end = seq.indexOf('}', i + 1);
|
|
160
|
-
if (end === -1)
|
|
161
|
-
return undefined;
|
|
162
|
-
reps = Number.parseInt(seq.slice(i + 1, end), 10) || 0;
|
|
163
|
-
i = end + 1;
|
|
164
|
-
}
|
|
165
|
-
out += unit.repeat(reps);
|
|
166
|
-
}
|
|
167
|
-
return out;
|
|
168
|
-
};
|
|
169
|
-
return sampleAlt(body);
|
|
143
|
+
return out;
|
|
144
|
+
};
|
|
145
|
+
return sampleAlt(body);
|
|
170
146
|
};
|
|
171
|
-
/** Returns a representative string honouring `format`, `pattern`, and length. */
|
|
172
147
|
const exampleString = (schema) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
148
|
+
if (hasFormat(schema)) {
|
|
149
|
+
switch (schema.format) {
|
|
150
|
+
case "email":
|
|
151
|
+
return "user@example.com";
|
|
152
|
+
case "uuid":
|
|
153
|
+
return "00000000-0000-0000-0000-000000000000";
|
|
154
|
+
case "uri":
|
|
155
|
+
case "url":
|
|
156
|
+
return "https://example.com";
|
|
157
|
+
case "date-time":
|
|
158
|
+
return "1970-01-01T00:00:00.000Z";
|
|
159
|
+
case "date":
|
|
160
|
+
return "1970-01-01";
|
|
161
|
+
case "time":
|
|
162
|
+
return "00:00:00.000Z";
|
|
163
|
+
case "hostname":
|
|
164
|
+
return "example.com";
|
|
165
|
+
case "ipv4":
|
|
166
|
+
return "127.0.0.1";
|
|
167
|
+
case "ipv6":
|
|
168
|
+
return "::1";
|
|
195
169
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
170
|
+
}
|
|
171
|
+
const minLength = hasMinLength(schema) ? schema.minLength : 0;
|
|
172
|
+
if (hasPattern(schema)) {
|
|
173
|
+
const sampled = sampleFromPattern(schema.pattern, minLength);
|
|
174
|
+
if (sampled !== void 0 && new RegExp(schema.pattern).test(sampled)) {
|
|
175
|
+
if (!(hasMaxLength(schema) && sampled.length > schema.maxLength))
|
|
176
|
+
return sampled;
|
|
204
177
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
178
|
+
}
|
|
179
|
+
let value = "string";
|
|
180
|
+
if (value.length < minLength)
|
|
181
|
+
value = value.padEnd(minLength, "x");
|
|
182
|
+
if (hasMaxLength(schema) && value.length > schema.maxLength)
|
|
183
|
+
value = value.slice(0, schema.maxLength);
|
|
184
|
+
return value;
|
|
211
185
|
};
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
* `$ref`s are resolved and inlined by value; recursive refs short-circuit to
|
|
218
|
-
* `null` (tracked via `seen`).
|
|
219
|
-
*
|
|
220
|
-
* Note: values constrained only by `pattern` are not guaranteed to match the
|
|
221
|
-
* pattern — use the generated arbitrary when pattern fidelity matters.
|
|
222
|
-
*/
|
|
223
|
-
export const deriveExample = (schema, rootSchema, seen = new Set()) => {
|
|
224
|
-
if (!isSchemaObject(schema))
|
|
225
|
-
return null;
|
|
226
|
-
const base = deriveBase(schema, rootSchema, seen);
|
|
227
|
-
// Keywords the structural deriver can't fully honour (`if`/`then`/`else`,
|
|
228
|
-
// `not`, `oneOf` exclusivity) are reconciled by validating candidates against a
|
|
229
|
-
// real validator and picking the first that passes.
|
|
230
|
-
return needsValidationFilter(schema) ? refineExample(schema, base, rootSchema, seen) : base;
|
|
186
|
+
const deriveExample = (schema, rootSchema, seen = /* @__PURE__ */ new Set()) => {
|
|
187
|
+
if (!isSchemaObject(schema))
|
|
188
|
+
return null;
|
|
189
|
+
const base = deriveBase(schema, rootSchema, seen);
|
|
190
|
+
return needsValidationFilter(schema) ? refineExample(schema, base, rootSchema, seen) : base;
|
|
231
191
|
};
|
|
232
|
-
/** Structural derivation for a node, before any validating refinement. */
|
|
233
192
|
const deriveBase = (schema, rootSchema, seen) => {
|
|
234
|
-
|
|
235
|
-
return null;
|
|
236
|
-
if (hasConst(schema))
|
|
237
|
-
return schema.const;
|
|
238
|
-
if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0)
|
|
239
|
-
return schema.examples[0];
|
|
240
|
-
if (hasDefault(schema))
|
|
241
|
-
return schema.default;
|
|
242
|
-
if (hasEnum(schema) && schema.enum.length > 0) {
|
|
243
|
-
// Prefer the first member that also satisfies any sibling length/range
|
|
244
|
-
// constraints (e.g. `enum` + `minLength`), falling back to the first member.
|
|
245
|
-
const fitting = schema.enum.find((value) => satisfiesScalarConstraints(schema, value));
|
|
246
|
-
return fitting !== undefined ? fitting : schema.enum[0];
|
|
247
|
-
}
|
|
248
|
-
if (hasRef(schema)) {
|
|
249
|
-
const ref = schema.$ref;
|
|
250
|
-
if (seen.has(ref) || !rootSchema)
|
|
251
|
-
return null;
|
|
252
|
-
const resolved = resolveRef(ref, rootSchema);
|
|
253
|
-
if (!resolved)
|
|
254
|
-
return null;
|
|
255
|
-
return deriveExample(resolved, rootSchema, new Set([...seen, ref]));
|
|
256
|
-
}
|
|
257
|
-
const instanceOf = getMjstInstanceOf(schema);
|
|
258
|
-
if (instanceOf === 'Date')
|
|
259
|
-
return new Date(0);
|
|
260
|
-
const primitive = getMjstPrimitive(schema);
|
|
261
|
-
if (primitive === 'bigint')
|
|
262
|
-
return 0n;
|
|
263
|
-
// `allOf` must satisfy every branch at once, so derive from a single schema
|
|
264
|
-
// that merges the branches (and the node's own keywords) rather than picking
|
|
265
|
-
// one branch — picking one would ignore the others' constraints.
|
|
266
|
-
if (hasAllOf(schema))
|
|
267
|
-
return deriveExample(mergeAllOf(schema), rootSchema, seen);
|
|
268
|
-
if (hasOneOf(schema) && schema.oneOf[0] !== undefined)
|
|
269
|
-
return deriveExample(schema.oneOf[0], rootSchema, seen);
|
|
270
|
-
if (hasAnyOf(schema) && schema.anyOf[0] !== undefined)
|
|
271
|
-
return deriveExample(schema.anyOf[0], rootSchema, seen);
|
|
272
|
-
if (hasType(schema))
|
|
273
|
-
return deriveForType(schema.type, schema, rootSchema, seen);
|
|
274
|
-
// Multi-type schemas (`type: ['string', 'null']`) derive from their first
|
|
275
|
-
// member type; `hasType` only matches a single string `type`.
|
|
276
|
-
if (Array.isArray(schema.type) && schema.type.length > 0) {
|
|
277
|
-
return deriveForType(schema.type[0], schema, rootSchema, seen);
|
|
278
|
-
}
|
|
193
|
+
if (!isSchemaObject(schema))
|
|
279
194
|
return null;
|
|
195
|
+
if (hasConst(schema))
|
|
196
|
+
return schema.const;
|
|
197
|
+
if (hasExamples(schema) && Array.isArray(schema.examples) && schema.examples.length > 0)
|
|
198
|
+
return schema.examples[0];
|
|
199
|
+
if (hasDefault(schema))
|
|
200
|
+
return schema.default;
|
|
201
|
+
if (hasEnum(schema) && schema.enum.length > 0) {
|
|
202
|
+
const fitting = schema.enum.find((value) => satisfiesScalarConstraints(schema, value));
|
|
203
|
+
return fitting !== void 0 ? fitting : schema.enum[0];
|
|
204
|
+
}
|
|
205
|
+
if (hasRef(schema)) {
|
|
206
|
+
const ref = schema.$ref;
|
|
207
|
+
if (seen.has(ref) || !rootSchema)
|
|
208
|
+
return null;
|
|
209
|
+
const resolved = resolveRef(ref, rootSchema);
|
|
210
|
+
if (!resolved)
|
|
211
|
+
return null;
|
|
212
|
+
return deriveExample(resolved, rootSchema, /* @__PURE__ */ new Set([...seen, ref]));
|
|
213
|
+
}
|
|
214
|
+
const instanceOf = getMjstInstanceOf(schema);
|
|
215
|
+
if (instanceOf === "Date")
|
|
216
|
+
return /* @__PURE__ */ new Date(0);
|
|
217
|
+
const primitive = getMjstPrimitive(schema);
|
|
218
|
+
if (primitive === "bigint")
|
|
219
|
+
return 0n;
|
|
220
|
+
if (hasAllOf(schema))
|
|
221
|
+
return deriveExample(mergeAllOf(schema), rootSchema, seen);
|
|
222
|
+
if (hasOneOf(schema) && schema.oneOf[0] !== void 0)
|
|
223
|
+
return deriveExample(schema.oneOf[0], rootSchema, seen);
|
|
224
|
+
if (hasAnyOf(schema) && schema.anyOf[0] !== void 0)
|
|
225
|
+
return deriveExample(schema.anyOf[0], rootSchema, seen);
|
|
226
|
+
if (hasType(schema))
|
|
227
|
+
return deriveForType(schema.type, schema, rootSchema, seen);
|
|
228
|
+
if (Array.isArray(schema.type) && schema.type.length > 0) {
|
|
229
|
+
return deriveForType(schema.type[0], schema, rootSchema, seen);
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
280
232
|
};
|
|
281
|
-
|
|
282
|
-
const REFINED_APPLICATORS = ['if', 'then', 'else', 'not', 'oneOf'];
|
|
283
|
-
/** A shallow copy of `schema` with the refined applicator keywords removed. */
|
|
233
|
+
const REFINED_APPLICATORS = ["if", "then", "else", "not", "oneOf"];
|
|
284
234
|
const structuralOnly = (schema) => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
235
|
+
const clone = { ...schema };
|
|
236
|
+
for (const key of REFINED_APPLICATORS)
|
|
237
|
+
delete clone[key];
|
|
238
|
+
return clone;
|
|
289
239
|
};
|
|
290
|
-
/**
|
|
291
|
-
* Reconciles keywords the structural deriver can't satisfy on its own
|
|
292
|
-
* (`if`/`then`/`else`, `not`, `oneOf` exclusivity). It validates the structural
|
|
293
|
-
* `base` against the full schema and, if it fails, tries alternative candidates —
|
|
294
|
-
* each `oneOf` branch, and the `then`/`else` branches merged with the structural
|
|
295
|
-
* siblings — returning the first that validates. Falls back to `base` when none
|
|
296
|
-
* do (a best-effort for schemas that can't be satisfied structurally, e.g. an
|
|
297
|
-
* adversarial `not`).
|
|
298
|
-
*/
|
|
299
240
|
const refineExample = (schema, base, rootSchema, seen) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return base;
|
|
303
|
-
const raw = schema;
|
|
304
|
-
const structural = structuralOnly(schema);
|
|
305
|
-
const combine = (branch) => deriveExample({ allOf: [structural, branch] }, rootSchema, seen);
|
|
306
|
-
const candidates = [];
|
|
307
|
-
if (hasOneOf(schema)) {
|
|
308
|
-
for (const branch of schema.oneOf)
|
|
309
|
-
if (branch !== undefined)
|
|
310
|
-
candidates.push(combine(branch));
|
|
311
|
-
}
|
|
312
|
-
if ('if' in raw) {
|
|
313
|
-
if (raw['then'] !== undefined)
|
|
314
|
-
candidates.push(combine(raw['then']));
|
|
315
|
-
if (raw['else'] !== undefined)
|
|
316
|
-
candidates.push(combine(raw['else']));
|
|
317
|
-
// `if` failing (so neither `then` nor a matched `else` applies) is also valid.
|
|
318
|
-
candidates.push(deriveExample(structural, rootSchema, seen));
|
|
319
|
-
}
|
|
320
|
-
for (const candidate of candidates)
|
|
321
|
-
if (check(candidate))
|
|
322
|
-
return candidate;
|
|
241
|
+
const check = makeInstanceCheck(schema, rootSchema);
|
|
242
|
+
if (check(base))
|
|
323
243
|
return base;
|
|
244
|
+
const raw = schema;
|
|
245
|
+
const structural = structuralOnly(schema);
|
|
246
|
+
const combine = (branch) => deriveExample({ allOf: [structural, branch] }, rootSchema, seen);
|
|
247
|
+
const candidates = [];
|
|
248
|
+
if (hasOneOf(schema)) {
|
|
249
|
+
for (const branch of schema.oneOf)
|
|
250
|
+
if (branch !== void 0)
|
|
251
|
+
candidates.push(combine(branch));
|
|
252
|
+
}
|
|
253
|
+
if ("if" in raw) {
|
|
254
|
+
if (raw["then"] !== void 0)
|
|
255
|
+
candidates.push(combine(raw["then"]));
|
|
256
|
+
if (raw["else"] !== void 0)
|
|
257
|
+
candidates.push(combine(raw["else"]));
|
|
258
|
+
candidates.push(deriveExample(structural, rootSchema, seen));
|
|
259
|
+
}
|
|
260
|
+
for (const candidate of candidates)
|
|
261
|
+
if (check(candidate))
|
|
262
|
+
return candidate;
|
|
263
|
+
return base;
|
|
324
264
|
};
|
|
325
|
-
/**
|
|
326
|
-
* True when a candidate value (e.g. an `enum`/`const` member) satisfies the
|
|
327
|
-
* node's simple string-length and numeric-range constraints. Used to pick an
|
|
328
|
-
* `enum` member that also meets a sibling `minLength`/`minimum`/etc.
|
|
329
|
-
*/
|
|
330
265
|
const satisfiesScalarConstraints = (schema, value) => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return true;
|
|
266
|
+
if (typeof value === "string") {
|
|
267
|
+
if (hasMinLength(schema) && value.length < schema.minLength)
|
|
268
|
+
return false;
|
|
269
|
+
if (hasMaxLength(schema) && value.length > schema.maxLength)
|
|
270
|
+
return false;
|
|
271
|
+
} else if (typeof value === "number") {
|
|
272
|
+
if (hasMinimum(schema) && value < schema.minimum)
|
|
273
|
+
return false;
|
|
274
|
+
if (hasMaximum(schema) && value > schema.maximum)
|
|
275
|
+
return false;
|
|
276
|
+
if (hasExclusiveMinimum(schema) && value <= schema.exclusiveMinimum)
|
|
277
|
+
return false;
|
|
278
|
+
if (hasExclusiveMaximum(schema) && value >= schema.exclusiveMaximum)
|
|
279
|
+
return false;
|
|
280
|
+
if (hasMultipleOf(schema) && schema.multipleOf > 0 && value % schema.multipleOf !== 0)
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
350
284
|
};
|
|
351
|
-
/** Derives a canonical value for a single declared `type`. */
|
|
352
285
|
const deriveForType = (type, schema, rootSchema, seen) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
286
|
+
switch (type) {
|
|
287
|
+
case "string":
|
|
288
|
+
return exampleString(schema);
|
|
289
|
+
case "number":
|
|
290
|
+
case "integer":
|
|
291
|
+
return deriveNumber(schema, type === "integer");
|
|
292
|
+
case "boolean":
|
|
293
|
+
return true;
|
|
294
|
+
case "null":
|
|
295
|
+
return null;
|
|
296
|
+
case "array":
|
|
297
|
+
return deriveArray(schema, rootSchema, seen);
|
|
298
|
+
case "object":
|
|
299
|
+
return deriveObject(schema, rootSchema, seen);
|
|
300
|
+
default:
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
370
303
|
};
|
|
371
|
-
/**
|
|
372
|
-
* Builds an object value honouring `properties`/`required`/`additionalProperties`
|
|
373
|
-
* plus the presence-gated and key-shaped keywords: `patternProperties` and
|
|
374
|
-
* `additionalProperties` pick the value schema for synthesized keys,
|
|
375
|
-
* `propertyNames` constrains those keys, `dependentRequired`/`dependentSchemas`
|
|
376
|
-
* add keys once their trigger is present, and `minProperties`/`maxProperties`
|
|
377
|
-
* bound the key count.
|
|
378
|
-
*/
|
|
379
304
|
const deriveObject = (schema, rootSchema, seen) => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
catch {
|
|
387
|
-
return [];
|
|
388
|
-
}
|
|
389
|
-
})
|
|
390
|
-
: [];
|
|
391
|
-
const additional = hasAdditionalProperties(schema) ? schema.additionalProperties : false;
|
|
392
|
-
const additionalSchema = isSchemaObject(additional) ? additional : undefined;
|
|
393
|
-
const additionalClosed = hasAdditionalProperties(schema) && schema.additionalProperties === false;
|
|
394
|
-
// With `additionalProperties: false`, extra keys are still allowed when they
|
|
395
|
-
// match a `patternProperties` entry; only a fully closed object forbids all.
|
|
396
|
-
const extrasAllowed = !additionalClosed || patternEntries.length > 0;
|
|
397
|
-
const nameCheck = hasPropertyNames(schema) ? makeInstanceCheck(schema.propertyNames, rootSchema) : undefined;
|
|
398
|
-
// The value schema for a key not declared in `properties`: the matching
|
|
399
|
-
// `patternProperties` (intersected when several match), else `additionalProperties`.
|
|
400
|
-
const valueSchemaFor = (key) => {
|
|
401
|
-
const matches = patternEntries.filter(([re]) => re.test(key)).map(([, sub]) => sub);
|
|
402
|
-
if (matches.length === 1)
|
|
403
|
-
return matches[0];
|
|
404
|
-
if (matches.length > 1)
|
|
405
|
-
return { allOf: matches };
|
|
406
|
-
return additionalSchema;
|
|
407
|
-
};
|
|
408
|
-
const addKey = (key) => {
|
|
409
|
-
const sub = valueSchemaFor(key);
|
|
410
|
-
out[key] = sub !== undefined ? deriveExample(sub, rootSchema, seen) : null;
|
|
411
|
-
};
|
|
412
|
-
if (hasProperties(schema)) {
|
|
413
|
-
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
414
|
-
out[key] = deriveExample(propSchema, rootSchema, seen);
|
|
415
|
-
}
|
|
305
|
+
const out = {};
|
|
306
|
+
const patternEntries = hasPatternProperties(schema) ? Object.entries(schema.patternProperties).flatMap(([source, sub]) => {
|
|
307
|
+
try {
|
|
308
|
+
return [[new RegExp(source), sub]];
|
|
309
|
+
} catch {
|
|
310
|
+
return [];
|
|
416
311
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
312
|
+
}) : [];
|
|
313
|
+
const additional = hasAdditionalProperties(schema) ? schema.additionalProperties : false;
|
|
314
|
+
const additionalSchema = isSchemaObject(additional) ? additional : void 0;
|
|
315
|
+
const additionalClosed = hasAdditionalProperties(schema) && schema.additionalProperties === false;
|
|
316
|
+
const extrasAllowed = !additionalClosed || patternEntries.length > 0;
|
|
317
|
+
const nameCheck = hasPropertyNames(schema) ? makeInstanceCheck(schema.propertyNames, rootSchema) : void 0;
|
|
318
|
+
const valueSchemaFor = (key) => {
|
|
319
|
+
const matches = patternEntries.filter(([re]) => re.test(key)).map(([, sub]) => sub);
|
|
320
|
+
if (matches.length === 1)
|
|
321
|
+
return matches[0];
|
|
322
|
+
if (matches.length > 1)
|
|
323
|
+
return { allOf: matches };
|
|
324
|
+
return additionalSchema;
|
|
325
|
+
};
|
|
326
|
+
const addKey = (key) => {
|
|
327
|
+
const sub = valueSchemaFor(key);
|
|
328
|
+
out[key] = sub !== void 0 ? deriveExample(sub, rootSchema, seen) : null;
|
|
329
|
+
};
|
|
330
|
+
if (hasProperties(schema)) {
|
|
331
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
332
|
+
out[key] = deriveExample(propSchema, rootSchema, seen);
|
|
422
333
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
334
|
+
}
|
|
335
|
+
if (hasRequired(schema)) {
|
|
336
|
+
for (const key of schema.required)
|
|
337
|
+
if (!(key in out))
|
|
338
|
+
addKey(key);
|
|
339
|
+
}
|
|
340
|
+
if (hasDependentRequired(schema)) {
|
|
341
|
+
for (const [trigger, deps] of Object.entries(schema.dependentRequired)) {
|
|
342
|
+
if (!(trigger in out))
|
|
343
|
+
continue;
|
|
344
|
+
for (const dep of deps)
|
|
345
|
+
if (!(dep in out))
|
|
346
|
+
addKey(dep);
|
|
432
347
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
applyDependentSchema(out, sub, rootSchema, seen);
|
|
439
|
-
}
|
|
348
|
+
}
|
|
349
|
+
if (hasDependentSchemas(schema)) {
|
|
350
|
+
for (const [trigger, sub] of Object.entries(schema.dependentSchemas)) {
|
|
351
|
+
if (trigger in out && isSchemaObject(sub))
|
|
352
|
+
applyDependentSchema(out, sub, rootSchema, seen);
|
|
440
353
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
354
|
+
}
|
|
355
|
+
if (hasMinProperties(schema) && extrasAllowed) {
|
|
356
|
+
let n = 0;
|
|
357
|
+
let guard = 0;
|
|
358
|
+
while (Object.keys(out).length < schema.minProperties && guard++ < schema.minProperties + 50) {
|
|
359
|
+
const key = synthKey(n++, patternEntries, schema, nameCheck);
|
|
360
|
+
if (key === void 0 || key in out)
|
|
361
|
+
continue;
|
|
362
|
+
addKey(key);
|
|
451
363
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return out;
|
|
364
|
+
}
|
|
365
|
+
if (hasMaxProperties(schema))
|
|
366
|
+
enforceMaxProperties(out, schema, schema.maxProperties);
|
|
367
|
+
return out;
|
|
457
368
|
};
|
|
458
|
-
/** Applies a `dependentSchemas` branch's object shape (`properties`/`required`) in place. */
|
|
459
369
|
const applyDependentSchema = (out, sub, rootSchema, seen) => {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
370
|
+
if (hasProperties(sub)) {
|
|
371
|
+
for (const [key, propSchema] of Object.entries(sub.properties)) {
|
|
372
|
+
if (!(key in out))
|
|
373
|
+
out[key] = deriveExample(propSchema, rootSchema, seen);
|
|
465
374
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
375
|
+
}
|
|
376
|
+
if (hasRequired(sub)) {
|
|
377
|
+
const propSchemas = hasProperties(sub) ? sub.properties : {};
|
|
378
|
+
for (const key of sub.required) {
|
|
379
|
+
if (key in out)
|
|
380
|
+
continue;
|
|
381
|
+
const propSchema = propSchemas[key];
|
|
382
|
+
out[key] = propSchema !== void 0 ? deriveExample(propSchema, rootSchema, seen) : null;
|
|
474
383
|
}
|
|
384
|
+
}
|
|
475
385
|
};
|
|
476
|
-
/**
|
|
477
|
-
* Produces a candidate key name for the i-th synthesized property. Prefers a key
|
|
478
|
-
* matching a `patternProperties` entry (so the entry supplies its value schema),
|
|
479
|
-
* then one matching `propertyNames`, then a plain `extraN`. Returns `undefined`
|
|
480
|
-
* when the candidate can't satisfy a `propertyNames` constraint.
|
|
481
|
-
*/
|
|
482
386
|
const synthKey = (i, patternEntries, schema, nameCheck) => {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return seed;
|
|
503
|
-
}
|
|
504
|
-
return undefined;
|
|
387
|
+
const vary = (seed) => seed.repeat(i + 1);
|
|
388
|
+
const seeds = [];
|
|
389
|
+
for (const [re] of patternEntries) {
|
|
390
|
+
const sampled = sampleFromPattern(re.source, 0);
|
|
391
|
+
if (sampled !== void 0 && sampled.length > 0)
|
|
392
|
+
seeds.push(vary(sampled));
|
|
393
|
+
}
|
|
394
|
+
const propertyNames = isSchemaObject(schema) && hasPropertyNames(schema) ? schema.propertyNames : void 0;
|
|
395
|
+
if (propertyNames !== void 0 && isSchemaObject(propertyNames) && hasPattern(propertyNames)) {
|
|
396
|
+
const sampled = sampleFromPattern(propertyNames.pattern, 0);
|
|
397
|
+
if (sampled !== void 0 && sampled.length > 0)
|
|
398
|
+
seeds.push(vary(sampled));
|
|
399
|
+
}
|
|
400
|
+
seeds.push(vary("extra"));
|
|
401
|
+
for (const seed of seeds) {
|
|
402
|
+
if (!nameCheck || nameCheck(seed))
|
|
403
|
+
return seed;
|
|
404
|
+
}
|
|
405
|
+
return void 0;
|
|
505
406
|
};
|
|
506
|
-
/** Drops non-required keys until `out` has at most `max` properties. */
|
|
507
407
|
const enforceMaxProperties = (out, schema, max) => {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
for (const key of Object.keys(out)) {
|
|
519
|
-
if (Object.keys(out).length <= max)
|
|
520
|
-
break;
|
|
521
|
-
if (!protectedKeys.has(key))
|
|
522
|
-
delete out[key];
|
|
408
|
+
if (Object.keys(out).length <= max)
|
|
409
|
+
return;
|
|
410
|
+
const protectedKeys = new Set(hasRequired(schema) ? schema.required : []);
|
|
411
|
+
if (hasDependentRequired(schema)) {
|
|
412
|
+
for (const [trigger, deps] of Object.entries(schema.dependentRequired)) {
|
|
413
|
+
if (trigger in out)
|
|
414
|
+
for (const dep of deps)
|
|
415
|
+
protectedKeys.add(dep);
|
|
523
416
|
}
|
|
417
|
+
}
|
|
418
|
+
for (const key of Object.keys(out)) {
|
|
419
|
+
if (Object.keys(out).length <= max)
|
|
420
|
+
break;
|
|
421
|
+
if (!protectedKeys.has(key))
|
|
422
|
+
delete out[key];
|
|
423
|
+
}
|
|
524
424
|
};
|
|
525
|
-
/**
|
|
526
|
-
* Picks a number satisfying the node's bounds and `multipleOf`. Starts at the
|
|
527
|
-
* lower bound (or 0 when unbounded), nudges past an exclusive bound, then rounds
|
|
528
|
-
* up to the nearest multiple. An unsatisfiable range (e.g. `minimum > maximum`)
|
|
529
|
-
* can't be met and falls back to the lower bound.
|
|
530
|
-
*/
|
|
531
425
|
const deriveNumber = (schema, isInteger) => {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
value = Math.ceil(value / m - 1e-9) * m;
|
|
550
|
-
// Rounding up can overshoot the upper bound; drop to the largest multiple
|
|
551
|
-
// that fits. (If even that falls below `lo`, the range has no multiple — an
|
|
552
|
-
// unsatisfiable schema — and we return the in-range candidate as best effort.)
|
|
553
|
-
if (value > hi && Number.isFinite(hi))
|
|
554
|
-
value = Math.floor(hi / m + 1e-9) * m;
|
|
555
|
-
}
|
|
556
|
-
// `+ 0` normalizes a `-0` (which `Math.ceil`/`Math.floor` can produce) to `0`.
|
|
557
|
-
return (isInteger ? Math.round(value) : value) + 0;
|
|
426
|
+
const step = isInteger ? 1 : 0.5;
|
|
427
|
+
let lo = -Infinity;
|
|
428
|
+
if (hasMinimum(schema))
|
|
429
|
+
lo = Math.max(lo, schema.minimum);
|
|
430
|
+
if (hasExclusiveMinimum(schema))
|
|
431
|
+
lo = Math.max(lo, schema.exclusiveMinimum + step);
|
|
432
|
+
const hi = hasMaximum(schema) ? schema.maximum : hasExclusiveMaximum(schema) ? schema.exclusiveMaximum - step : Number.POSITIVE_INFINITY;
|
|
433
|
+
let value = Number.isFinite(lo) ? lo : Number.isFinite(hi) ? Math.min(0, hi) : 0;
|
|
434
|
+
if (isInteger)
|
|
435
|
+
value = Math.ceil(value);
|
|
436
|
+
if (hasMultipleOf(schema) && schema.multipleOf > 0) {
|
|
437
|
+
const m = schema.multipleOf;
|
|
438
|
+
value = Math.ceil(value / m - 1e-9) * m;
|
|
439
|
+
if (value > hi && Number.isFinite(hi))
|
|
440
|
+
value = Math.floor(hi / m + 1e-9) * m;
|
|
441
|
+
}
|
|
442
|
+
return (isInteger ? Math.round(value) : value) + 0;
|
|
558
443
|
};
|
|
559
|
-
/**
|
|
560
|
-
* Derives an array value. A tuple schema (`prefixItems`, or the draft-07
|
|
561
|
-
* array-form `items`) derives one value per position; a uniform array repeats a
|
|
562
|
-
* single item value. The count is clamped into `[minItems, maxItems]` so a
|
|
563
|
-
* `maxItems: 0` yields `[]` and a `minItems` is always met.
|
|
564
|
-
*/
|
|
565
444
|
const deriveArray = (schema, rootSchema, seen) => {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
// (in 2020-12 additional items past `prefixItems` are unconstrained unless
|
|
583
|
-
// `items: false`) with a plain `null`.
|
|
584
|
-
const itemsClosed = schema['items'] === false;
|
|
585
|
-
while (tuple.length < min && tuple.length < max) {
|
|
586
|
-
if (rest !== undefined)
|
|
587
|
-
tuple.push(deriveExample(rest, rootSchema, seen));
|
|
588
|
-
else if (itemsClosed)
|
|
589
|
-
break;
|
|
590
|
-
else
|
|
591
|
-
tuple.push(null);
|
|
592
|
-
}
|
|
593
|
-
return tuple.length > max ? tuple.slice(0, max) : tuple;
|
|
445
|
+
const items = hasItems(schema) ? schema.items : void 0;
|
|
446
|
+
const prefixItems = schema["prefixItems"];
|
|
447
|
+
const prefix = Array.isArray(prefixItems) ? prefixItems : Array.isArray(items) ? items : void 0;
|
|
448
|
+
const min = hasMinItems(schema) ? schema.minItems : 0;
|
|
449
|
+
const max = hasMaxItems(schema) ? schema.maxItems : Number.POSITIVE_INFINITY;
|
|
450
|
+
const rest = items !== void 0 && !Array.isArray(items) && isSchemaObject(items) ? items : void 0;
|
|
451
|
+
if (prefix) {
|
|
452
|
+
const tuple = prefix.map((item) => deriveExample(item, rootSchema, seen));
|
|
453
|
+
const itemsClosed = schema["items"] === false;
|
|
454
|
+
while (tuple.length < min && tuple.length < max) {
|
|
455
|
+
if (rest !== void 0)
|
|
456
|
+
tuple.push(deriveExample(rest, rootSchema, seen));
|
|
457
|
+
else if (itemsClosed)
|
|
458
|
+
break;
|
|
459
|
+
else
|
|
460
|
+
tuple.push(null);
|
|
594
461
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
return result;
|
|
462
|
+
return tuple.length > max ? tuple.slice(0, max) : tuple;
|
|
463
|
+
}
|
|
464
|
+
const raw = schema;
|
|
465
|
+
const containsRaw = raw["contains"];
|
|
466
|
+
const contains = containsRaw !== void 0 && isSchemaObject(containsRaw) ? containsRaw : void 0;
|
|
467
|
+
const minContains = contains !== void 0 ? typeof raw["minContains"] === "number" ? raw["minContains"] : 1 : 0;
|
|
468
|
+
const unique = hasUniqueItems(schema) && schema.uniqueItems === true;
|
|
469
|
+
const elem = rest ?? contains;
|
|
470
|
+
const count = Math.min(Math.max(min, minContains, max === 0 ? 0 : 1), max);
|
|
471
|
+
const result = [];
|
|
472
|
+
for (let i = 0; i < count; i++) {
|
|
473
|
+
const itemSchema = contains !== void 0 && i < minContains ? contains : elem;
|
|
474
|
+
const base = itemSchema !== void 0 ? deriveExample(itemSchema, rootSchema, seen) : null;
|
|
475
|
+
result.push(unique ? distinctify(base, i, itemSchema) : base);
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
612
478
|
};
|
|
613
|
-
/**
|
|
614
|
-
* Returns a value distinct from earlier ones for index `i`, used to satisfy
|
|
615
|
-
* `uniqueItems`, while staying within the item schema's constraints: numbers step
|
|
616
|
-
* by `multipleOf` (so the perturbed values remain valid multiples) rather than by
|
|
617
|
-
* 1, strings are suffixed, booleans alternated. Values that can't be cheaply
|
|
618
|
-
* varied are returned as-is (a best-effort the generated `fast-check` arbitrary
|
|
619
|
-
* covers fully).
|
|
620
|
-
*/
|
|
621
479
|
const distinctify = (base, i, itemSchema) => {
|
|
622
|
-
|
|
623
|
-
return base;
|
|
624
|
-
if (typeof base === 'number') {
|
|
625
|
-
const step = itemSchema && isSchemaObject(itemSchema) && hasMultipleOf(itemSchema) && itemSchema.multipleOf > 0
|
|
626
|
-
? itemSchema.multipleOf
|
|
627
|
-
: 1;
|
|
628
|
-
return base + i * step;
|
|
629
|
-
}
|
|
630
|
-
if (typeof base === 'string')
|
|
631
|
-
return `${base}${i}`;
|
|
632
|
-
if (typeof base === 'boolean')
|
|
633
|
-
return i % 2 === 1 ? !base : base;
|
|
480
|
+
if (i === 0)
|
|
634
481
|
return base;
|
|
482
|
+
if (typeof base === "number") {
|
|
483
|
+
const step = itemSchema && isSchemaObject(itemSchema) && hasMultipleOf(itemSchema) && itemSchema.multipleOf > 0 ? itemSchema.multipleOf : 1;
|
|
484
|
+
return base + i * step;
|
|
485
|
+
}
|
|
486
|
+
if (typeof base === "string")
|
|
487
|
+
return `${base}${i}`;
|
|
488
|
+
if (typeof base === "boolean")
|
|
489
|
+
return i % 2 === 1 ? !base : base;
|
|
490
|
+
return base;
|
|
635
491
|
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
['minProperties', 'max'],
|
|
648
|
-
['maximum', 'min'],
|
|
649
|
-
['exclusiveMaximum', 'min'],
|
|
650
|
-
['maxLength', 'min'],
|
|
651
|
-
['maxItems', 'min'],
|
|
652
|
-
['maxProperties', 'min'],
|
|
492
|
+
const TIGHTEST = /* @__PURE__ */ new Map([
|
|
493
|
+
["minimum", "max"],
|
|
494
|
+
["exclusiveMinimum", "max"],
|
|
495
|
+
["minLength", "max"],
|
|
496
|
+
["minItems", "max"],
|
|
497
|
+
["minProperties", "max"],
|
|
498
|
+
["maximum", "min"],
|
|
499
|
+
["exclusiveMaximum", "min"],
|
|
500
|
+
["maxLength", "min"],
|
|
501
|
+
["maxItems", "min"],
|
|
502
|
+
["maxProperties", "min"]
|
|
653
503
|
]);
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
bucket.push(propSchema);
|
|
673
|
-
else
|
|
674
|
-
properties[prop] = [propSchema];
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
else if (key === 'required' && Array.isArray(value)) {
|
|
678
|
-
for (const r of value)
|
|
679
|
-
required.add(r);
|
|
680
|
-
}
|
|
681
|
-
else if (key === 'enum' && Array.isArray(value)) {
|
|
682
|
-
// A value must be in *every* branch's enum, so intersect rather than let
|
|
683
|
-
// a later branch's enum replace an earlier one (which could pick a member
|
|
684
|
-
// the earlier branch rejects).
|
|
685
|
-
merged['enum'] = Array.isArray(merged['enum'])
|
|
686
|
-
? merged['enum'].filter((member) => value.includes(member))
|
|
687
|
-
: value;
|
|
688
|
-
}
|
|
689
|
-
else if (TIGHTEST.has(key) && typeof value === 'number' && typeof merged[key] === 'number') {
|
|
690
|
-
// Numeric bounds from different branches combine to the tightest one.
|
|
691
|
-
merged[key] = TIGHTEST.get(key) === 'max' ? Math.max(merged[key], value) : Math.min(merged[key], value);
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
merged[key] = value;
|
|
695
|
-
}
|
|
504
|
+
const mergeAllOf = (schema) => {
|
|
505
|
+
const branches = hasAllOf(schema) ? schema.allOf : [];
|
|
506
|
+
const merged = {};
|
|
507
|
+
const properties = {};
|
|
508
|
+
const required = /* @__PURE__ */ new Set();
|
|
509
|
+
for (const branch of [...branches, schema]) {
|
|
510
|
+
if (!isSchemaObject(branch))
|
|
511
|
+
continue;
|
|
512
|
+
for (const [key, value] of Object.entries(branch)) {
|
|
513
|
+
if (key === "allOf")
|
|
514
|
+
continue;
|
|
515
|
+
if (key === "properties" && value && typeof value === "object") {
|
|
516
|
+
for (const [prop, propSchema] of Object.entries(value)) {
|
|
517
|
+
const bucket = properties[prop];
|
|
518
|
+
if (bucket)
|
|
519
|
+
bucket.push(propSchema);
|
|
520
|
+
else
|
|
521
|
+
properties[prop] = [propSchema];
|
|
696
522
|
}
|
|
523
|
+
} else if (key === "required" && Array.isArray(value)) {
|
|
524
|
+
for (const r of value)
|
|
525
|
+
required.add(r);
|
|
526
|
+
} else if (key === "enum" && Array.isArray(value)) {
|
|
527
|
+
merged["enum"] = Array.isArray(merged["enum"]) ? merged["enum"].filter((member) => value.includes(member)) : value;
|
|
528
|
+
} else if (TIGHTEST.has(key) && typeof value === "number" && typeof merged[key] === "number") {
|
|
529
|
+
merged[key] = TIGHTEST.get(key) === "max" ? Math.max(merged[key], value) : Math.min(merged[key], value);
|
|
530
|
+
} else {
|
|
531
|
+
merged[key] = value;
|
|
532
|
+
}
|
|
697
533
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
534
|
+
}
|
|
535
|
+
const mergedProps = {};
|
|
536
|
+
for (const [prop, schemas] of Object.entries(properties)) {
|
|
537
|
+
mergedProps[prop] = schemas.length === 1 ? schemas[0] : { allOf: schemas };
|
|
538
|
+
}
|
|
539
|
+
if (Object.keys(mergedProps).length > 0)
|
|
540
|
+
merged["properties"] = mergedProps;
|
|
541
|
+
if (required.size > 0)
|
|
542
|
+
merged["required"] = [...required];
|
|
543
|
+
return merged;
|
|
707
544
|
};
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
return `{ ${entries.join(', ')} }`;
|
|
725
|
-
}
|
|
726
|
-
return JSON.stringify(value);
|
|
545
|
+
const serializeValue = (value) => {
|
|
546
|
+
if (typeof value === "bigint")
|
|
547
|
+
return `${value}n`;
|
|
548
|
+
if (value instanceof Date)
|
|
549
|
+
return `new Date(${JSON.stringify(value.toISOString())})`;
|
|
550
|
+
if (Array.isArray(value))
|
|
551
|
+
return `[${value.map(serializeValue).join(", ")}]`;
|
|
552
|
+
if (value !== null && typeof value === "object") {
|
|
553
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).map(([key, v]) => `${JSON.stringify(key)}: ${serializeValue(v)}`);
|
|
554
|
+
return `{ ${entries.join(", ")} }`;
|
|
555
|
+
}
|
|
556
|
+
return JSON.stringify(value);
|
|
557
|
+
};
|
|
558
|
+
const generateExampleConst = (schema, typeName, rootSchema) => {
|
|
559
|
+
const value = deriveExample(schema, rootSchema);
|
|
560
|
+
return `export const ${exampleName(typeName)}: ${typeName} = ${serializeValue(value)}`;
|
|
727
561
|
};
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
* generateExampleConst({ type: 'object', properties: { name: { type: 'string' } } }, 'Info')
|
|
734
|
-
* // export const infoExample: Info = { "name": "string" }
|
|
735
|
-
* ```
|
|
736
|
-
*/
|
|
737
|
-
export const generateExampleConst = (schema, typeName, rootSchema) => {
|
|
738
|
-
const value = deriveExample(schema, rootSchema);
|
|
739
|
-
return `export const ${exampleName(typeName)}: ${typeName} = ${serializeValue(value)}`;
|
|
562
|
+
export {
|
|
563
|
+
deriveExample,
|
|
564
|
+
generateExampleConst,
|
|
565
|
+
mergeAllOf,
|
|
566
|
+
serializeValue
|
|
740
567
|
};
|