@lovrozagar/oat 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1936
- package/dist/cli.js +326 -60
- package/dist/config/define-config.d.ts +15 -1
- package/dist/config/define-config.d.ts.map +1 -1
- package/dist/config/define-config.js.map +1 -1
- package/dist/config/validate.d.ts +12 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +86 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/conformance/effects.d.ts +9 -0
- package/dist/conformance/effects.d.ts.map +1 -0
- package/dist/conformance/effects.js +276 -0
- package/dist/conformance/effects.js.map +1 -0
- package/dist/conformance/expected-failures.d.ts +18 -0
- package/dist/conformance/expected-failures.d.ts.map +1 -0
- package/dist/conformance/expected-failures.js +48 -0
- package/dist/conformance/expected-failures.js.map +1 -0
- package/dist/conformance/feature-gate.d.ts +7 -0
- package/dist/conformance/feature-gate.d.ts.map +1 -0
- package/dist/conformance/feature-gate.js +428 -0
- package/dist/conformance/feature-gate.js.map +1 -0
- package/dist/conformance/fuzz.d.ts +108 -0
- package/dist/conformance/fuzz.d.ts.map +1 -0
- package/dist/conformance/fuzz.js +246 -0
- package/dist/conformance/fuzz.js.map +1 -0
- package/dist/conformance/leaks.d.ts +10 -0
- package/dist/conformance/leaks.d.ts.map +1 -0
- package/dist/conformance/leaks.js +23 -0
- package/dist/conformance/leaks.js.map +1 -0
- package/dist/conformance/pool.d.ts +49 -0
- package/dist/conformance/pool.d.ts.map +1 -0
- package/dist/conformance/pool.js +75 -0
- package/dist/conformance/pool.js.map +1 -0
- package/dist/conformance/rate-limit.d.ts +7 -0
- package/dist/conformance/rate-limit.d.ts.map +1 -0
- package/dist/conformance/rate-limit.js +372 -0
- package/dist/conformance/rate-limit.js.map +1 -0
- package/dist/conformance/scope.d.ts +15 -0
- package/dist/conformance/scope.d.ts.map +1 -0
- package/dist/conformance/scope.js +78 -0
- package/dist/conformance/scope.js.map +1 -0
- package/dist/conformance/seed-contract.d.ts +11 -0
- package/dist/conformance/seed-contract.d.ts.map +1 -0
- package/dist/conformance/seed-contract.js +680 -0
- package/dist/conformance/seed-contract.js.map +1 -0
- package/dist/conformance/shapes.d.ts +35 -0
- package/dist/conformance/shapes.d.ts.map +1 -0
- package/dist/conformance/shapes.js +162 -0
- package/dist/conformance/shapes.js.map +1 -0
- package/dist/conformance/specs.d.ts +21 -0
- package/dist/conformance/specs.d.ts.map +1 -0
- package/dist/conformance/specs.js +306 -0
- package/dist/conformance/specs.js.map +1 -0
- package/dist/conformance/suite.d.ts +209 -0
- package/dist/conformance/suite.d.ts.map +1 -0
- package/dist/conformance/suite.js +1538 -0
- package/dist/conformance/suite.js.map +1 -0
- package/dist/conformance/unique.d.ts +7 -0
- package/dist/conformance/unique.d.ts.map +1 -0
- package/dist/conformance/unique.js +586 -0
- package/dist/conformance/unique.js.map +1 -0
- package/dist/conformance/worker.d.ts +3 -0
- package/dist/conformance/worker.d.ts.map +1 -0
- package/dist/conformance/worker.js +27 -0
- package/dist/conformance/worker.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +6 -2
- package/dist/reference/defects.d.ts +71 -1
- package/dist/reference/defects.d.ts.map +1 -1
- package/dist/reference/defects.js +74 -8
- package/dist/reference/defects.js.map +1 -1
- package/dist/reference/dialect.d.ts +6 -4
- package/dist/reference/dialect.d.ts.map +1 -1
- package/dist/reference/dialect.js +1 -0
- package/dist/reference/dialect.js.map +1 -1
- package/dist/reference/http.d.ts +17 -25
- package/dist/reference/http.d.ts.map +1 -1
- package/dist/reference/http.js +574 -224
- package/dist/reference/http.js.map +1 -1
- package/dist/reference/listing.d.ts +51 -0
- package/dist/reference/listing.d.ts.map +1 -0
- package/dist/reference/listing.js +256 -0
- package/dist/reference/listing.js.map +1 -0
- package/dist/reference/model.d.ts +35 -1
- package/dist/reference/model.d.ts.map +1 -1
- package/dist/reference/model.js +42 -0
- package/dist/reference/model.js.map +1 -1
- package/dist/reference/query.d.ts +132 -43
- package/dist/reference/query.d.ts.map +1 -1
- package/dist/reference/query.js +397 -315
- package/dist/reference/query.js.map +1 -1
- package/dist/reference/shapes.d.ts +60 -0
- package/dist/reference/shapes.d.ts.map +1 -0
- package/dist/reference/shapes.js +142 -0
- package/dist/reference/shapes.js.map +1 -0
- package/dist/reference/spec.d.ts +9 -2
- package/dist/reference/spec.d.ts.map +1 -1
- package/dist/reference/spec.js +291 -71
- package/dist/reference/spec.js.map +1 -1
- package/dist/reference/store-api.d.ts +14 -43
- package/dist/reference/store-api.d.ts.map +1 -1
- package/dist/reference/store-api.js +8 -58
- package/dist/reference/store-api.js.map +1 -1
- package/dist/reference/stores/memory.d.ts +12 -15
- package/dist/reference/stores/memory.d.ts.map +1 -1
- package/dist/reference/stores/memory.js +22 -70
- package/dist/reference/stores/memory.js.map +1 -1
- package/dist/reference/stores/postgres.d.ts +11 -23
- package/dist/reference/stores/postgres.d.ts.map +1 -1
- package/dist/reference/stores/postgres.js +53 -334
- package/dist/reference/stores/postgres.js.map +1 -1
- package/dist/reference/stores/sql-compile.d.ts +36 -0
- package/dist/reference/stores/sql-compile.d.ts.map +1 -0
- package/dist/reference/stores/sql-compile.js +95 -0
- package/dist/reference/stores/sql-compile.js.map +1 -0
- package/dist/reference/stores/sqlite.d.ts +12 -27
- package/dist/reference/stores/sqlite.d.ts.map +1 -1
- package/dist/reference/stores/sqlite.js +54 -355
- package/dist/reference/stores/sqlite.js.map +1 -1
- package/dist/report/console.d.ts +2 -1
- package/dist/report/console.d.ts.map +1 -1
- package/dist/report/console.js +56 -10
- package/dist/report/console.js.map +1 -1
- package/dist/report/matrix.d.ts +10 -13
- package/dist/report/matrix.d.ts.map +1 -1
- package/dist/report/matrix.js +82 -18
- package/dist/report/matrix.js.map +1 -1
- package/dist/report/render.d.ts +18 -1
- package/dist/report/render.d.ts.map +1 -1
- package/dist/report/render.js +169 -14
- package/dist/report/render.js.map +1 -1
- package/dist/runtime/async.d.ts +16 -3
- package/dist/runtime/async.d.ts.map +1 -1
- package/dist/runtime/async.js +24 -19
- package/dist/runtime/async.js.map +1 -1
- package/dist/runtime/auth.d.ts +2 -9
- package/dist/runtime/auth.d.ts.map +1 -1
- package/dist/runtime/auth.js +38 -62
- package/dist/runtime/auth.js.map +1 -1
- package/dist/runtime/body.d.ts +15 -3
- package/dist/runtime/body.d.ts.map +1 -1
- package/dist/runtime/body.js +61 -12
- package/dist/runtime/body.js.map +1 -1
- package/dist/runtime/check-costs.d.ts +15 -0
- package/dist/runtime/check-costs.d.ts.map +1 -0
- package/dist/runtime/check-costs.js +114 -0
- package/dist/runtime/check-costs.js.map +1 -0
- package/dist/runtime/check-ids.d.ts +15 -0
- package/dist/runtime/check-ids.d.ts.map +1 -0
- package/dist/runtime/check-ids.js +140 -0
- package/dist/runtime/check-ids.js.map +1 -0
- package/dist/runtime/checks.d.ts +122 -18
- package/dist/runtime/checks.d.ts.map +1 -1
- package/dist/runtime/checks.js +3437 -1933
- package/dist/runtime/checks.js.map +1 -1
- package/dist/runtime/client.d.ts +112 -0
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +271 -39
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/cookies.d.ts +16 -2
- package/dist/runtime/cookies.d.ts.map +1 -1
- package/dist/runtime/cookies.js +83 -22
- package/dist/runtime/cookies.js.map +1 -1
- package/dist/runtime/exchanges.d.ts +4 -5
- package/dist/runtime/exchanges.d.ts.map +1 -1
- package/dist/runtime/exchanges.js +33 -55
- package/dist/runtime/exchanges.js.map +1 -1
- package/dist/runtime/exit.d.ts +40 -0
- package/dist/runtime/exit.d.ts.map +1 -0
- package/dist/runtime/exit.js +48 -0
- package/dist/runtime/exit.js.map +1 -0
- package/dist/runtime/feature-gate.d.ts +0 -9
- package/dist/runtime/feature-gate.d.ts.map +1 -1
- package/dist/runtime/feature-gate.js +0 -21
- package/dist/runtime/feature-gate.js.map +1 -1
- package/dist/runtime/finding.d.ts +61 -7
- package/dist/runtime/finding.d.ts.map +1 -1
- package/dist/runtime/finding.js +74 -12
- package/dist/runtime/finding.js.map +1 -1
- package/dist/runtime/fixture.d.ts +35 -28
- package/dist/runtime/fixture.d.ts.map +1 -1
- package/dist/runtime/fixture.js +92 -731
- package/dist/runtime/fixture.js.map +1 -1
- package/dist/runtime/generate.d.ts +63 -0
- package/dist/runtime/generate.d.ts.map +1 -0
- package/dist/runtime/generate.js +635 -0
- package/dist/runtime/generate.js.map +1 -0
- package/dist/runtime/headers.d.ts +6 -0
- package/dist/runtime/headers.d.ts.map +1 -0
- package/dist/runtime/headers.js +16 -0
- package/dist/runtime/headers.js.map +1 -0
- package/dist/runtime/path.d.ts +7 -0
- package/dist/runtime/path.d.ts.map +1 -0
- package/dist/runtime/path.js +44 -0
- package/dist/runtime/path.js.map +1 -0
- package/dist/runtime/payloads.d.ts +2 -0
- package/dist/runtime/payloads.d.ts.map +1 -1
- package/dist/runtime/payloads.js +2 -1
- package/dist/runtime/payloads.js.map +1 -1
- package/dist/runtime/rate-limit.d.ts +6 -4
- package/dist/runtime/rate-limit.d.ts.map +1 -1
- package/dist/runtime/rate-limit.js +22 -33
- package/dist/runtime/rate-limit.js.map +1 -1
- package/dist/runtime/redact.d.ts +24 -0
- package/dist/runtime/redact.d.ts.map +1 -0
- package/dist/runtime/redact.js +98 -0
- package/dist/runtime/redact.js.map +1 -0
- package/dist/runtime/run.d.ts +51 -2
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js +463 -195
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/runs.d.ts +5 -0
- package/dist/runtime/runs.d.ts.map +1 -1
- package/dist/runtime/runs.js +7 -1
- package/dist/runtime/runs.js.map +1 -1
- package/dist/runtime/scope.d.ts +163 -0
- package/dist/runtime/scope.d.ts.map +1 -0
- package/dist/runtime/scope.js +423 -0
- package/dist/runtime/scope.js.map +1 -0
- package/dist/runtime/teardown.d.ts +17 -4
- package/dist/runtime/teardown.d.ts.map +1 -1
- package/dist/runtime/teardown.js +49 -16
- package/dist/runtime/teardown.js.map +1 -1
- package/dist/runtime/transcript.d.ts +34 -2
- package/dist/runtime/transcript.d.ts.map +1 -1
- package/dist/runtime/transcript.js +119 -24
- package/dist/runtime/transcript.js.map +1 -1
- package/dist/runtime/unique.d.ts +2 -11
- package/dist/runtime/unique.d.ts.map +1 -1
- package/dist/runtime/unique.js +27 -53
- package/dist/runtime/unique.js.map +1 -1
- package/dist/runtime/validate.d.ts +13 -2
- package/dist/runtime/validate.d.ts.map +1 -1
- package/dist/runtime/validate.js +51 -31
- package/dist/runtime/validate.js.map +1 -1
- package/dist/runtime/wait.d.ts +2 -1
- package/dist/runtime/wait.d.ts.map +1 -1
- package/dist/runtime/wait.js +2 -36
- package/dist/runtime/wait.js.map +1 -1
- package/dist/runtime/world.d.ts +31 -3
- package/dist/runtime/world.d.ts.map +1 -1
- package/dist/runtime/world.js +113 -32
- package/dist/runtime/world.js.map +1 -1
- package/dist/spec/collection.d.ts +8 -2
- package/dist/spec/collection.d.ts.map +1 -1
- package/dist/spec/collection.js +47 -7
- package/dist/spec/collection.js.map +1 -1
- package/dist/spec/extensions.d.ts +5 -1
- package/dist/spec/extensions.d.ts.map +1 -1
- package/dist/spec/extensions.js +9 -2
- package/dist/spec/extensions.js.map +1 -1
- package/dist/spec/graph.d.ts +46 -0
- package/dist/spec/graph.d.ts.map +1 -1
- package/dist/spec/graph.js +175 -11
- package/dist/spec/graph.js.map +1 -1
- package/dist/spec/load.d.ts +25 -5
- package/dist/spec/load.d.ts.map +1 -1
- package/dist/spec/load.js +160 -59
- package/dist/spec/load.js.map +1 -1
- package/dist/spec/query-capabilities.d.ts +4 -0
- package/dist/spec/query-capabilities.d.ts.map +1 -1
- package/dist/spec/query-capabilities.js +6 -0
- package/dist/spec/query-capabilities.js.map +1 -1
- package/dist/spec/schema.d.ts +20 -0
- package/dist/spec/schema.d.ts.map +1 -0
- package/dist/spec/schema.js +118 -0
- package/dist/spec/schema.js.map +1 -0
- package/dist/spec/tag-schema.d.ts +12 -0
- package/dist/spec/tag-schema.d.ts.map +1 -0
- package/dist/spec/tag-schema.js +143 -0
- package/dist/spec/tag-schema.js.map +1 -0
- package/dist/spec/types.js +17 -1
- package/dist/spec/types.js.map +1 -1
- package/docs/api.md +59 -0
- package/docs/assumptions.md +99 -0
- package/docs/checks.md +183 -0
- package/docs/commands.md +214 -0
- package/docs/configuration.md +650 -0
- package/docs/model.md +184 -0
- package/docs/reference-defects.md +94 -0
- package/docs/reports.md +211 -0
- package/docs/tags.md +400 -0
- package/package.json +3 -1
package/dist/runtime/fixture.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema-driven fixture generation.
|
|
2
|
+
* Schema-driven fixture generation: the discriminating cohort.
|
|
3
3
|
*
|
|
4
4
|
* Valid-random data cannot test a query engine: `ilike` needs a substring, `order` needs a total
|
|
5
|
-
* order, escaping needs metacharacters. oat seeds a *
|
|
6
|
-
*
|
|
5
|
+
* order, escaping needs metacharacters. oat seeds a *cohort* — one record per variant, each shaped
|
|
6
|
+
* so some query assertion has signal — and derives everything from a seed so a failing run is
|
|
7
7
|
* exactly reproducible.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The values themselves come from `generate.ts`, the one place oat invents data, from the request
|
|
10
|
+
* schema as `spec/schema.ts` normalizes it. Every body is checked against that schema before it
|
|
11
|
+
* leaves; one that cannot be made valid is a gap naming the operation and the field, never a
|
|
12
|
+
* request the document forbids.
|
|
12
13
|
*/
|
|
13
|
-
import {
|
|
14
|
+
import { normalizeSchema } from "../spec/schema.js";
|
|
15
|
+
import { generate, isVacuous } from "./generate.js";
|
|
16
|
+
import { instanceErrors } from "./validate.js";
|
|
17
|
+
export { isVacuous } from "./generate.js";
|
|
14
18
|
export const COHORT = [
|
|
15
19
|
"baseline",
|
|
16
20
|
"lexical-first",
|
|
@@ -30,32 +34,27 @@ export function mulberry32(seed) {
|
|
|
30
34
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
31
35
|
};
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"lexical-last": "zzz last alphabetically",
|
|
38
|
-
metacharacter: "100% _off_ *everything*",
|
|
39
|
-
"null-heavy": "null heavy record",
|
|
40
|
-
unicode: UNICODE_COHORT_STRING,
|
|
41
|
-
};
|
|
42
|
-
const EMAIL_RE = /^[^@]+@[^@]+\.[^@]+$/;
|
|
43
|
-
const MAX_DEPTH = 8;
|
|
44
|
-
const PATTERN_ATTEMPTS = 48;
|
|
45
|
-
/** Thrown when a walk still overflows; the pointer names the node that blew the stack. */
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when no valid body can be built for an operation. The pointer names the field; the
|
|
39
|
+
* reason says why — and the fix is `hooks.resolveInput`, which can supply what the schema cannot.
|
|
40
|
+
*/
|
|
46
41
|
export class FixtureOverflow extends Error {
|
|
47
42
|
operationId;
|
|
48
43
|
pointer;
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
reason;
|
|
45
|
+
constructor(operationId, pointer, reason) {
|
|
46
|
+
super(reason === undefined
|
|
47
|
+
? `fixture generation overflow on ${operationId} (${pointer})`
|
|
48
|
+
: `cannot generate a valid body for ${operationId} at ${pointer}: ${reason} — supply one with hooks.resolveInput`);
|
|
51
49
|
this.operationId = operationId;
|
|
52
50
|
this.pointer = pointer;
|
|
51
|
+
this.reason = reason;
|
|
53
52
|
this.name = "FixtureOverflow";
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
export function overflowFrom(error, operationId, pointer = "/") {
|
|
57
56
|
if (error instanceof FixtureOverflow) {
|
|
58
|
-
return new FixtureOverflow(operationId, error.pointer === "/" ? pointer : error.pointer);
|
|
57
|
+
return new FixtureOverflow(operationId, error.pointer === "/" ? pointer : error.pointer, error.reason);
|
|
59
58
|
}
|
|
60
59
|
return new FixtureOverflow(operationId, pointer);
|
|
61
60
|
}
|
|
@@ -64,28 +63,85 @@ export function isOverflowError(error) {
|
|
|
64
63
|
return true;
|
|
65
64
|
return error instanceof RangeError;
|
|
66
65
|
}
|
|
66
|
+
function requestSchema(schema) {
|
|
67
|
+
return typeof schema === "object" && schema !== null ? normalizeSchema(schema, { direction: "request" }) : schema;
|
|
68
|
+
}
|
|
69
|
+
/** One body per variant, each valid for the schema — or a `FixtureOverflow` naming what is not. */
|
|
70
|
+
export function buildCohort(bodySchema, seed, variants = COHORT, operationId = "unknown", options = {}) {
|
|
71
|
+
const schema = requestSchema(bodySchema);
|
|
72
|
+
return variants.map((variant, index) => {
|
|
73
|
+
const result = generate(schema, {
|
|
74
|
+
...(options.defs === undefined ? {} : { defs: options.defs }),
|
|
75
|
+
...(options.distinct === undefined ? {} : { distinct: options.distinct }),
|
|
76
|
+
...(options.nonce === undefined ? {} : { nonce: options.nonce }),
|
|
77
|
+
index,
|
|
78
|
+
rand: mulberry32(seed + index * 7919),
|
|
79
|
+
variant,
|
|
80
|
+
});
|
|
81
|
+
if (!result.ok)
|
|
82
|
+
throw new FixtureOverflow(operationId, result.pointer, result.reason);
|
|
83
|
+
const body = isObject(result.value) ? result.value : {};
|
|
84
|
+
/* The self-check: a body oat invented must satisfy the document before it is sent. */
|
|
85
|
+
if (typeof schema === "object" && !isVacuous(schema)) {
|
|
86
|
+
const errors = instanceErrors(schema, body, options.defs);
|
|
87
|
+
if (errors.length > 0)
|
|
88
|
+
throw new FixtureOverflow(operationId, "/", errors.slice(0, 3).join("; "));
|
|
89
|
+
}
|
|
90
|
+
return { body, variant };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
67
93
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* When the document cannot express two distinct values, extra variants are dropped and the
|
|
71
|
-
* caller records a gap.
|
|
94
|
+
* One body, with the required fields that could not be made rather than an exception — for
|
|
95
|
+
* callers that report each gap themselves. Optional fields that cannot be made are left out.
|
|
72
96
|
*/
|
|
73
|
-
export function
|
|
97
|
+
export function generateBody(bodySchema, variant, rand, index, operationId = "unknown") {
|
|
98
|
+
void operationId;
|
|
99
|
+
const schema = requestSchema(bodySchema);
|
|
100
|
+
const result = generate(schema, { index, rand, variant });
|
|
101
|
+
if (result.ok)
|
|
102
|
+
return { body: isObject(result.value) ? result.value : {}, missingRequired: [] };
|
|
103
|
+
/* Without the failing field, the rest may still be made: drop it from `required` and retry. */
|
|
104
|
+
const field = result.pointer.split("/")[1];
|
|
105
|
+
if (field !== undefined && field !== "" && typeof schema === "object") {
|
|
106
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((name) => name !== field) : [];
|
|
107
|
+
const properties = { ...schema.properties };
|
|
108
|
+
delete properties[field];
|
|
109
|
+
const rest = generateBody({ ...schema, properties, required }, variant, rand, index, operationId);
|
|
110
|
+
return { body: rest.body, missingRequired: [result.pointer, ...rest.missingRequired] };
|
|
111
|
+
}
|
|
112
|
+
return { body: {}, missingRequired: [result.pointer] };
|
|
113
|
+
}
|
|
114
|
+
function isObject(value) {
|
|
115
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Makes unique-set tuples differ across cohort members, by drawing new values from each
|
|
119
|
+
* column's schema. When the document cannot express two distinct values — a two-value enum and
|
|
120
|
+
* seven members — the extra members are dropped and the caller records a gap.
|
|
121
|
+
*/
|
|
122
|
+
export function ensureDistinctUniqueValues(members, sets, schema, options = {}) {
|
|
74
123
|
if (sets.length === 0 || members.length <= 1)
|
|
75
124
|
return { gap: null, members };
|
|
76
|
-
const
|
|
125
|
+
const normalized = requestSchema(schema);
|
|
126
|
+
const properties = typeof normalized === "object" && isObject(normalized.properties) ? normalized.properties : {};
|
|
77
127
|
const kept = [];
|
|
78
128
|
for (const member of members) {
|
|
79
129
|
const body = { ...member.body };
|
|
80
130
|
let ok = true;
|
|
81
131
|
for (const set of sets) {
|
|
82
|
-
let
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
ok = false;
|
|
132
|
+
for (let attempt = 0; kept.some((prior) => sameUniqueTuple(prior.body, body, set)) && attempt < 16; attempt++) {
|
|
133
|
+
const col = set.find((name) => Object.hasOwn(body, name) && isObject(properties[name]));
|
|
134
|
+
if (col === undefined)
|
|
86
135
|
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
136
|
+
const redrawn = generate({ properties: { [col]: properties[col] }, required: [col], type: "object" }, {
|
|
137
|
+
...(options.defs === undefined ? {} : { defs: options.defs }),
|
|
138
|
+
distinct: new Set([col]),
|
|
139
|
+
index: kept.length * 17 + attempt + 1,
|
|
140
|
+
nonce: `${options.nonce ?? ""}${attempt}`,
|
|
141
|
+
});
|
|
142
|
+
if (!redrawn.ok)
|
|
143
|
+
break;
|
|
144
|
+
body[col] = redrawn.value[col];
|
|
89
145
|
}
|
|
90
146
|
if (kept.some((prior) => sameUniqueTuple(prior.body, body, set)))
|
|
91
147
|
ok = false;
|
|
@@ -103,8 +159,7 @@ export function ensureDistinctUniqueValues(members, sets, schema) {
|
|
|
103
159
|
}
|
|
104
160
|
function uniqueDistinctGap(sets) {
|
|
105
161
|
const shown = sets.map((set) => `[${set.join(", ")}]`).join("; ");
|
|
106
|
-
return
|
|
107
|
-
"maxLength/pattern; extra variants skipped");
|
|
162
|
+
return `x-unique cannot express distinct values for ${shown} within the schema; extra variants skipped`;
|
|
108
163
|
}
|
|
109
164
|
function sameUniqueTuple(a, b, set) {
|
|
110
165
|
const cols = set.filter((col) => Object.hasOwn(a, col) || Object.hasOwn(b, col));
|
|
@@ -112,698 +167,4 @@ function sameUniqueTuple(a, b, set) {
|
|
|
112
167
|
return false;
|
|
113
168
|
return cols.every((col) => JSON.stringify(a[col]) === JSON.stringify(b[col]));
|
|
114
169
|
}
|
|
115
|
-
function nudgeUniqueSet(body, set, schema, token) {
|
|
116
|
-
for (const col of set) {
|
|
117
|
-
if (!Object.hasOwn(body, col))
|
|
118
|
-
continue;
|
|
119
|
-
const current = body[col];
|
|
120
|
-
const field = propertySchema(schema, col);
|
|
121
|
-
if (typeof current === "number" && Number.isFinite(current)) {
|
|
122
|
-
body[col] = current + token;
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
if (Array.isArray(field?.enum) && field.enum.length > 1) {
|
|
126
|
-
const next = field.enum[token % field.enum.length];
|
|
127
|
-
if (next !== current) {
|
|
128
|
-
body[col] = next;
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (typeof current !== "string")
|
|
134
|
-
continue;
|
|
135
|
-
const nudged = suffixWithinConstraints(current, String(token), field);
|
|
136
|
-
if (nudged !== current) {
|
|
137
|
-
body[col] = nudged;
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
function propertySchema(schema, name) {
|
|
144
|
-
if (schema === null)
|
|
145
|
-
return undefined;
|
|
146
|
-
const props = schema.properties;
|
|
147
|
-
if (props === null || typeof props !== "object")
|
|
148
|
-
return undefined;
|
|
149
|
-
const value = props[name];
|
|
150
|
-
return value !== null && typeof value === "object" ? value : undefined;
|
|
151
|
-
}
|
|
152
|
-
function suffixWithinConstraints(value, token, schema) {
|
|
153
|
-
const max = typeof schema?.maxLength === "number" ? schema.maxLength : undefined;
|
|
154
|
-
const pattern = typeof schema?.pattern === "string" ? schema.pattern : undefined;
|
|
155
|
-
const suffix = `-${token}`;
|
|
156
|
-
let next = `${value}${suffix}`;
|
|
157
|
-
if (max !== undefined && next.length > max) {
|
|
158
|
-
const keep = Math.max(0, max - suffix.length);
|
|
159
|
-
next = `${value.slice(0, keep)}${suffix}`;
|
|
160
|
-
if (next.length > max)
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
if (pattern !== undefined) {
|
|
164
|
-
try {
|
|
165
|
-
if (!new RegExp(pattern, "u").test(next))
|
|
166
|
-
return value;
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return next;
|
|
173
|
-
}
|
|
174
|
-
export function buildCohort(bodySchema, seed, variants = COHORT, operationId = "unknown") {
|
|
175
|
-
try {
|
|
176
|
-
return variants.map((variant, index) => ({
|
|
177
|
-
body: generateObject(bodySchema, variant, mulberry32(seed + index * 7919), index, 0, "/", new WeakSet(), operationId),
|
|
178
|
-
variant,
|
|
179
|
-
}));
|
|
180
|
-
}
|
|
181
|
-
catch (error) {
|
|
182
|
-
if (isOverflowError(error))
|
|
183
|
-
throw overflowFrom(error, operationId);
|
|
184
|
-
throw error;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* One create body, or a gap if a required field cannot be honoured.
|
|
189
|
-
*
|
|
190
|
-
* Optional fields that cannot match `format`/`pattern` are omitted. Required ones must not fall
|
|
191
|
-
* back to `"Quarterly Report N"` — that is what 400s every email/handle/url field.
|
|
192
|
-
*/
|
|
193
|
-
export function generateBody(bodySchema, variant, rand, index, operationId = "unknown") {
|
|
194
|
-
const missingRequired = [];
|
|
195
|
-
const body = generateObject(bodySchema, variant, rand, index, 0, "/", new WeakSet(), operationId, missingRequired);
|
|
196
|
-
return { body, missingRequired };
|
|
197
|
-
}
|
|
198
|
-
function generateObject(schema, variant, rand, index, depth, pointer, seen, operationId, missingRequired = []) {
|
|
199
|
-
try {
|
|
200
|
-
if (depth > MAX_DEPTH)
|
|
201
|
-
return {};
|
|
202
|
-
if (isVacuous(schema))
|
|
203
|
-
return {};
|
|
204
|
-
if (schema === false)
|
|
205
|
-
return {};
|
|
206
|
-
if (typeof schema !== "object" || schema === null)
|
|
207
|
-
return {};
|
|
208
|
-
if (seen.has(schema))
|
|
209
|
-
return {};
|
|
210
|
-
seen.add(schema);
|
|
211
|
-
const concrete = concreteBranch(schema, depth, pointer, seen, operationId);
|
|
212
|
-
if (isVacuous(concrete) || typeof concrete !== "object" || concrete === null)
|
|
213
|
-
return {};
|
|
214
|
-
if (seen.has(concrete) && concrete !== schema)
|
|
215
|
-
return {};
|
|
216
|
-
const properties = concrete.properties;
|
|
217
|
-
if (properties === null || typeof properties !== "object")
|
|
218
|
-
return {};
|
|
219
|
-
const required = Array.isArray(concrete.required) ? concrete.required : [];
|
|
220
|
-
const out = {};
|
|
221
|
-
for (const [name, raw] of Object.entries(properties)) {
|
|
222
|
-
const child = `${pointer === "/" ? "" : pointer}/${escapePointer(name)}`;
|
|
223
|
-
const value = generateValue(name, raw, variant, rand, index, depth + 1, child, seen, operationId);
|
|
224
|
-
if (value === undefined) {
|
|
225
|
-
if (required.includes(name)) {
|
|
226
|
-
const fallback = requiredFallback(raw, variant, index);
|
|
227
|
-
if (fallback === undefined)
|
|
228
|
-
missingRequired.push(child);
|
|
229
|
-
else
|
|
230
|
-
out[name] = fallback;
|
|
231
|
-
}
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
out[name] = value;
|
|
235
|
-
}
|
|
236
|
-
return out;
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
if (isOverflowError(error))
|
|
240
|
-
throw overflowFrom(error, operationId, pointer);
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
function generateValue(name, raw, variant, rand, index, depth, pointer, seen, operationId) {
|
|
245
|
-
try {
|
|
246
|
-
if (depth > MAX_DEPTH)
|
|
247
|
-
return undefined;
|
|
248
|
-
if (isVacuous(raw))
|
|
249
|
-
return scalarFor(name, variant, index, undefined);
|
|
250
|
-
if (raw === false)
|
|
251
|
-
return undefined;
|
|
252
|
-
if (typeof raw !== "object" || raw === null)
|
|
253
|
-
return undefined;
|
|
254
|
-
if (seen.has(raw))
|
|
255
|
-
return {};
|
|
256
|
-
const schema = raw;
|
|
257
|
-
const nullable = isNullable(schema);
|
|
258
|
-
if (variant === "null-heavy" && nullable)
|
|
259
|
-
return null;
|
|
260
|
-
if (schema.readOnly === true)
|
|
261
|
-
return undefined;
|
|
262
|
-
const followed = followRef(schema, depth, pointer, seen, operationId);
|
|
263
|
-
if (followed === "cycle")
|
|
264
|
-
return {};
|
|
265
|
-
const concrete = concreteBranch(followed === null ? schema : followed, depth, pointer, seen, operationId);
|
|
266
|
-
if (isVacuous(concrete))
|
|
267
|
-
return scalarFor(name, variant, index, undefined);
|
|
268
|
-
if (typeof concrete !== "object" || concrete === null)
|
|
269
|
-
return scalarFor(name, variant, index, undefined);
|
|
270
|
-
if (seen.has(concrete) && concrete !== schema)
|
|
271
|
-
return {};
|
|
272
|
-
if (Array.isArray(concrete.enum) && concrete.enum.length > 0) {
|
|
273
|
-
return concrete.enum[index % concrete.enum.length];
|
|
274
|
-
}
|
|
275
|
-
const type = typeOf(concrete);
|
|
276
|
-
switch (type) {
|
|
277
|
-
case "string":
|
|
278
|
-
return generateString(name, concrete, variant, index);
|
|
279
|
-
case "integer":
|
|
280
|
-
case "number": {
|
|
281
|
-
if (variant === "boundary" && typeof concrete.maximum === "number")
|
|
282
|
-
return concrete.maximum;
|
|
283
|
-
const min = typeof concrete.minimum === "number" ? concrete.minimum : 0;
|
|
284
|
-
/*
|
|
285
|
-
* A ladder whose lexical order differs from its numeric order: as text these sort
|
|
286
|
-
* 1, 10, 100, 2, 20, 5, 50 — nothing like their numeric order.
|
|
287
|
-
*
|
|
288
|
-
* An evenly spaced sequence (10, 20, 30…) sorts identically either way, so a backend
|
|
289
|
-
* comparing numbers as strings would look correct. Values have to disagree for the
|
|
290
|
-
* assertion to carry any signal.
|
|
291
|
-
*/
|
|
292
|
-
const ladder = [1, 2, 5, 10, 20, 50, 100];
|
|
293
|
-
const step = ladder[index % ladder.length] ?? 1;
|
|
294
|
-
return type === "integer" ? min + step : min + step + Math.round(rand() * 9) / 10;
|
|
295
|
-
}
|
|
296
|
-
case "boolean":
|
|
297
|
-
return index % 2 === 0;
|
|
298
|
-
case "array": {
|
|
299
|
-
/* An empty array is not a safe default: `minItems` is common on required collections
|
|
300
|
-
* (column definitions, line items), and sending [] fails validation on exactly the
|
|
301
|
-
* endpoints most worth testing. */
|
|
302
|
-
const min = typeof concrete.minItems === "number" ? concrete.minItems : 0;
|
|
303
|
-
const count = Math.max(min, 1);
|
|
304
|
-
const prefix = concrete.prefixItems;
|
|
305
|
-
if (Array.isArray(prefix)) {
|
|
306
|
-
return prefix.map((item, position) => generateValue(`${name}_${position}`, item, variant, rand, index + position, depth + 1, `${pointer}/prefixItems/${position}`, seen, operationId));
|
|
307
|
-
}
|
|
308
|
-
const items = concrete.items;
|
|
309
|
-
if (isVacuous(items) || items === false)
|
|
310
|
-
return Array.from({ length: count }, () => ({}));
|
|
311
|
-
if (items === null || typeof items !== "object")
|
|
312
|
-
return [];
|
|
313
|
-
return Array.from({ length: count }, (_, position) => generateValue(`${name}_item`, items, variant, rand, index + position, depth + 1, `${pointer}/items`, seen, operationId));
|
|
314
|
-
}
|
|
315
|
-
case "object":
|
|
316
|
-
return generateObject(concrete, variant, rand, index, depth + 1, pointer, seen, operationId);
|
|
317
|
-
default:
|
|
318
|
-
return undefined;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
if (isOverflowError(error))
|
|
323
|
-
throw overflowFrom(error, operationId, pointer);
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
function generateString(name, schema, variant, index) {
|
|
328
|
-
const maxLength = typeof schema.maxLength === "number" ? schema.maxLength : undefined;
|
|
329
|
-
const minLength = typeof schema.minLength === "number" ? schema.minLength : undefined;
|
|
330
|
-
if (minLength !== undefined && maxLength !== undefined && minLength > maxLength)
|
|
331
|
-
return undefined;
|
|
332
|
-
const max = maxLength ?? Math.max(64, minLength ?? 0);
|
|
333
|
-
const format = typeof schema.format === "string" ? schema.format.toLowerCase() : "";
|
|
334
|
-
const pattern = typeof schema.pattern === "string" ? schema.pattern : undefined;
|
|
335
|
-
let text;
|
|
336
|
-
if (format === "email")
|
|
337
|
-
text = emailOf(variant, index);
|
|
338
|
-
else if (format === "uri" || format === "url")
|
|
339
|
-
text = uriOf(variant, index);
|
|
340
|
-
else if (format === "uuid")
|
|
341
|
-
text = uuidOf(variant, index);
|
|
342
|
-
else if (pattern !== undefined)
|
|
343
|
-
text = stringMatching(pattern, max, variant, index, minLength);
|
|
344
|
-
else if (variant === "boundary")
|
|
345
|
-
text = "B".repeat(Math.max(1, Math.min(max, 512)));
|
|
346
|
-
else {
|
|
347
|
-
const base = STRINGS[variant];
|
|
348
|
-
text = `${base} ${index}`;
|
|
349
|
-
}
|
|
350
|
-
if (text === undefined)
|
|
351
|
-
return undefined;
|
|
352
|
-
if (codePointCount(text) > max) {
|
|
353
|
-
if (format === "email" || format === "uri" || format === "url" || format === "uuid" || pattern !== undefined) {
|
|
354
|
-
const trimmed = fitMax(text, max, format, pattern, variant, index, minLength);
|
|
355
|
-
if (trimmed === undefined)
|
|
356
|
-
return undefined;
|
|
357
|
-
text = trimmed;
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
text = sliceCodePoints(text, max);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
if (minLength !== undefined && codePointCount(text) < minLength) {
|
|
364
|
-
const padded = padToMin(text, minLength, max, pattern);
|
|
365
|
-
if (padded !== undefined)
|
|
366
|
-
text = padded;
|
|
367
|
-
else if (pattern !== undefined)
|
|
368
|
-
text = stringMatching(pattern, max, variant, index, minLength);
|
|
369
|
-
else
|
|
370
|
-
return undefined;
|
|
371
|
-
if (text === undefined)
|
|
372
|
-
return undefined;
|
|
373
|
-
}
|
|
374
|
-
if (pattern !== undefined && !safeTest(pattern, text)) {
|
|
375
|
-
return stringMatching(pattern, max, variant, index, minLength);
|
|
376
|
-
}
|
|
377
|
-
if (format === "email" && !EMAIL_RE.test(text))
|
|
378
|
-
return undefined;
|
|
379
|
-
void name;
|
|
380
|
-
return text;
|
|
381
|
-
}
|
|
382
|
-
function padToMin(text, min, max, pattern) {
|
|
383
|
-
const n = codePointCount(text);
|
|
384
|
-
if (n >= min)
|
|
385
|
-
return text;
|
|
386
|
-
if (min > max)
|
|
387
|
-
return undefined;
|
|
388
|
-
const last = n === 0 ? "a" : ([...text].at(-1) ?? "a");
|
|
389
|
-
const need = min - n;
|
|
390
|
-
if (n + need > max)
|
|
391
|
-
return undefined;
|
|
392
|
-
const padded = text + last.repeat(need);
|
|
393
|
-
if (pattern !== undefined && !safeTest(pattern, padded))
|
|
394
|
-
return undefined;
|
|
395
|
-
return padded;
|
|
396
|
-
}
|
|
397
|
-
function requiredFallback(raw, variant, index) {
|
|
398
|
-
if (isVacuous(raw) || raw === false || raw === null || typeof raw !== "object")
|
|
399
|
-
return scalarFor("field", variant, index, undefined);
|
|
400
|
-
const schema = raw;
|
|
401
|
-
const format = typeof schema.format === "string" ? schema.format.toLowerCase() : "";
|
|
402
|
-
const pattern = typeof schema.pattern === "string" ? schema.pattern : undefined;
|
|
403
|
-
if (format === "email" || format === "uri" || format === "url" || format === "uuid" || pattern !== undefined) {
|
|
404
|
-
return generateString("field", schema, variant, index);
|
|
405
|
-
}
|
|
406
|
-
switch (typeOf(concreteBranch(schema, 0, "/", new WeakSet(), "unknown"))) {
|
|
407
|
-
case "integer":
|
|
408
|
-
case "number":
|
|
409
|
-
return 0;
|
|
410
|
-
case "boolean":
|
|
411
|
-
return false;
|
|
412
|
-
case "array":
|
|
413
|
-
return [];
|
|
414
|
-
case "object":
|
|
415
|
-
return {};
|
|
416
|
-
default: {
|
|
417
|
-
const generated = generateString("field", schema, variant, index);
|
|
418
|
-
if (generated !== undefined)
|
|
419
|
-
return generated;
|
|
420
|
-
const min = typeof schema.minLength === "number" ? schema.minLength : undefined;
|
|
421
|
-
const max = typeof schema.maxLength === "number" ? schema.maxLength : undefined;
|
|
422
|
-
if (min !== undefined && max !== undefined && min > max)
|
|
423
|
-
return undefined;
|
|
424
|
-
return "value";
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
function emailOf(variant, index) {
|
|
429
|
-
const slug = slugVariant(variant);
|
|
430
|
-
return `oat-${slug}-${index}@example.test`;
|
|
431
|
-
}
|
|
432
|
-
function uriOf(variant, index) {
|
|
433
|
-
return `https://example.test/${slugVariant(variant)}-${index}`;
|
|
434
|
-
}
|
|
435
|
-
function uuidOf(variant, index) {
|
|
436
|
-
const n = (slugVariant(variant).length * 17 + index * 7919) >>> 0;
|
|
437
|
-
const hex = (offset, width) => {
|
|
438
|
-
let out = "";
|
|
439
|
-
let x = (n + offset * 0x9e3779b9) >>> 0;
|
|
440
|
-
for (let i = 0; i < width; i++) {
|
|
441
|
-
out += (x & 0xf).toString(16);
|
|
442
|
-
x = (Math.imul(x, 1664525) + 1013904223) >>> 0;
|
|
443
|
-
}
|
|
444
|
-
return out;
|
|
445
|
-
};
|
|
446
|
-
return `${hex(1, 8)}-${hex(2, 4)}-4${hex(3, 3)}-8${hex(4, 3)}-${hex(5, 12)}`;
|
|
447
|
-
}
|
|
448
|
-
function slugVariant(variant) {
|
|
449
|
-
return variant
|
|
450
|
-
.replace(/[^a-z0-9]+/gi, "-")
|
|
451
|
-
.replace(/^-|-$/g, "")
|
|
452
|
-
.toLowerCase();
|
|
453
|
-
}
|
|
454
|
-
function fitMax(text, max, format, pattern, variant, index, minLength) {
|
|
455
|
-
if (format === "email") {
|
|
456
|
-
const short = `o${index}@e.t`;
|
|
457
|
-
if (short.length <= max && EMAIL_RE.test(short))
|
|
458
|
-
return short;
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
if (format === "uri" || format === "url") {
|
|
462
|
-
const short = "https://e.t/x";
|
|
463
|
-
if (short.length <= max)
|
|
464
|
-
return short;
|
|
465
|
-
return undefined;
|
|
466
|
-
}
|
|
467
|
-
if (format === "uuid")
|
|
468
|
-
return undefined;
|
|
469
|
-
if (pattern !== undefined)
|
|
470
|
-
return stringMatching(pattern, max, variant, index, minLength);
|
|
471
|
-
return text.slice(0, max);
|
|
472
|
-
}
|
|
473
|
-
/** Bounded attempt at a string the documented regex will accept. */
|
|
474
|
-
function stringMatching(pattern, max, variant, index, minLength) {
|
|
475
|
-
const min = minLength ?? 0;
|
|
476
|
-
const candidates = [
|
|
477
|
-
expandPattern(pattern, max, variant, index, min),
|
|
478
|
-
emailOf(variant, index),
|
|
479
|
-
uriOf(variant, index),
|
|
480
|
-
uuidOf(variant, index),
|
|
481
|
-
slugVariant(variant),
|
|
482
|
-
`oat${index}`,
|
|
483
|
-
`h${index}`,
|
|
484
|
-
"a",
|
|
485
|
-
"ab",
|
|
486
|
-
"abc",
|
|
487
|
-
"handle",
|
|
488
|
-
`handle${index}`,
|
|
489
|
-
"https://example.test/x",
|
|
490
|
-
"oat@example.test",
|
|
491
|
-
];
|
|
492
|
-
for (const candidate of candidates) {
|
|
493
|
-
if (candidate === undefined)
|
|
494
|
-
continue;
|
|
495
|
-
const text = honourBounds(candidate, min, max, pattern);
|
|
496
|
-
if (text !== undefined)
|
|
497
|
-
return text;
|
|
498
|
-
}
|
|
499
|
-
for (let attempt = 0; attempt < PATTERN_ATTEMPTS; attempt++) {
|
|
500
|
-
const text = brutePattern(pattern, max, variant, index + attempt, min);
|
|
501
|
-
if (text !== undefined) {
|
|
502
|
-
const honoured = honourBounds(text, min, max, pattern);
|
|
503
|
-
if (honoured !== undefined)
|
|
504
|
-
return honoured;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return undefined;
|
|
508
|
-
}
|
|
509
|
-
function honourBounds(candidate, min, max, pattern) {
|
|
510
|
-
let text = candidate.length > max ? candidate.slice(0, max) : candidate;
|
|
511
|
-
if (min > 0 && text.length < min) {
|
|
512
|
-
const padded = padToMin(text, min, max, pattern);
|
|
513
|
-
if (padded === undefined)
|
|
514
|
-
return undefined;
|
|
515
|
-
text = padded;
|
|
516
|
-
}
|
|
517
|
-
if (text.length < min || text.length > max)
|
|
518
|
-
return undefined;
|
|
519
|
-
if (!safeTest(pattern, text))
|
|
520
|
-
return undefined;
|
|
521
|
-
return text;
|
|
522
|
-
}
|
|
523
|
-
function safeTest(pattern, text) {
|
|
524
|
-
try {
|
|
525
|
-
return new RegExp(pattern).test(text);
|
|
526
|
-
}
|
|
527
|
-
catch {
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Expand a small, common subset of regexes (`[a-z0-9_]+`, `^handle$`, optional groups). Anything
|
|
533
|
-
* we cannot expand is left for the candidate list — better to omit than to send a junk string.
|
|
534
|
-
*/
|
|
535
|
-
function expandPattern(pattern, max, variant, index, min) {
|
|
536
|
-
let source = pattern;
|
|
537
|
-
if (source.startsWith("^"))
|
|
538
|
-
source = source.slice(1);
|
|
539
|
-
if (source.endsWith("$"))
|
|
540
|
-
source = source.slice(0, -1);
|
|
541
|
-
try {
|
|
542
|
-
let built = expandAtoms(source, max, variant, index);
|
|
543
|
-
if (min > 0 && built.length < min) {
|
|
544
|
-
const padded = padToMin(built, min, max, undefined);
|
|
545
|
-
if (padded !== undefined)
|
|
546
|
-
built = padded;
|
|
547
|
-
}
|
|
548
|
-
if (built.length > max)
|
|
549
|
-
return built.slice(0, max);
|
|
550
|
-
return built;
|
|
551
|
-
}
|
|
552
|
-
catch {
|
|
553
|
-
return undefined;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
function expandAtoms(source, max, variant, index) {
|
|
557
|
-
let i = 0;
|
|
558
|
-
let out = "";
|
|
559
|
-
const takeQuantifier = () => {
|
|
560
|
-
if (source[i] === "+") {
|
|
561
|
-
i++;
|
|
562
|
-
return { max: 3, min: 1 };
|
|
563
|
-
}
|
|
564
|
-
if (source[i] === "*") {
|
|
565
|
-
i++;
|
|
566
|
-
return { max: 2, min: 0 };
|
|
567
|
-
}
|
|
568
|
-
if (source[i] === "?") {
|
|
569
|
-
i++;
|
|
570
|
-
return { max: 1, min: 0 };
|
|
571
|
-
}
|
|
572
|
-
if (source[i] === "{") {
|
|
573
|
-
const close = source.indexOf("}", i);
|
|
574
|
-
if (close < 0)
|
|
575
|
-
return { max: 1, min: 1 };
|
|
576
|
-
const spec = source.slice(i + 1, close);
|
|
577
|
-
i = close + 1;
|
|
578
|
-
const [lo, hi] = spec.split(",");
|
|
579
|
-
const min = Number.parseInt(lo ?? "1", 10);
|
|
580
|
-
const upper = hi === undefined ? min : hi === "" ? min + 2 : Number.parseInt(hi, 10);
|
|
581
|
-
if (!Number.isFinite(min) || !Number.isFinite(upper))
|
|
582
|
-
return { max: 1, min: 1 };
|
|
583
|
-
return { max: Math.min(upper, 16), min: Math.max(0, min) };
|
|
584
|
-
}
|
|
585
|
-
return { max: 1, min: 1 };
|
|
586
|
-
};
|
|
587
|
-
const emit = (atom, times) => {
|
|
588
|
-
for (let n = 0; n < times && out.length < max; n++)
|
|
589
|
-
out += atom;
|
|
590
|
-
};
|
|
591
|
-
while (i < source.length && out.length < max) {
|
|
592
|
-
const ch = source[i];
|
|
593
|
-
if (ch === undefined)
|
|
594
|
-
break;
|
|
595
|
-
if (ch === "(") {
|
|
596
|
-
const close = matchingParen(source, i);
|
|
597
|
-
if (close < 0)
|
|
598
|
-
break;
|
|
599
|
-
const inner = source.slice(i + 1, close).replace(/^\?:/, "");
|
|
600
|
-
i = close + 1;
|
|
601
|
-
const q = takeQuantifier();
|
|
602
|
-
const piece = expandAtoms(inner.split("|")[0] ?? inner, max, variant, index);
|
|
603
|
-
emit(piece, Math.max(q.min, Math.min(q.max, q.min === 0 ? 0 : 1)));
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
if (ch === "[") {
|
|
607
|
-
const close = source.indexOf("]", i + 1);
|
|
608
|
-
if (close < 0)
|
|
609
|
-
break;
|
|
610
|
-
const atom = firstClassChar(source.slice(i + 1, close));
|
|
611
|
-
i = close + 1;
|
|
612
|
-
const q = takeQuantifier();
|
|
613
|
-
emit(atom, Math.max(q.min, 1));
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
if (ch === "\\") {
|
|
617
|
-
const next = source[i + 1];
|
|
618
|
-
i += 2;
|
|
619
|
-
const atom = next === "d" ? "0" : next === "w" ? "a" : next === "s" ? " " : (next ?? "a");
|
|
620
|
-
const q = takeQuantifier();
|
|
621
|
-
emit(atom, Math.max(q.min, 1));
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
if (ch === ".") {
|
|
625
|
-
i++;
|
|
626
|
-
const q = takeQuantifier();
|
|
627
|
-
emit("a", Math.max(q.min, 1));
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
if (ch === "|" || ch === ")" || ch === "^" || ch === "$") {
|
|
631
|
-
i++;
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
i++;
|
|
635
|
-
const q = takeQuantifier();
|
|
636
|
-
emit(ch, Math.max(q.min, 1));
|
|
637
|
-
}
|
|
638
|
-
void variant;
|
|
639
|
-
void index;
|
|
640
|
-
return out;
|
|
641
|
-
}
|
|
642
|
-
function matchingParen(source, start) {
|
|
643
|
-
let depth = 0;
|
|
644
|
-
for (let i = start; i < source.length; i++) {
|
|
645
|
-
if (source[i] === "(")
|
|
646
|
-
depth++;
|
|
647
|
-
else if (source[i] === ")") {
|
|
648
|
-
depth--;
|
|
649
|
-
if (depth === 0)
|
|
650
|
-
return i;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
return -1;
|
|
654
|
-
}
|
|
655
|
-
function firstClassChar(body) {
|
|
656
|
-
let i = 0;
|
|
657
|
-
if (body[0] === "^")
|
|
658
|
-
i = 1;
|
|
659
|
-
while (i < body.length) {
|
|
660
|
-
const ch = body[i];
|
|
661
|
-
if (ch === undefined)
|
|
662
|
-
break;
|
|
663
|
-
if (ch === "\\" && body[i + 1] !== undefined) {
|
|
664
|
-
const next = body[i + 1];
|
|
665
|
-
if (next === "d")
|
|
666
|
-
return "0";
|
|
667
|
-
if (next === "w")
|
|
668
|
-
return "a";
|
|
669
|
-
return next ?? "a";
|
|
670
|
-
}
|
|
671
|
-
if (body[i + 1] === "-" && body[i + 2] !== undefined) {
|
|
672
|
-
return ch === "^" ? (body[i + 2] ?? "a") : ch;
|
|
673
|
-
}
|
|
674
|
-
if (ch !== "^")
|
|
675
|
-
return ch;
|
|
676
|
-
i++;
|
|
677
|
-
}
|
|
678
|
-
return "a";
|
|
679
|
-
}
|
|
680
|
-
function brutePattern(pattern, max, variant, salt, min = 0) {
|
|
681
|
-
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
682
|
-
const len = Math.min(Math.max(Math.max(3, min), (salt % 8) + 3), max);
|
|
683
|
-
let text = "";
|
|
684
|
-
let x = (salt * 1664525 + slugVariant(variant).length) >>> 0;
|
|
685
|
-
for (let i = 0; i < len; i++) {
|
|
686
|
-
text += alphabet[x % alphabet.length];
|
|
687
|
-
x = (Math.imul(x, 22695477) + 1) >>> 0;
|
|
688
|
-
}
|
|
689
|
-
return text;
|
|
690
|
-
}
|
|
691
|
-
function scalarFor(name, variant, index, schema) {
|
|
692
|
-
if (schema !== undefined) {
|
|
693
|
-
const generated = generateString(name, schema, variant, index);
|
|
694
|
-
if (generated !== undefined)
|
|
695
|
-
return generated;
|
|
696
|
-
}
|
|
697
|
-
const base = STRINGS[variant];
|
|
698
|
-
return `${base} ${index}`;
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* `{}` / `true` / a lone `additionalProperties: {}` is "any JSON", not an object to descend.
|
|
702
|
-
* Walking it again is what turns `z.unknown()` into a stack overflow.
|
|
703
|
-
*/
|
|
704
|
-
export function isVacuous(schema) {
|
|
705
|
-
if (schema === true)
|
|
706
|
-
return true;
|
|
707
|
-
if (schema === false || schema === null || typeof schema !== "object")
|
|
708
|
-
return false;
|
|
709
|
-
if (Array.isArray(schema))
|
|
710
|
-
return false;
|
|
711
|
-
const keys = Object.keys(schema).filter((key) => !isAnnotation(key));
|
|
712
|
-
if (keys.length === 0)
|
|
713
|
-
return true;
|
|
714
|
-
if (keys.length === 1 && (keys[0] === "additionalProperties" || keys[0] === "unevaluatedProperties")) {
|
|
715
|
-
return isVacuous(schema[keys[0]]);
|
|
716
|
-
}
|
|
717
|
-
if (keys.length === 1 && keys[0] === "default") {
|
|
718
|
-
const value = schema.default;
|
|
719
|
-
return (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0);
|
|
720
|
-
}
|
|
721
|
-
return false;
|
|
722
|
-
}
|
|
723
|
-
function isAnnotation(key) {
|
|
724
|
-
return (key === "description" ||
|
|
725
|
-
key === "title" ||
|
|
726
|
-
key === "deprecated" ||
|
|
727
|
-
key === "example" ||
|
|
728
|
-
key === "examples" ||
|
|
729
|
-
key === "nullable" ||
|
|
730
|
-
key === "readOnly" ||
|
|
731
|
-
key === "writeOnly" ||
|
|
732
|
-
key === "xml" ||
|
|
733
|
-
key === "externalDocs");
|
|
734
|
-
}
|
|
735
|
-
/** `oneOf: [{type: string}, {type: null}]` is the nullable idiom; pick the non-null branch. */
|
|
736
|
-
function concreteBranch(schema, depth, pointer, seen, operationId) {
|
|
737
|
-
if (depth > MAX_DEPTH)
|
|
738
|
-
return {};
|
|
739
|
-
const followed = followRef(schema, depth, pointer, seen, operationId);
|
|
740
|
-
const base = followed === "cycle" ? {} : (followed ?? schema);
|
|
741
|
-
if (typeof base !== "object" || base === null)
|
|
742
|
-
return {};
|
|
743
|
-
const union = base.oneOf ?? base.anyOf;
|
|
744
|
-
if (!Array.isArray(union))
|
|
745
|
-
return base;
|
|
746
|
-
const branch = union.find((candidate) => {
|
|
747
|
-
if (candidate === null || typeof candidate !== "object")
|
|
748
|
-
return candidate !== null;
|
|
749
|
-
return candidate.type !== "null";
|
|
750
|
-
});
|
|
751
|
-
if (branch === null || typeof branch !== "object")
|
|
752
|
-
return base;
|
|
753
|
-
if (seen.has(branch))
|
|
754
|
-
return {};
|
|
755
|
-
return concreteBranch(branch, depth + 1, `${pointer}/oneOf`, seen, operationId);
|
|
756
|
-
}
|
|
757
|
-
function followRef(schema, depth, pointer, seen, operationId) {
|
|
758
|
-
void pointer;
|
|
759
|
-
void operationId;
|
|
760
|
-
if (depth > MAX_DEPTH)
|
|
761
|
-
return "cycle";
|
|
762
|
-
const ref = schema.$ref;
|
|
763
|
-
if (typeof ref !== "string")
|
|
764
|
-
return null;
|
|
765
|
-
/* After inlining, leftover `$ref: "#"` (or any self-ref) is the same node. Do not follow. */
|
|
766
|
-
if (ref === "#" || ref === "#/" || seen.has(schema))
|
|
767
|
-
return "cycle";
|
|
768
|
-
return "cycle";
|
|
769
|
-
}
|
|
770
|
-
function isNullable(schema) {
|
|
771
|
-
if (schema.nullable === true)
|
|
772
|
-
return true;
|
|
773
|
-
const type = schema.type;
|
|
774
|
-
if (Array.isArray(type))
|
|
775
|
-
return type.includes("null");
|
|
776
|
-
const union = schema.oneOf ?? schema.anyOf;
|
|
777
|
-
if (!Array.isArray(union))
|
|
778
|
-
return false;
|
|
779
|
-
return union.some((candidate) => candidate !== null && typeof candidate === "object" && candidate.type === "null");
|
|
780
|
-
}
|
|
781
|
-
function typeOf(schema) {
|
|
782
|
-
if (isVacuous(schema))
|
|
783
|
-
return "string";
|
|
784
|
-
const add = schema.additionalProperties;
|
|
785
|
-
const uneval = schema.unevaluatedProperties;
|
|
786
|
-
if (schema.type === undefined &&
|
|
787
|
-
schema.properties === undefined &&
|
|
788
|
-
schema.items === undefined &&
|
|
789
|
-
(isVacuous(add) || isVacuous(uneval))) {
|
|
790
|
-
return "object";
|
|
791
|
-
}
|
|
792
|
-
const type = schema.type;
|
|
793
|
-
if (typeof type === "string")
|
|
794
|
-
return type;
|
|
795
|
-
if (Array.isArray(type)) {
|
|
796
|
-
const concrete = type.find((t) => t !== "null");
|
|
797
|
-
if (typeof concrete === "string")
|
|
798
|
-
return concrete;
|
|
799
|
-
}
|
|
800
|
-
if (schema.properties !== undefined)
|
|
801
|
-
return "object";
|
|
802
|
-
if (schema.items !== undefined || schema.prefixItems !== undefined)
|
|
803
|
-
return "array";
|
|
804
|
-
return "string";
|
|
805
|
-
}
|
|
806
|
-
function escapePointer(name) {
|
|
807
|
-
return name.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
808
|
-
}
|
|
809
170
|
//# sourceMappingURL=fixture.js.map
|