@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.
Files changed (283) hide show
  1. package/README.md +26 -1936
  2. package/dist/cli.js +326 -60
  3. package/dist/config/define-config.d.ts +15 -1
  4. package/dist/config/define-config.d.ts.map +1 -1
  5. package/dist/config/define-config.js.map +1 -1
  6. package/dist/config/validate.d.ts +12 -0
  7. package/dist/config/validate.d.ts.map +1 -0
  8. package/dist/config/validate.js +86 -0
  9. package/dist/config/validate.js.map +1 -0
  10. package/dist/conformance/effects.d.ts +9 -0
  11. package/dist/conformance/effects.d.ts.map +1 -0
  12. package/dist/conformance/effects.js +276 -0
  13. package/dist/conformance/effects.js.map +1 -0
  14. package/dist/conformance/expected-failures.d.ts +18 -0
  15. package/dist/conformance/expected-failures.d.ts.map +1 -0
  16. package/dist/conformance/expected-failures.js +48 -0
  17. package/dist/conformance/expected-failures.js.map +1 -0
  18. package/dist/conformance/feature-gate.d.ts +7 -0
  19. package/dist/conformance/feature-gate.d.ts.map +1 -0
  20. package/dist/conformance/feature-gate.js +428 -0
  21. package/dist/conformance/feature-gate.js.map +1 -0
  22. package/dist/conformance/fuzz.d.ts +108 -0
  23. package/dist/conformance/fuzz.d.ts.map +1 -0
  24. package/dist/conformance/fuzz.js +246 -0
  25. package/dist/conformance/fuzz.js.map +1 -0
  26. package/dist/conformance/leaks.d.ts +10 -0
  27. package/dist/conformance/leaks.d.ts.map +1 -0
  28. package/dist/conformance/leaks.js +23 -0
  29. package/dist/conformance/leaks.js.map +1 -0
  30. package/dist/conformance/pool.d.ts +49 -0
  31. package/dist/conformance/pool.d.ts.map +1 -0
  32. package/dist/conformance/pool.js +75 -0
  33. package/dist/conformance/pool.js.map +1 -0
  34. package/dist/conformance/rate-limit.d.ts +7 -0
  35. package/dist/conformance/rate-limit.d.ts.map +1 -0
  36. package/dist/conformance/rate-limit.js +372 -0
  37. package/dist/conformance/rate-limit.js.map +1 -0
  38. package/dist/conformance/scope.d.ts +15 -0
  39. package/dist/conformance/scope.d.ts.map +1 -0
  40. package/dist/conformance/scope.js +78 -0
  41. package/dist/conformance/scope.js.map +1 -0
  42. package/dist/conformance/seed-contract.d.ts +11 -0
  43. package/dist/conformance/seed-contract.d.ts.map +1 -0
  44. package/dist/conformance/seed-contract.js +680 -0
  45. package/dist/conformance/seed-contract.js.map +1 -0
  46. package/dist/conformance/shapes.d.ts +35 -0
  47. package/dist/conformance/shapes.d.ts.map +1 -0
  48. package/dist/conformance/shapes.js +162 -0
  49. package/dist/conformance/shapes.js.map +1 -0
  50. package/dist/conformance/specs.d.ts +21 -0
  51. package/dist/conformance/specs.d.ts.map +1 -0
  52. package/dist/conformance/specs.js +306 -0
  53. package/dist/conformance/specs.js.map +1 -0
  54. package/dist/conformance/suite.d.ts +209 -0
  55. package/dist/conformance/suite.d.ts.map +1 -0
  56. package/dist/conformance/suite.js +1538 -0
  57. package/dist/conformance/suite.js.map +1 -0
  58. package/dist/conformance/unique.d.ts +7 -0
  59. package/dist/conformance/unique.d.ts.map +1 -0
  60. package/dist/conformance/unique.js +586 -0
  61. package/dist/conformance/unique.js.map +1 -0
  62. package/dist/conformance/worker.d.ts +3 -0
  63. package/dist/conformance/worker.d.ts.map +1 -0
  64. package/dist/conformance/worker.js +27 -0
  65. package/dist/conformance/worker.js.map +1 -0
  66. package/dist/index.d.ts +10 -3
  67. package/dist/index.js +6 -2
  68. package/dist/reference/defects.d.ts +71 -1
  69. package/dist/reference/defects.d.ts.map +1 -1
  70. package/dist/reference/defects.js +74 -8
  71. package/dist/reference/defects.js.map +1 -1
  72. package/dist/reference/dialect.d.ts +6 -4
  73. package/dist/reference/dialect.d.ts.map +1 -1
  74. package/dist/reference/dialect.js +1 -0
  75. package/dist/reference/dialect.js.map +1 -1
  76. package/dist/reference/http.d.ts +17 -25
  77. package/dist/reference/http.d.ts.map +1 -1
  78. package/dist/reference/http.js +574 -224
  79. package/dist/reference/http.js.map +1 -1
  80. package/dist/reference/listing.d.ts +51 -0
  81. package/dist/reference/listing.d.ts.map +1 -0
  82. package/dist/reference/listing.js +256 -0
  83. package/dist/reference/listing.js.map +1 -0
  84. package/dist/reference/model.d.ts +35 -1
  85. package/dist/reference/model.d.ts.map +1 -1
  86. package/dist/reference/model.js +42 -0
  87. package/dist/reference/model.js.map +1 -1
  88. package/dist/reference/query.d.ts +132 -43
  89. package/dist/reference/query.d.ts.map +1 -1
  90. package/dist/reference/query.js +397 -315
  91. package/dist/reference/query.js.map +1 -1
  92. package/dist/reference/shapes.d.ts +60 -0
  93. package/dist/reference/shapes.d.ts.map +1 -0
  94. package/dist/reference/shapes.js +142 -0
  95. package/dist/reference/shapes.js.map +1 -0
  96. package/dist/reference/spec.d.ts +9 -2
  97. package/dist/reference/spec.d.ts.map +1 -1
  98. package/dist/reference/spec.js +291 -71
  99. package/dist/reference/spec.js.map +1 -1
  100. package/dist/reference/store-api.d.ts +14 -43
  101. package/dist/reference/store-api.d.ts.map +1 -1
  102. package/dist/reference/store-api.js +8 -58
  103. package/dist/reference/store-api.js.map +1 -1
  104. package/dist/reference/stores/memory.d.ts +12 -15
  105. package/dist/reference/stores/memory.d.ts.map +1 -1
  106. package/dist/reference/stores/memory.js +22 -70
  107. package/dist/reference/stores/memory.js.map +1 -1
  108. package/dist/reference/stores/postgres.d.ts +11 -23
  109. package/dist/reference/stores/postgres.d.ts.map +1 -1
  110. package/dist/reference/stores/postgres.js +53 -334
  111. package/dist/reference/stores/postgres.js.map +1 -1
  112. package/dist/reference/stores/sql-compile.d.ts +36 -0
  113. package/dist/reference/stores/sql-compile.d.ts.map +1 -0
  114. package/dist/reference/stores/sql-compile.js +95 -0
  115. package/dist/reference/stores/sql-compile.js.map +1 -0
  116. package/dist/reference/stores/sqlite.d.ts +12 -27
  117. package/dist/reference/stores/sqlite.d.ts.map +1 -1
  118. package/dist/reference/stores/sqlite.js +54 -355
  119. package/dist/reference/stores/sqlite.js.map +1 -1
  120. package/dist/report/console.d.ts +2 -1
  121. package/dist/report/console.d.ts.map +1 -1
  122. package/dist/report/console.js +56 -10
  123. package/dist/report/console.js.map +1 -1
  124. package/dist/report/matrix.d.ts +10 -13
  125. package/dist/report/matrix.d.ts.map +1 -1
  126. package/dist/report/matrix.js +82 -18
  127. package/dist/report/matrix.js.map +1 -1
  128. package/dist/report/render.d.ts +18 -1
  129. package/dist/report/render.d.ts.map +1 -1
  130. package/dist/report/render.js +169 -14
  131. package/dist/report/render.js.map +1 -1
  132. package/dist/runtime/async.d.ts +16 -3
  133. package/dist/runtime/async.d.ts.map +1 -1
  134. package/dist/runtime/async.js +24 -19
  135. package/dist/runtime/async.js.map +1 -1
  136. package/dist/runtime/auth.d.ts +2 -9
  137. package/dist/runtime/auth.d.ts.map +1 -1
  138. package/dist/runtime/auth.js +38 -62
  139. package/dist/runtime/auth.js.map +1 -1
  140. package/dist/runtime/body.d.ts +15 -3
  141. package/dist/runtime/body.d.ts.map +1 -1
  142. package/dist/runtime/body.js +61 -12
  143. package/dist/runtime/body.js.map +1 -1
  144. package/dist/runtime/check-costs.d.ts +15 -0
  145. package/dist/runtime/check-costs.d.ts.map +1 -0
  146. package/dist/runtime/check-costs.js +114 -0
  147. package/dist/runtime/check-costs.js.map +1 -0
  148. package/dist/runtime/check-ids.d.ts +15 -0
  149. package/dist/runtime/check-ids.d.ts.map +1 -0
  150. package/dist/runtime/check-ids.js +140 -0
  151. package/dist/runtime/check-ids.js.map +1 -0
  152. package/dist/runtime/checks.d.ts +122 -18
  153. package/dist/runtime/checks.d.ts.map +1 -1
  154. package/dist/runtime/checks.js +3437 -1933
  155. package/dist/runtime/checks.js.map +1 -1
  156. package/dist/runtime/client.d.ts +112 -0
  157. package/dist/runtime/client.d.ts.map +1 -1
  158. package/dist/runtime/client.js +271 -39
  159. package/dist/runtime/client.js.map +1 -1
  160. package/dist/runtime/cookies.d.ts +16 -2
  161. package/dist/runtime/cookies.d.ts.map +1 -1
  162. package/dist/runtime/cookies.js +83 -22
  163. package/dist/runtime/cookies.js.map +1 -1
  164. package/dist/runtime/exchanges.d.ts +4 -5
  165. package/dist/runtime/exchanges.d.ts.map +1 -1
  166. package/dist/runtime/exchanges.js +33 -55
  167. package/dist/runtime/exchanges.js.map +1 -1
  168. package/dist/runtime/exit.d.ts +40 -0
  169. package/dist/runtime/exit.d.ts.map +1 -0
  170. package/dist/runtime/exit.js +48 -0
  171. package/dist/runtime/exit.js.map +1 -0
  172. package/dist/runtime/feature-gate.d.ts +0 -9
  173. package/dist/runtime/feature-gate.d.ts.map +1 -1
  174. package/dist/runtime/feature-gate.js +0 -21
  175. package/dist/runtime/feature-gate.js.map +1 -1
  176. package/dist/runtime/finding.d.ts +61 -7
  177. package/dist/runtime/finding.d.ts.map +1 -1
  178. package/dist/runtime/finding.js +74 -12
  179. package/dist/runtime/finding.js.map +1 -1
  180. package/dist/runtime/fixture.d.ts +35 -28
  181. package/dist/runtime/fixture.d.ts.map +1 -1
  182. package/dist/runtime/fixture.js +92 -731
  183. package/dist/runtime/fixture.js.map +1 -1
  184. package/dist/runtime/generate.d.ts +63 -0
  185. package/dist/runtime/generate.d.ts.map +1 -0
  186. package/dist/runtime/generate.js +635 -0
  187. package/dist/runtime/generate.js.map +1 -0
  188. package/dist/runtime/headers.d.ts +6 -0
  189. package/dist/runtime/headers.d.ts.map +1 -0
  190. package/dist/runtime/headers.js +16 -0
  191. package/dist/runtime/headers.js.map +1 -0
  192. package/dist/runtime/path.d.ts +7 -0
  193. package/dist/runtime/path.d.ts.map +1 -0
  194. package/dist/runtime/path.js +44 -0
  195. package/dist/runtime/path.js.map +1 -0
  196. package/dist/runtime/payloads.d.ts +2 -0
  197. package/dist/runtime/payloads.d.ts.map +1 -1
  198. package/dist/runtime/payloads.js +2 -1
  199. package/dist/runtime/payloads.js.map +1 -1
  200. package/dist/runtime/rate-limit.d.ts +6 -4
  201. package/dist/runtime/rate-limit.d.ts.map +1 -1
  202. package/dist/runtime/rate-limit.js +22 -33
  203. package/dist/runtime/rate-limit.js.map +1 -1
  204. package/dist/runtime/redact.d.ts +24 -0
  205. package/dist/runtime/redact.d.ts.map +1 -0
  206. package/dist/runtime/redact.js +98 -0
  207. package/dist/runtime/redact.js.map +1 -0
  208. package/dist/runtime/run.d.ts +51 -2
  209. package/dist/runtime/run.d.ts.map +1 -1
  210. package/dist/runtime/run.js +463 -195
  211. package/dist/runtime/run.js.map +1 -1
  212. package/dist/runtime/runs.d.ts +5 -0
  213. package/dist/runtime/runs.d.ts.map +1 -1
  214. package/dist/runtime/runs.js +7 -1
  215. package/dist/runtime/runs.js.map +1 -1
  216. package/dist/runtime/scope.d.ts +163 -0
  217. package/dist/runtime/scope.d.ts.map +1 -0
  218. package/dist/runtime/scope.js +423 -0
  219. package/dist/runtime/scope.js.map +1 -0
  220. package/dist/runtime/teardown.d.ts +17 -4
  221. package/dist/runtime/teardown.d.ts.map +1 -1
  222. package/dist/runtime/teardown.js +49 -16
  223. package/dist/runtime/teardown.js.map +1 -1
  224. package/dist/runtime/transcript.d.ts +34 -2
  225. package/dist/runtime/transcript.d.ts.map +1 -1
  226. package/dist/runtime/transcript.js +119 -24
  227. package/dist/runtime/transcript.js.map +1 -1
  228. package/dist/runtime/unique.d.ts +2 -11
  229. package/dist/runtime/unique.d.ts.map +1 -1
  230. package/dist/runtime/unique.js +27 -53
  231. package/dist/runtime/unique.js.map +1 -1
  232. package/dist/runtime/validate.d.ts +13 -2
  233. package/dist/runtime/validate.d.ts.map +1 -1
  234. package/dist/runtime/validate.js +51 -31
  235. package/dist/runtime/validate.js.map +1 -1
  236. package/dist/runtime/wait.d.ts +2 -1
  237. package/dist/runtime/wait.d.ts.map +1 -1
  238. package/dist/runtime/wait.js +2 -36
  239. package/dist/runtime/wait.js.map +1 -1
  240. package/dist/runtime/world.d.ts +31 -3
  241. package/dist/runtime/world.d.ts.map +1 -1
  242. package/dist/runtime/world.js +113 -32
  243. package/dist/runtime/world.js.map +1 -1
  244. package/dist/spec/collection.d.ts +8 -2
  245. package/dist/spec/collection.d.ts.map +1 -1
  246. package/dist/spec/collection.js +47 -7
  247. package/dist/spec/collection.js.map +1 -1
  248. package/dist/spec/extensions.d.ts +5 -1
  249. package/dist/spec/extensions.d.ts.map +1 -1
  250. package/dist/spec/extensions.js +9 -2
  251. package/dist/spec/extensions.js.map +1 -1
  252. package/dist/spec/graph.d.ts +46 -0
  253. package/dist/spec/graph.d.ts.map +1 -1
  254. package/dist/spec/graph.js +175 -11
  255. package/dist/spec/graph.js.map +1 -1
  256. package/dist/spec/load.d.ts +25 -5
  257. package/dist/spec/load.d.ts.map +1 -1
  258. package/dist/spec/load.js +160 -59
  259. package/dist/spec/load.js.map +1 -1
  260. package/dist/spec/query-capabilities.d.ts +4 -0
  261. package/dist/spec/query-capabilities.d.ts.map +1 -1
  262. package/dist/spec/query-capabilities.js +6 -0
  263. package/dist/spec/query-capabilities.js.map +1 -1
  264. package/dist/spec/schema.d.ts +20 -0
  265. package/dist/spec/schema.d.ts.map +1 -0
  266. package/dist/spec/schema.js +118 -0
  267. package/dist/spec/schema.js.map +1 -0
  268. package/dist/spec/tag-schema.d.ts +12 -0
  269. package/dist/spec/tag-schema.d.ts.map +1 -0
  270. package/dist/spec/tag-schema.js +143 -0
  271. package/dist/spec/tag-schema.js.map +1 -0
  272. package/dist/spec/types.js +17 -1
  273. package/dist/spec/types.js.map +1 -1
  274. package/docs/api.md +59 -0
  275. package/docs/assumptions.md +99 -0
  276. package/docs/checks.md +183 -0
  277. package/docs/commands.md +214 -0
  278. package/docs/configuration.md +650 -0
  279. package/docs/model.md +184 -0
  280. package/docs/reference-defects.md +94 -0
  281. package/docs/reports.md +211 -0
  282. package/docs/tags.md +400 -0
  283. package/package.json +3 -1
@@ -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 *discriminating cohort* — instances shaped so
6
- * that every query assertion has signal — and derives everything from a seed so a failing run is
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
- * After `$ref` inlining the same object identity can cycle, and empty schemas (`{}`, `true`,
10
- * `additionalProperties: {}`) mean "any value", not "walk this object again". Generation therefore
11
- * keys a WeakSet on every node and treats those open shapes as a scalar or `{}`.
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 { UNICODE_COHORT_STRING, codePointCount, sliceCodePoints } from "./payloads.js";
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
- const STRINGS = {
34
- baseline: "Quarterly Report",
35
- boundary: "",
36
- "lexical-first": "aaa first alphabetically",
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
- constructor(operationId, pointer) {
50
- super(`fixture generation overflow on ${operationId} (${pointer})`);
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
- * Makes unique-set tuples differ across cohort members without relaxing `maxLength` / `pattern`.
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 ensureDistinctUniqueValues(members, sets, schema) {
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 schemaObj = schema !== null && typeof schema === "object" ? schema : null;
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 attempts = 0;
83
- while (kept.some((prior) => sameUniqueTuple(prior.body, body, set)) && attempts < 16) {
84
- if (!nudgeUniqueSet(body, set, schemaObj, kept.length + attempts + 1)) {
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
- attempts++;
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 (`x-unique cannot express two distinct values for ${shown} without weakening ` +
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