@hegeldev/hegel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/binary.d.ts +27 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +50 -0
- package/dist/binary.js.map +1 -0
- package/dist/collections.d.ts +114 -0
- package/dist/collections.d.ts.map +1 -0
- package/dist/collections.js +313 -0
- package/dist/collections.js.map +1 -0
- package/dist/combinators.d.ts +34 -0
- package/dist/combinators.d.ts.map +1 -0
- package/dist/combinators.js +152 -0
- package/dist/combinators.js.map +1 -0
- package/dist/conformance.d.ts +31 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +60 -0
- package/dist/conformance.js.map +1 -0
- package/dist/connection.d.ts +82 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +231 -0
- package/dist/connection.js.map +1 -0
- package/dist/crc32.d.ts +13 -0
- package/dist/crc32.d.ts.map +1 -0
- package/dist/crc32.js +30 -0
- package/dist/crc32.js.map +1 -0
- package/dist/derive.d.ts +225 -0
- package/dist/derive.d.ts.map +1 -0
- package/dist/derive.js +296 -0
- package/dist/derive.js.map +1 -0
- package/dist/embedded.d.ts +38 -0
- package/dist/embedded.d.ts.map +1 -0
- package/dist/embedded.js +237 -0
- package/dist/embedded.js.map +1 -0
- package/dist/floats.d.ts +57 -0
- package/dist/floats.d.ts.map +1 -0
- package/dist/floats.js +100 -0
- package/dist/floats.js.map +1 -0
- package/dist/formats.d.ts +62 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +164 -0
- package/dist/formats.js.map +1 -0
- package/dist/generator.d.ts +80 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +128 -0
- package/dist/generator.js.map +1 -0
- package/dist/generators/collections.d.ts +20 -0
- package/dist/generators/collections.d.ts.map +1 -0
- package/dist/generators/collections.js +209 -0
- package/dist/generators/collections.js.map +1 -0
- package/dist/generators/combinators.d.ts +15 -0
- package/dist/generators/combinators.d.ts.map +1 -0
- package/dist/generators/combinators.js +143 -0
- package/dist/generators/combinators.js.map +1 -0
- package/dist/generators/compose.d.ts +27 -0
- package/dist/generators/compose.d.ts.map +1 -0
- package/dist/generators/compose.js +82 -0
- package/dist/generators/compose.js.map +1 -0
- package/dist/generators/core.d.ts +53 -0
- package/dist/generators/core.d.ts.map +1 -0
- package/dist/generators/core.js +134 -0
- package/dist/generators/core.js.map +1 -0
- package/dist/generators/index.d.ts +18 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +15 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/numeric.d.ts +40 -0
- package/dist/generators/numeric.d.ts.map +1 -0
- package/dist/generators/numeric.js +128 -0
- package/dist/generators/numeric.js.map +1 -0
- package/dist/generators/primitives.d.ts +138 -0
- package/dist/generators/primitives.d.ts.map +1 -0
- package/dist/generators/primitives.js +240 -0
- package/dist/generators/primitives.js.map +1 -0
- package/dist/generators/strings.d.ts +73 -0
- package/dist/generators/strings.d.ts.map +1 -0
- package/dist/generators/strings.js +215 -0
- package/dist/generators/strings.js.map +1 -0
- package/dist/generators/tuples.d.ts +11 -0
- package/dist/generators/tuples.d.ts.map +1 -0
- package/dist/generators/tuples.js +43 -0
- package/dist/generators/tuples.js.map +1 -0
- package/dist/generators.d.ts +408 -0
- package/dist/generators.d.ts.map +1 -0
- package/dist/generators.js +898 -0
- package/dist/generators.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +6 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +91 -0
- package/dist/install.js.map +1 -0
- package/dist/integers.d.ts +37 -0
- package/dist/integers.d.ts.map +1 -0
- package/dist/integers.js +63 -0
- package/dist/integers.js.map +1 -0
- package/dist/labels.d.ts +21 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +20 -0
- package/dist/labels.js.map +1 -0
- package/dist/objects.d.ts +39 -0
- package/dist/objects.d.ts.map +1 -0
- package/dist/objects.js +98 -0
- package/dist/objects.js.map +1 -0
- package/dist/primitives.d.ts +14 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +51 -0
- package/dist/primitives.js.map +1 -0
- package/dist/protocol.d.ts +25 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +77 -0
- package/dist/protocol.js.map +1 -0
- package/dist/runner.d.ts +115 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +455 -0
- package/dist/runner.js.map +1 -0
- package/dist/session.d.ts +19 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +157 -0
- package/dist/session.js.map +1 -0
- package/dist/spans.d.ts +23 -0
- package/dist/spans.d.ts.map +1 -0
- package/dist/spans.js +51 -0
- package/dist/spans.js.map +1 -0
- package/dist/strings.d.ts +67 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +107 -0
- package/dist/strings.js.map +1 -0
- package/dist/testCase.d.ts +118 -0
- package/dist/testCase.d.ts.map +1 -0
- package/dist/testCase.js +186 -0
- package/dist/testCase.js.map +1 -0
- package/dist/uv-install.sh +2226 -0
- package/dist/uv.d.ts +20 -0
- package/dist/uv.d.ts.map +1 -0
- package/dist/uv.js +103 -0
- package/dist/uv.js.map +1 -0
- package/dist/wtf8.d.ts +16 -0
- package/dist/wtf8.d.ts.map +1 -0
- package/dist/wtf8.js +52 -0
- package/dist/wtf8.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator infrastructure for the Hegel SDK.
|
|
3
|
+
*
|
|
4
|
+
* Provides the {@link Generator} abstract class, {@link BasicGenerator},
|
|
5
|
+
* composite generators (Mapped, FlatMapped, Filtered), collection protocol,
|
|
6
|
+
* span helpers, and the initial set of generators.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import { RequestError } from "./connection.js";
|
|
11
|
+
import { assume, DataExhausted, generateFromSchema, Labels, startSpan, stopSpan, } from "./runner.js";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Generator base class
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Base class for all generators.
|
|
17
|
+
*
|
|
18
|
+
* Subclasses implement `doDraw` to produce a value. The {@link map},
|
|
19
|
+
* {@link flatMap}, and {@link filter} combinators produce new generators.
|
|
20
|
+
*
|
|
21
|
+
* {@link BasicGenerator} is special: its {@link BasicGenerator.map} preserves
|
|
22
|
+
* the schema (stays basic), enabling the server to see the original schema for
|
|
23
|
+
* better shrinking.
|
|
24
|
+
*/
|
|
25
|
+
export class Generator {
|
|
26
|
+
/**
|
|
27
|
+
* Transform each generated value with `f`.
|
|
28
|
+
*
|
|
29
|
+
* On a {@link BasicGenerator} this stays basic (preserves schema).
|
|
30
|
+
* On any other generator it returns a MappedGenerator.
|
|
31
|
+
*/
|
|
32
|
+
map(f) {
|
|
33
|
+
return new MappedGenerator(this, f);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Dependent generation: generate a value then use it to choose a second generator.
|
|
37
|
+
*/
|
|
38
|
+
flatMap(f) {
|
|
39
|
+
return new FlatMappedGenerator(this, f);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Filter generated values. Tries up to 3 times before giving up.
|
|
43
|
+
*
|
|
44
|
+
* Prefer narrow schemas over filters when possible — filters slow shrinking.
|
|
45
|
+
*/
|
|
46
|
+
filter(predicate) {
|
|
47
|
+
return new FilteredGenerator(this, predicate);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// BasicGenerator
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* A generator backed by a raw schema sent to the server.
|
|
55
|
+
*
|
|
56
|
+
* `map()` on a BasicGenerator **preserves the schema** — it composes the
|
|
57
|
+
* transform client-side so the server still sees the original schema. This is
|
|
58
|
+
* the key optimization: the server can shrink within the original schema space.
|
|
59
|
+
*/
|
|
60
|
+
export class BasicGenerator extends Generator {
|
|
61
|
+
/** @internal */
|
|
62
|
+
_rawSchema;
|
|
63
|
+
/** @internal */
|
|
64
|
+
_transform;
|
|
65
|
+
constructor(rawSchema, transform = null) {
|
|
66
|
+
super();
|
|
67
|
+
this._rawSchema = rawSchema;
|
|
68
|
+
this._transform = transform;
|
|
69
|
+
}
|
|
70
|
+
/** The raw schema sent to the server. */
|
|
71
|
+
schema() {
|
|
72
|
+
return this._rawSchema;
|
|
73
|
+
}
|
|
74
|
+
/** @internal */
|
|
75
|
+
async doDraw(data) {
|
|
76
|
+
const raw = await generateFromSchema(this._rawSchema, data);
|
|
77
|
+
if (this._transform !== null) {
|
|
78
|
+
return this._transform(raw);
|
|
79
|
+
}
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Transform values while **preserving the schema**.
|
|
84
|
+
*
|
|
85
|
+
* If this generator already has a transform `t`, the new transform is
|
|
86
|
+
* `f(t(raw))`. This keeps the generator basic (server sees original schema).
|
|
87
|
+
*/
|
|
88
|
+
map(f) {
|
|
89
|
+
const current = this._transform;
|
|
90
|
+
const composed = current !== null ? (raw) => f(current(raw)) : (raw) => f(raw);
|
|
91
|
+
return new BasicGenerator(this._rawSchema, composed);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// MappedGenerator
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* A generator that applies a transform to values from another generator.
|
|
99
|
+
* Uses a MAPPED span so the server can track the transformation.
|
|
100
|
+
*/
|
|
101
|
+
export class MappedGenerator extends Generator {
|
|
102
|
+
_source;
|
|
103
|
+
_f;
|
|
104
|
+
constructor(source, f) {
|
|
105
|
+
super();
|
|
106
|
+
this._source = source;
|
|
107
|
+
this._f = f;
|
|
108
|
+
}
|
|
109
|
+
async doDraw(data) {
|
|
110
|
+
await startSpan(Labels.MAPPED, data);
|
|
111
|
+
try {
|
|
112
|
+
const value = await this._source.doDraw(data);
|
|
113
|
+
return this._f(value);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await stopSpan({}, data);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// FlatMappedGenerator
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
/**
|
|
124
|
+
* A generator for dependent generation.
|
|
125
|
+
* Generates a first value, uses it to choose a second generator, then generates from that.
|
|
126
|
+
*/
|
|
127
|
+
export class FlatMappedGenerator extends Generator {
|
|
128
|
+
_source;
|
|
129
|
+
_f;
|
|
130
|
+
constructor(source, f) {
|
|
131
|
+
super();
|
|
132
|
+
this._source = source;
|
|
133
|
+
this._f = f;
|
|
134
|
+
}
|
|
135
|
+
async doDraw(data) {
|
|
136
|
+
await startSpan(Labels.FLAT_MAP, data);
|
|
137
|
+
try {
|
|
138
|
+
const first = await this._source.doDraw(data);
|
|
139
|
+
const secondGen = this._f(first);
|
|
140
|
+
return await secondGen.doDraw(data);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await stopSpan({}, data);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// FilteredGenerator
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
/** Maximum number of filter attempts before giving up. */
|
|
151
|
+
const FILTER_MAX_ATTEMPTS = 3;
|
|
152
|
+
/**
|
|
153
|
+
* A generator that filters values from another generator.
|
|
154
|
+
*
|
|
155
|
+
* Tries up to 3 times. Each failed attempt discards the span.
|
|
156
|
+
* After all attempts fail, calls `assume(false)` to mark the test case as invalid.
|
|
157
|
+
*/
|
|
158
|
+
export class FilteredGenerator extends Generator {
|
|
159
|
+
_source;
|
|
160
|
+
_predicate;
|
|
161
|
+
constructor(source, predicate) {
|
|
162
|
+
super();
|
|
163
|
+
this._source = source;
|
|
164
|
+
this._predicate = predicate;
|
|
165
|
+
}
|
|
166
|
+
async doDraw(data) {
|
|
167
|
+
for (let i = 0; i < FILTER_MAX_ATTEMPTS; i++) {
|
|
168
|
+
await startSpan(Labels.FILTER, data);
|
|
169
|
+
const value = await this._source.doDraw(data);
|
|
170
|
+
if (this._predicate(value)) {
|
|
171
|
+
await stopSpan({}, data);
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
await stopSpan({ discard: true }, data);
|
|
175
|
+
}
|
|
176
|
+
assume(false);
|
|
177
|
+
// unreachable (assume(false) always throws)
|
|
178
|
+
throw new Error("unreachable");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Span helpers
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
/**
|
|
185
|
+
* Run `fn` inside a span with the given label.
|
|
186
|
+
*
|
|
187
|
+
* Starts the span before calling `fn`, then stops it (with `discard=false`)
|
|
188
|
+
* after `fn` returns. Returns the value returned by `fn`.
|
|
189
|
+
*
|
|
190
|
+
* @param label - Span label constant (see `Labels`).
|
|
191
|
+
* @param fn - Function to run inside the span.
|
|
192
|
+
*/
|
|
193
|
+
export async function group(label, fn, data) {
|
|
194
|
+
await startSpan(label, data);
|
|
195
|
+
try {
|
|
196
|
+
return await fn();
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
await stopSpan({}, data);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Run `fn` inside a discardable span.
|
|
204
|
+
*
|
|
205
|
+
* If `fn` throws, stops the span with `discard=true`. Otherwise stops with
|
|
206
|
+
* `discard=false`. The exception (if any) is re-thrown.
|
|
207
|
+
*
|
|
208
|
+
* @param label - Span label constant (see `Labels`).
|
|
209
|
+
* @param fn - Function to run inside the span.
|
|
210
|
+
*/
|
|
211
|
+
export async function discardableGroup(label, fn, data) {
|
|
212
|
+
await startSpan(label, data);
|
|
213
|
+
let discard = false;
|
|
214
|
+
try {
|
|
215
|
+
return await fn();
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
discard = true;
|
|
219
|
+
throw e;
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await stopSpan({ discard }, data);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Collection protocol
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
/**
|
|
229
|
+
* Server-managed collection for generating variable-length sequences.
|
|
230
|
+
*
|
|
231
|
+
* The server decides how many elements to generate based on the configured
|
|
232
|
+
* size constraints. Call `more()` in a loop; when it returns false, the
|
|
233
|
+
* collection is done. Call `reject()` to discard the most recently
|
|
234
|
+
* generated element.
|
|
235
|
+
*
|
|
236
|
+
* StopTest errors from any collection command propagate as {@link DataExhausted}
|
|
237
|
+
* (same as in `generateFromSchema`).
|
|
238
|
+
*/
|
|
239
|
+
export class Collection {
|
|
240
|
+
_baseName;
|
|
241
|
+
_serverName = null;
|
|
242
|
+
_serverNameResolved = false;
|
|
243
|
+
_finished = false;
|
|
244
|
+
/** Minimum number of elements. */
|
|
245
|
+
minSize;
|
|
246
|
+
/** Maximum number of elements (null = unlimited). */
|
|
247
|
+
maxSize;
|
|
248
|
+
constructor(name, minSize = 0, maxSize = null) {
|
|
249
|
+
this._baseName = name;
|
|
250
|
+
this.minSize = minSize;
|
|
251
|
+
this.maxSize = maxSize;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get (or lazily initialize) the server-side collection name.
|
|
255
|
+
* Sends `new_collection` on first call.
|
|
256
|
+
*/
|
|
257
|
+
async _getServerName(data) {
|
|
258
|
+
if (!this._serverNameResolved) {
|
|
259
|
+
this._serverNameResolved = true;
|
|
260
|
+
const channel = data.channel;
|
|
261
|
+
try {
|
|
262
|
+
this._serverName = await channel
|
|
263
|
+
.request({
|
|
264
|
+
command: "new_collection",
|
|
265
|
+
name: this._baseName,
|
|
266
|
+
min_size: this.minSize,
|
|
267
|
+
max_size: this.maxSize,
|
|
268
|
+
})
|
|
269
|
+
.get();
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
if (isStopTest(e)) {
|
|
273
|
+
data.testAborted = true;
|
|
274
|
+
throw new DataExhausted("Server ran out of data");
|
|
275
|
+
}
|
|
276
|
+
throw e;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return this._serverName;
|
|
280
|
+
}
|
|
281
|
+
/** @internal */
|
|
282
|
+
async more(data) {
|
|
283
|
+
if (this._finished)
|
|
284
|
+
return false;
|
|
285
|
+
const serverName = await this._getServerName(data);
|
|
286
|
+
const channel = data.channel;
|
|
287
|
+
let result;
|
|
288
|
+
try {
|
|
289
|
+
result = await channel.request({ command: "collection_more", collection: serverName }).get();
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
if (isStopTest(e)) {
|
|
293
|
+
data.testAborted = true;
|
|
294
|
+
throw new DataExhausted("Server ran out of data");
|
|
295
|
+
}
|
|
296
|
+
throw e;
|
|
297
|
+
}
|
|
298
|
+
if (!result) {
|
|
299
|
+
this._finished = true;
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
/** @internal */
|
|
304
|
+
async reject(data, why = null) {
|
|
305
|
+
if (this._finished)
|
|
306
|
+
return;
|
|
307
|
+
const serverName = await this._getServerName(data);
|
|
308
|
+
const channel = data.channel;
|
|
309
|
+
await channel
|
|
310
|
+
.request({
|
|
311
|
+
command: "collection_reject",
|
|
312
|
+
collection: serverName,
|
|
313
|
+
why,
|
|
314
|
+
})
|
|
315
|
+
.get();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Check if a caught value is a StopTest RequestError. */
|
|
319
|
+
function isStopTest(e) {
|
|
320
|
+
return e instanceof RequestError && e.errorType === "StopTest";
|
|
321
|
+
}
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Built-in generators
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
/**
|
|
326
|
+
* Generate integers.
|
|
327
|
+
*
|
|
328
|
+
* @param minValue - Minimum value (inclusive), or null for unbounded.
|
|
329
|
+
* @param maxValue - Maximum value (inclusive), or null for unbounded.
|
|
330
|
+
*/
|
|
331
|
+
export function integers(minValue = null, maxValue = null) {
|
|
332
|
+
const schema = { type: "integer" };
|
|
333
|
+
if (minValue !== null)
|
|
334
|
+
schema["min_value"] = minValue;
|
|
335
|
+
if (maxValue !== null)
|
|
336
|
+
schema["max_value"] = maxValue;
|
|
337
|
+
return new BasicGenerator(schema);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Generate floating-point numbers.
|
|
341
|
+
*
|
|
342
|
+
* By default, allows NaN and infinity unless a range is given. When a min or
|
|
343
|
+
* max is provided the defaults tighten: NaN becomes disallowed, and infinity
|
|
344
|
+
* is disallowed on the bounded side.
|
|
345
|
+
*
|
|
346
|
+
* @param minValue - Minimum value (inclusive), or null for unbounded.
|
|
347
|
+
* @param maxValue - Maximum value (inclusive), or null for unbounded.
|
|
348
|
+
* @param allowNan - Whether to allow NaN. Defaults to true only when both bounds are absent.
|
|
349
|
+
* @param allowInfinity - Whether to allow ±Infinity. Defaults to true when at least one bound is absent.
|
|
350
|
+
* @param excludeMin - Whether to exclude the minimum value (open interval on the left).
|
|
351
|
+
* @param excludeMax - Whether to exclude the maximum value (open interval on the right).
|
|
352
|
+
*/
|
|
353
|
+
export function floats(minValue = null, maxValue = null, allowNan = null, allowInfinity = null, excludeMin = false, excludeMax = false) {
|
|
354
|
+
const hasMin = minValue !== null;
|
|
355
|
+
const hasMax = maxValue !== null;
|
|
356
|
+
const resolvedAllowNan = allowNan !== null ? allowNan : !hasMin && !hasMax;
|
|
357
|
+
const resolvedAllowInfinity = allowInfinity !== null ? allowInfinity : !hasMin || !hasMax;
|
|
358
|
+
const schema = { type: "float" };
|
|
359
|
+
if (hasMin)
|
|
360
|
+
schema["min_value"] = minValue;
|
|
361
|
+
if (hasMax)
|
|
362
|
+
schema["max_value"] = maxValue;
|
|
363
|
+
schema["allow_nan"] = resolvedAllowNan;
|
|
364
|
+
schema["allow_infinity"] = resolvedAllowInfinity;
|
|
365
|
+
// exclude_min/exclude_max are only valid when the corresponding bound is set;
|
|
366
|
+
// sending them without a bound causes the server to return InvalidArgument.
|
|
367
|
+
schema["exclude_min"] = hasMin && excludeMin;
|
|
368
|
+
schema["exclude_max"] = hasMax && excludeMax;
|
|
369
|
+
schema["width"] = 64;
|
|
370
|
+
return new BasicGenerator(schema);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Generate booleans.
|
|
374
|
+
*
|
|
375
|
+
* @param p - Probability of generating `true`. Defaults to 0.5.
|
|
376
|
+
*/
|
|
377
|
+
export function booleans(p = 0.5) {
|
|
378
|
+
return new BasicGenerator({ type: "boolean", p });
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Generate text strings.
|
|
382
|
+
*
|
|
383
|
+
* @param minSize - Minimum number of Unicode codepoints. Defaults to 0.
|
|
384
|
+
* @param maxSize - Maximum number of Unicode codepoints, or null for unbounded.
|
|
385
|
+
*/
|
|
386
|
+
export function text(minSize = 0, maxSize = null) {
|
|
387
|
+
const schema = { type: "string", min_size: minSize };
|
|
388
|
+
if (maxSize !== null)
|
|
389
|
+
schema["max_size"] = maxSize;
|
|
390
|
+
return new BasicGenerator(schema);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Generate binary data (byte strings).
|
|
394
|
+
*
|
|
395
|
+
* The server returns CBOR byte strings which are decoded directly as
|
|
396
|
+
* `Uint8Array`. No transform is needed.
|
|
397
|
+
*
|
|
398
|
+
* @param minSize - Minimum byte length. Defaults to 0.
|
|
399
|
+
* @param maxSize - Maximum byte length, or null for unbounded.
|
|
400
|
+
*/
|
|
401
|
+
export function binary(minSize = 0, maxSize = null) {
|
|
402
|
+
const schema = { type: "binary", min_size: minSize };
|
|
403
|
+
if (maxSize !== null)
|
|
404
|
+
schema["max_size"] = maxSize;
|
|
405
|
+
return new BasicGenerator(schema);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Always return the same constant value, ignoring the server's suggestion.
|
|
409
|
+
*
|
|
410
|
+
* @param value - The constant to always return.
|
|
411
|
+
*/
|
|
412
|
+
export function just(value) {
|
|
413
|
+
return new BasicGenerator({ const: null }, (_raw) => value);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Pick uniformly at random from a list of values.
|
|
417
|
+
*
|
|
418
|
+
* The server generates an integer index; the transform maps it to the
|
|
419
|
+
* corresponding element of `values`.
|
|
420
|
+
*
|
|
421
|
+
* @param values - The list to sample from. Must be non-empty.
|
|
422
|
+
* @throws {Error} If `values` is empty.
|
|
423
|
+
*/
|
|
424
|
+
export function sampledFrom(values) {
|
|
425
|
+
const elements = Array.from(values);
|
|
426
|
+
if (elements.length === 0) {
|
|
427
|
+
throw new Error("sampledFrom requires at least one element");
|
|
428
|
+
}
|
|
429
|
+
const schema = {
|
|
430
|
+
type: "integer",
|
|
431
|
+
min_value: 0,
|
|
432
|
+
max_value: elements.length - 1,
|
|
433
|
+
};
|
|
434
|
+
return new BasicGenerator(schema, (idx) => elements[idx]);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Generate strings matching a regular expression pattern.
|
|
438
|
+
*
|
|
439
|
+
* @param pattern - The regex pattern to match.
|
|
440
|
+
* @param fullmatch - If true (default), the entire string must match the pattern.
|
|
441
|
+
* If false, a substring match is sufficient.
|
|
442
|
+
*/
|
|
443
|
+
export function fromRegex(pattern, fullmatch = true) {
|
|
444
|
+
return new BasicGenerator({ type: "regex", pattern, fullmatch });
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Generate email addresses.
|
|
448
|
+
*
|
|
449
|
+
* Each generated value is a valid email address string containing '@'.
|
|
450
|
+
*/
|
|
451
|
+
export function emails() {
|
|
452
|
+
return new BasicGenerator({ type: "email" });
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Generate URLs.
|
|
456
|
+
*
|
|
457
|
+
* Each generated value is a valid URL string starting with "http://" or "https://".
|
|
458
|
+
*/
|
|
459
|
+
export function urls() {
|
|
460
|
+
return new BasicGenerator({ type: "url" });
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Generate domain names.
|
|
464
|
+
*
|
|
465
|
+
* @param maxLength - Optional maximum length for generated domain names.
|
|
466
|
+
*/
|
|
467
|
+
export function domains(maxLength = null) {
|
|
468
|
+
const schema = { type: "domain" };
|
|
469
|
+
if (maxLength !== null)
|
|
470
|
+
schema["max_length"] = maxLength;
|
|
471
|
+
return new BasicGenerator(schema);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Generate dates.
|
|
475
|
+
*
|
|
476
|
+
* Each generated value is an ISO 8601 date string in YYYY-MM-DD format.
|
|
477
|
+
*/
|
|
478
|
+
export function dates() {
|
|
479
|
+
return new BasicGenerator({ type: "date" });
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Generate times.
|
|
483
|
+
*
|
|
484
|
+
* Each generated value is a time string containing ':'.
|
|
485
|
+
*/
|
|
486
|
+
export function times() {
|
|
487
|
+
return new BasicGenerator({ type: "time" });
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Generate datetimes.
|
|
491
|
+
*
|
|
492
|
+
* Each generated value is a datetime string containing 'T'.
|
|
493
|
+
*/
|
|
494
|
+
export function datetimes() {
|
|
495
|
+
return new BasicGenerator({ type: "datetime" });
|
|
496
|
+
}
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
// CompositeTupleGenerator
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
/**
|
|
501
|
+
* A tuple generator for elements that are not all basic.
|
|
502
|
+
*
|
|
503
|
+
* Generates each element in sequence inside a TUPLE span (label 7).
|
|
504
|
+
* Used when at least one element cannot be represented as a basic schema.
|
|
505
|
+
*/
|
|
506
|
+
export class CompositeTupleGenerator extends Generator {
|
|
507
|
+
_elements;
|
|
508
|
+
constructor(elements) {
|
|
509
|
+
super();
|
|
510
|
+
this._elements = elements;
|
|
511
|
+
}
|
|
512
|
+
async doDraw(data) {
|
|
513
|
+
await startSpan(Labels.TUPLE, data);
|
|
514
|
+
try {
|
|
515
|
+
const result = [];
|
|
516
|
+
for (const elem of this._elements) {
|
|
517
|
+
result.push(await elem.doDraw(data));
|
|
518
|
+
}
|
|
519
|
+
return result;
|
|
520
|
+
}
|
|
521
|
+
finally {
|
|
522
|
+
await stopSpan({}, data);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// tuples2, tuples3, tuples4
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
/**
|
|
530
|
+
* Generate a 2-tuple (pair).
|
|
531
|
+
*
|
|
532
|
+
* If both elements are {@link BasicGenerator}s, returns a BasicGenerator with
|
|
533
|
+
* a tuple schema so the server can see and shrink both components. Otherwise
|
|
534
|
+
* returns a CompositeTupleGenerator that generates each element inside
|
|
535
|
+
* a TUPLE span.
|
|
536
|
+
*
|
|
537
|
+
* @param g1 - Generator for the first element.
|
|
538
|
+
* @param g2 - Generator for the second element.
|
|
539
|
+
*/
|
|
540
|
+
export function tuples2(g1, g2) {
|
|
541
|
+
if (g1 instanceof BasicGenerator && g2 instanceof BasicGenerator) {
|
|
542
|
+
return _basicTuple([g1, g2]);
|
|
543
|
+
}
|
|
544
|
+
return new CompositeTupleGenerator([g1, g2]);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Generate a 3-tuple.
|
|
548
|
+
*
|
|
549
|
+
* If all elements are {@link BasicGenerator}s, returns a BasicGenerator with
|
|
550
|
+
* a tuple schema. Otherwise returns a CompositeTupleGenerator.
|
|
551
|
+
*
|
|
552
|
+
* @param g1 - Generator for the first element.
|
|
553
|
+
* @param g2 - Generator for the second element.
|
|
554
|
+
* @param g3 - Generator for the third element.
|
|
555
|
+
*/
|
|
556
|
+
export function tuples3(g1, g2, g3) {
|
|
557
|
+
if (g1 instanceof BasicGenerator &&
|
|
558
|
+
g2 instanceof BasicGenerator &&
|
|
559
|
+
g3 instanceof BasicGenerator) {
|
|
560
|
+
return _basicTuple([g1, g2, g3]);
|
|
561
|
+
}
|
|
562
|
+
return new CompositeTupleGenerator([g1, g2, g3]);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Generate a 4-tuple.
|
|
566
|
+
*
|
|
567
|
+
* If all elements are {@link BasicGenerator}s, returns a BasicGenerator with
|
|
568
|
+
* a tuple schema. Otherwise returns a CompositeTupleGenerator.
|
|
569
|
+
*
|
|
570
|
+
* @param g1 - Generator for the first element.
|
|
571
|
+
* @param g2 - Generator for the second element.
|
|
572
|
+
* @param g3 - Generator for the third element.
|
|
573
|
+
* @param g4 - Generator for the fourth element.
|
|
574
|
+
*/
|
|
575
|
+
export function tuples4(g1, g2, g3, g4) {
|
|
576
|
+
if (g1 instanceof BasicGenerator &&
|
|
577
|
+
g2 instanceof BasicGenerator &&
|
|
578
|
+
g3 instanceof BasicGenerator &&
|
|
579
|
+
g4 instanceof BasicGenerator) {
|
|
580
|
+
return _basicTuple([g1, g2, g3, g4]);
|
|
581
|
+
}
|
|
582
|
+
return new CompositeTupleGenerator([g1, g2, g3, g4]);
|
|
583
|
+
}
|
|
584
|
+
// ---------------------------------------------------------------------------
|
|
585
|
+
// CompositeListGenerator
|
|
586
|
+
// ---------------------------------------------------------------------------
|
|
587
|
+
/**
|
|
588
|
+
* A list generator for elements that are not basic (e.g., filtered or mapped
|
|
589
|
+
* through a non-basic path). Uses the collection protocol in a LIST span.
|
|
590
|
+
*
|
|
591
|
+
* @typeParam T - The element type.
|
|
592
|
+
*/
|
|
593
|
+
export class CompositeListGenerator extends Generator {
|
|
594
|
+
_elements;
|
|
595
|
+
_minSize;
|
|
596
|
+
_maxSize;
|
|
597
|
+
constructor(elements, minSize = 0, maxSize = null) {
|
|
598
|
+
super();
|
|
599
|
+
this._elements = elements;
|
|
600
|
+
this._minSize = minSize;
|
|
601
|
+
this._maxSize = maxSize;
|
|
602
|
+
}
|
|
603
|
+
async doDraw(data) {
|
|
604
|
+
// Create a fresh Collection for each doDraw() call so that _finished
|
|
605
|
+
// state from prior calls does not carry over.
|
|
606
|
+
const collection = new Collection("composite_list", this._minSize, this._maxSize);
|
|
607
|
+
await startSpan(Labels.LIST, data);
|
|
608
|
+
try {
|
|
609
|
+
const result = [];
|
|
610
|
+
while (await collection.more(data)) {
|
|
611
|
+
result.push(await this._elements.doDraw(data));
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
await stopSpan({}, data);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// ---------------------------------------------------------------------------
|
|
621
|
+
// lists()
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
/**
|
|
624
|
+
* Generate lists of elements.
|
|
625
|
+
*
|
|
626
|
+
* When `elements` is a {@link BasicGenerator}, the list schema is sent to the
|
|
627
|
+
* server directly (optimal shrinking). If the element generator has a transform,
|
|
628
|
+
* a list-level transform is composed that applies it to each item. When elements
|
|
629
|
+
* is a composite generator (e.g., filtered or mapped), the collection protocol
|
|
630
|
+
* is used in a LIST span via CompositeListGenerator.
|
|
631
|
+
*
|
|
632
|
+
* @param elements - Generator for list elements.
|
|
633
|
+
* @param minSize - Minimum list length. Defaults to 0.
|
|
634
|
+
* @param maxSize - Maximum list length, or null for unbounded.
|
|
635
|
+
*/
|
|
636
|
+
export function lists(elements, minSize = 0, maxSize = null) {
|
|
637
|
+
if (elements instanceof BasicGenerator) {
|
|
638
|
+
const rawSchema = {
|
|
639
|
+
type: "list",
|
|
640
|
+
elements: elements._rawSchema,
|
|
641
|
+
min_size: minSize,
|
|
642
|
+
};
|
|
643
|
+
if (maxSize !== null) {
|
|
644
|
+
rawSchema["max_size"] = maxSize;
|
|
645
|
+
}
|
|
646
|
+
const elemTransform = elements._transform;
|
|
647
|
+
if (elemTransform !== null) {
|
|
648
|
+
const listTransform = (rawList) => rawList.map((item) => elemTransform(item));
|
|
649
|
+
return new BasicGenerator(rawSchema, listTransform);
|
|
650
|
+
}
|
|
651
|
+
return new BasicGenerator(rawSchema);
|
|
652
|
+
}
|
|
653
|
+
return new CompositeListGenerator(elements, minSize, maxSize);
|
|
654
|
+
}
|
|
655
|
+
// ---------------------------------------------------------------------------
|
|
656
|
+
// Internal helpers
|
|
657
|
+
// ---------------------------------------------------------------------------
|
|
658
|
+
/**
|
|
659
|
+
* Build a BasicGenerator for a tuple from an array of BasicGenerators.
|
|
660
|
+
*
|
|
661
|
+
* Combines their raw schemas into `{"type":"tuple","elements":[...]}`.
|
|
662
|
+
* If any element has a transform, builds a combined transform that applies
|
|
663
|
+
* each element's transform to the corresponding position.
|
|
664
|
+
*
|
|
665
|
+
* @internal
|
|
666
|
+
*/
|
|
667
|
+
function _basicTuple(elements) {
|
|
668
|
+
const rawSchemas = elements.map((e) => e._rawSchema);
|
|
669
|
+
const transforms = elements.map((e) => e._transform);
|
|
670
|
+
const combinedSchema = { type: "tuple", elements: rawSchemas };
|
|
671
|
+
if (transforms.every((t) => t === null)) {
|
|
672
|
+
return new BasicGenerator(combinedSchema);
|
|
673
|
+
}
|
|
674
|
+
const applyTransforms = (rawTuple) => {
|
|
675
|
+
const arr = rawTuple;
|
|
676
|
+
return arr.map((raw, i) => {
|
|
677
|
+
const t = transforms[i];
|
|
678
|
+
return t !== null ? t(raw) : raw;
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
return new BasicGenerator(combinedSchema, applyTransforms);
|
|
682
|
+
}
|
|
683
|
+
// ---------------------------------------------------------------------------
|
|
684
|
+
// CompositeOneOfGenerator
|
|
685
|
+
// ---------------------------------------------------------------------------
|
|
686
|
+
/**
|
|
687
|
+
* A one_of generator for generators that cannot be represented as a single schema
|
|
688
|
+
* (i.e., when at least one branch is not a {@link BasicGenerator}).
|
|
689
|
+
*
|
|
690
|
+
* Generates an integer index then delegates to the selected generator. Wrapped
|
|
691
|
+
* in a ONE_OF span (label `Labels.ONE_OF`) so the server tracks the choice.
|
|
692
|
+
*/
|
|
693
|
+
export class CompositeOneOfGenerator extends Generator {
|
|
694
|
+
/** @internal */
|
|
695
|
+
_generators;
|
|
696
|
+
constructor(generators) {
|
|
697
|
+
super();
|
|
698
|
+
this._generators = generators;
|
|
699
|
+
}
|
|
700
|
+
async doDraw(data) {
|
|
701
|
+
await startSpan(Labels.ONE_OF, data);
|
|
702
|
+
try {
|
|
703
|
+
const index = (await generateFromSchema({
|
|
704
|
+
type: "integer",
|
|
705
|
+
min_value: 0,
|
|
706
|
+
max_value: this._generators.length - 1,
|
|
707
|
+
}, data));
|
|
708
|
+
return await this._generators[index].doDraw(data);
|
|
709
|
+
}
|
|
710
|
+
finally {
|
|
711
|
+
await stopSpan({}, data);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// ---------------------------------------------------------------------------
|
|
716
|
+
// oneOf
|
|
717
|
+
// ---------------------------------------------------------------------------
|
|
718
|
+
/**
|
|
719
|
+
* Choose uniformly between two or more generators.
|
|
720
|
+
*
|
|
721
|
+
* There are three implementation paths depending on the inputs:
|
|
722
|
+
*
|
|
723
|
+
* - **Path 1** — all branches are {@link BasicGenerator} with no transform:
|
|
724
|
+
* returns a `BasicGenerator` with `{"one_of": [...schemas]}`.
|
|
725
|
+
* - **Path 2** — all branches are {@link BasicGenerator} but some have transforms:
|
|
726
|
+
* returns a `BasicGenerator` using tagged-tuple schemas so each branch can
|
|
727
|
+
* carry its own transform.
|
|
728
|
+
* - **Path 3** — any branch is not a `BasicGenerator`:
|
|
729
|
+
* returns a CompositeOneOfGenerator that generates an index then
|
|
730
|
+
* delegates to the selected generator inside a ONE_OF span.
|
|
731
|
+
*
|
|
732
|
+
* @param generators - Two or more generators to choose between.
|
|
733
|
+
* @throws {Error} If fewer than 2 generators are provided.
|
|
734
|
+
*/
|
|
735
|
+
export function oneOf(...generators) {
|
|
736
|
+
if (generators.length < 2) {
|
|
737
|
+
throw new Error("oneOf requires at least 2 generators");
|
|
738
|
+
}
|
|
739
|
+
// Check if all generators are BasicGenerator instances
|
|
740
|
+
const allBasic = generators.every((g) => g instanceof BasicGenerator);
|
|
741
|
+
if (!allBasic) {
|
|
742
|
+
// Path 3: composite
|
|
743
|
+
return new CompositeOneOfGenerator(generators);
|
|
744
|
+
}
|
|
745
|
+
const basicGenerators = generators;
|
|
746
|
+
const allIdentity = basicGenerators.every((g) => g._transform === null);
|
|
747
|
+
if (allIdentity) {
|
|
748
|
+
// Path 1: all basic, no transforms — flat one_of schema
|
|
749
|
+
const schemas = basicGenerators.map((g) => g._rawSchema);
|
|
750
|
+
return new BasicGenerator({ one_of: schemas });
|
|
751
|
+
}
|
|
752
|
+
// Path 2: all basic, some have transforms — use tagged tuples
|
|
753
|
+
const taggedSchemas = basicGenerators.map((g, i) => ({
|
|
754
|
+
type: "tuple",
|
|
755
|
+
elements: [{ const: i }, g._rawSchema],
|
|
756
|
+
}));
|
|
757
|
+
const transforms = basicGenerators.map((g) => g._transform);
|
|
758
|
+
const applyTaggedTransform = (tagged) => {
|
|
759
|
+
const [tag, value] = tagged;
|
|
760
|
+
const transform = transforms[tag];
|
|
761
|
+
if (transform !== null) {
|
|
762
|
+
return transform(value);
|
|
763
|
+
}
|
|
764
|
+
return value;
|
|
765
|
+
};
|
|
766
|
+
return new BasicGenerator({ one_of: taggedSchemas }, applyTaggedTransform);
|
|
767
|
+
}
|
|
768
|
+
// ---------------------------------------------------------------------------
|
|
769
|
+
// optional
|
|
770
|
+
// ---------------------------------------------------------------------------
|
|
771
|
+
/**
|
|
772
|
+
* Optionally generate a value — returns `null` or a value from `element`.
|
|
773
|
+
*
|
|
774
|
+
* Equivalent to `oneOf(just(null), element)`.
|
|
775
|
+
*
|
|
776
|
+
* @param element - Generator for the non-null case.
|
|
777
|
+
*/
|
|
778
|
+
export function optional(element) {
|
|
779
|
+
return oneOf(just(null), element);
|
|
780
|
+
}
|
|
781
|
+
// ---------------------------------------------------------------------------
|
|
782
|
+
// ipAddresses
|
|
783
|
+
// ---------------------------------------------------------------------------
|
|
784
|
+
/**
|
|
785
|
+
* Generate IP addresses.
|
|
786
|
+
*
|
|
787
|
+
* @param version - IP version: `4` for IPv4, `6` for IPv6, or `null` (default)
|
|
788
|
+
* to generate both versions mixed.
|
|
789
|
+
*/
|
|
790
|
+
export function ipAddresses(version = null) {
|
|
791
|
+
if (version === 4) {
|
|
792
|
+
return new BasicGenerator({ type: "ipv4" });
|
|
793
|
+
}
|
|
794
|
+
if (version === 6) {
|
|
795
|
+
return new BasicGenerator({ type: "ipv6" });
|
|
796
|
+
}
|
|
797
|
+
// version === null: both v4 and v6
|
|
798
|
+
return oneOf(ipAddresses(4), ipAddresses(6));
|
|
799
|
+
}
|
|
800
|
+
// ---------------------------------------------------------------------------
|
|
801
|
+
// CompositeDictGenerator
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
/**
|
|
804
|
+
* A dict generator for keys or values that are not basic (have no server schema).
|
|
805
|
+
*
|
|
806
|
+
* Uses the MAP span (label 5) for the whole dict and MAP_ENTRY spans (label 6)
|
|
807
|
+
* for each key-value pair. The server decides the size via generateFromSchema.
|
|
808
|
+
*/
|
|
809
|
+
export class CompositeDictGenerator extends Generator {
|
|
810
|
+
/** @internal */
|
|
811
|
+
_keys;
|
|
812
|
+
/** @internal */
|
|
813
|
+
_values;
|
|
814
|
+
/** @internal */
|
|
815
|
+
_minSize;
|
|
816
|
+
/** @internal */
|
|
817
|
+
_maxSize;
|
|
818
|
+
constructor(keys, values, minSize, maxSize) {
|
|
819
|
+
super();
|
|
820
|
+
this._keys = keys;
|
|
821
|
+
this._values = values;
|
|
822
|
+
this._minSize = minSize;
|
|
823
|
+
this._maxSize = maxSize;
|
|
824
|
+
}
|
|
825
|
+
async doDraw(data) {
|
|
826
|
+
await startSpan(Labels.MAP, data);
|
|
827
|
+
try {
|
|
828
|
+
const maxSz = this._maxSize !== null ? this._maxSize : this._minSize + 10;
|
|
829
|
+
const size = (await generateFromSchema({
|
|
830
|
+
type: "integer",
|
|
831
|
+
min_value: this._minSize,
|
|
832
|
+
max_value: maxSz,
|
|
833
|
+
}, data));
|
|
834
|
+
const result = new Map();
|
|
835
|
+
for (let i = 0; i < size; i++) {
|
|
836
|
+
await startSpan(Labels.MAP_ENTRY, data);
|
|
837
|
+
const key = await this._keys.doDraw(data);
|
|
838
|
+
const value = await this._values.doDraw(data);
|
|
839
|
+
result.set(key, value);
|
|
840
|
+
await stopSpan({}, data);
|
|
841
|
+
}
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
finally {
|
|
845
|
+
await stopSpan({}, data);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
// ---------------------------------------------------------------------------
|
|
850
|
+
// dicts()
|
|
851
|
+
// ---------------------------------------------------------------------------
|
|
852
|
+
/**
|
|
853
|
+
* Generate dictionaries with keys and values from the given generators.
|
|
854
|
+
*
|
|
855
|
+
* When both `keys` and `values` are {@link BasicGenerator}s, the server handles
|
|
856
|
+
* the full dict generation (basic path) and the result is a plain
|
|
857
|
+
* `Record<string, unknown>`. Otherwise a CompositeDictGenerator is used
|
|
858
|
+
* (non-basic path) which returns a `Map<K, V>`.
|
|
859
|
+
*
|
|
860
|
+
* @param keys - Generator for dictionary keys.
|
|
861
|
+
* @param values - Generator for dictionary values.
|
|
862
|
+
* @param minSize - Minimum number of entries. Defaults to 0.
|
|
863
|
+
* @param maxSize - Maximum number of entries, or null for unbounded.
|
|
864
|
+
*/
|
|
865
|
+
export function dicts(keys, values, minSize = 0, maxSize = null) {
|
|
866
|
+
if (keys instanceof BasicGenerator && values instanceof BasicGenerator) {
|
|
867
|
+
const rawSchema = {
|
|
868
|
+
type: "dict",
|
|
869
|
+
keys: keys._rawSchema,
|
|
870
|
+
values: values._rawSchema,
|
|
871
|
+
min_size: minSize,
|
|
872
|
+
};
|
|
873
|
+
if (maxSize !== null)
|
|
874
|
+
rawSchema["max_size"] = maxSize;
|
|
875
|
+
const keyTransform = keys._transform;
|
|
876
|
+
const valueTransform = values._transform;
|
|
877
|
+
if (keyTransform === null && valueTransform === null) {
|
|
878
|
+
return new BasicGenerator(rawSchema, (items) => {
|
|
879
|
+
return Object.fromEntries(items);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
return new BasicGenerator(rawSchema, (items) => {
|
|
884
|
+
const result = {};
|
|
885
|
+
for (const [k, v] of items) {
|
|
886
|
+
const key = keyTransform !== null ? String(keyTransform(k)) : String(k);
|
|
887
|
+
const value = valueTransform !== null ? valueTransform(v) : v;
|
|
888
|
+
result[key] = value;
|
|
889
|
+
}
|
|
890
|
+
return result;
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
return new CompositeDictGenerator(keys, values, minSize, maxSize);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
//# sourceMappingURL=generators.js.map
|