effect 3.14.0 → 3.14.2
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/dist/cjs/Arbitrary.js +595 -426
- package/dist/cjs/Arbitrary.js.map +1 -1
- package/dist/cjs/Context.js.map +1 -1
- package/dist/cjs/Schema.js +3 -3
- package/dist/cjs/Schema.js.map +1 -1
- package/dist/cjs/SchemaAST.js +7 -2
- package/dist/cjs/SchemaAST.js.map +1 -1
- package/dist/cjs/internal/context.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Arbitrary.d.ts.map +1 -1
- package/dist/dts/Context.d.ts +4 -8
- package/dist/dts/Context.d.ts.map +1 -1
- package/dist/dts/Schema.d.ts +1 -1
- package/dist/dts/Schema.d.ts.map +1 -1
- package/dist/dts/SchemaAST.d.ts +5 -0
- package/dist/dts/SchemaAST.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Arbitrary.js +593 -423
- package/dist/esm/Arbitrary.js.map +1 -1
- package/dist/esm/Context.js.map +1 -1
- package/dist/esm/Schema.js +3 -3
- package/dist/esm/Schema.js.map +1 -1
- package/dist/esm/SchemaAST.js +5 -0
- package/dist/esm/SchemaAST.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/context.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Arbitrary.ts +799 -477
- package/src/Context.ts +8 -9
- package/src/Schema.ts +4 -3
- package/src/SchemaAST.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal/context.ts +9 -9
- package/src/internal/version.ts +1 -1
package/src/Arbitrary.ts
CHANGED
|
@@ -11,7 +11,7 @@ import * as util_ from "./internal/schema/util.js"
|
|
|
11
11
|
import * as Option from "./Option.js"
|
|
12
12
|
import * as Predicate from "./Predicate.js"
|
|
13
13
|
import type * as Schema from "./Schema.js"
|
|
14
|
-
import * as
|
|
14
|
+
import * as SchemaAST from "./SchemaAST.js"
|
|
15
15
|
import type * as Types from "./Types.js"
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -49,8 +49,10 @@ export type ArbitraryAnnotation<A, TypeParameters extends ReadonlyArray<any> = r
|
|
|
49
49
|
* @category arbitrary
|
|
50
50
|
* @since 3.10.0
|
|
51
51
|
*/
|
|
52
|
-
export const makeLazy = <A, I, R>(schema: Schema.Schema<A, I, R>): LazyArbitrary<A> =>
|
|
53
|
-
|
|
52
|
+
export const makeLazy = <A, I, R>(schema: Schema.Schema<A, I, R>): LazyArbitrary<A> => {
|
|
53
|
+
const description = getDescription(schema.ast, [])
|
|
54
|
+
return go(description, { maxDepth: 2 })
|
|
55
|
+
}
|
|
54
56
|
|
|
55
57
|
/**
|
|
56
58
|
* Returns a fast-check Arbitrary for the `A` type of the provided schema.
|
|
@@ -60,65 +62,6 @@ export const makeLazy = <A, I, R>(schema: Schema.Schema<A, I, R>): LazyArbitrary
|
|
|
60
62
|
*/
|
|
61
63
|
export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): FastCheck.Arbitrary<A> => makeLazy(schema)(FastCheck)
|
|
62
64
|
|
|
63
|
-
const getArbitraryAnnotation = AST.getAnnotation<ArbitraryAnnotation<any, any>>(AST.ArbitraryAnnotationId)
|
|
64
|
-
|
|
65
|
-
type Op = Succeed | Deferred
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Represents an arbitrary with optional filters.
|
|
69
|
-
*/
|
|
70
|
-
class Succeed {
|
|
71
|
-
readonly _tag = "Succeed"
|
|
72
|
-
constructor(
|
|
73
|
-
readonly lazyArbitrary: LazyArbitrary<any>,
|
|
74
|
-
readonly filters: Array<Predicate.Predicate<any>> = []
|
|
75
|
-
) {}
|
|
76
|
-
|
|
77
|
-
toLazyArbitrary(): LazyArbitrary<any> {
|
|
78
|
-
return (fc) => {
|
|
79
|
-
let out = this.lazyArbitrary(fc)
|
|
80
|
-
for (const f of this.filters) {
|
|
81
|
-
out = out.filter(f)
|
|
82
|
-
}
|
|
83
|
-
return out
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Represents a deferred arbitrary value generator with optional filters.
|
|
90
|
-
*/
|
|
91
|
-
class Deferred {
|
|
92
|
-
readonly _tag = "Deferred"
|
|
93
|
-
constructor(
|
|
94
|
-
readonly config: Config,
|
|
95
|
-
readonly filters: Array<Predicate.Predicate<any>> = []
|
|
96
|
-
) {}
|
|
97
|
-
|
|
98
|
-
toLazyArbitrary(ctx: ArbitraryGenerationContext, path: ReadonlyArray<PropertyKey>): LazyArbitrary<any> {
|
|
99
|
-
const config = this.config
|
|
100
|
-
switch (config._tag) {
|
|
101
|
-
case "StringConstraints": {
|
|
102
|
-
const pattern = config.pattern
|
|
103
|
-
return pattern !== undefined ?
|
|
104
|
-
(fc) => fc.stringMatching(new RegExp(pattern)) :
|
|
105
|
-
(fc) => fc.string(config.constraints)
|
|
106
|
-
}
|
|
107
|
-
case "NumberConstraints": {
|
|
108
|
-
return config.isInteger ?
|
|
109
|
-
(fc) => fc.integer(config.constraints) :
|
|
110
|
-
(fc) => fc.float(config.constraints)
|
|
111
|
-
}
|
|
112
|
-
case "BigIntConstraints":
|
|
113
|
-
return (fc) => fc.bigInt(config.constraints)
|
|
114
|
-
case "DateConstraints":
|
|
115
|
-
return (fc) => fc.date(config.constraints)
|
|
116
|
-
case "ArrayConstraints":
|
|
117
|
-
return goTupleType(config.ast, ctx, path, config.constraints)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
65
|
interface StringConstraints {
|
|
123
66
|
readonly _tag: "StringConstraints"
|
|
124
67
|
readonly constraints: FastCheck.StringSharedConstraints
|
|
@@ -156,9 +99,9 @@ interface NumberConstraints {
|
|
|
156
99
|
/** @internal */
|
|
157
100
|
export const makeNumberConstraints = (options: {
|
|
158
101
|
readonly isInteger?: boolean | undefined
|
|
159
|
-
readonly min?:
|
|
102
|
+
readonly min?: unknown
|
|
160
103
|
readonly minExcluded?: boolean | undefined
|
|
161
|
-
readonly max?:
|
|
104
|
+
readonly max?: unknown
|
|
162
105
|
readonly maxExcluded?: boolean | undefined
|
|
163
106
|
readonly noNaN?: boolean | undefined
|
|
164
107
|
readonly noDefaultInfinity?: boolean | undefined
|
|
@@ -219,8 +162,8 @@ interface ArrayConstraints {
|
|
|
219
162
|
|
|
220
163
|
/** @internal */
|
|
221
164
|
export const makeArrayConstraints = (options: {
|
|
222
|
-
readonly minLength?:
|
|
223
|
-
readonly maxLength?:
|
|
165
|
+
readonly minLength?: unknown
|
|
166
|
+
readonly maxLength?: unknown
|
|
224
167
|
}): ArrayConstraints => {
|
|
225
168
|
const out: Types.Mutable<ArrayConstraints> = {
|
|
226
169
|
_tag: "ArrayConstraints",
|
|
@@ -248,9 +191,7 @@ export const makeDateConstraints = (options: {
|
|
|
248
191
|
}): DateConstraints => {
|
|
249
192
|
const out: Types.Mutable<DateConstraints> = {
|
|
250
193
|
_tag: "DateConstraints",
|
|
251
|
-
constraints: {
|
|
252
|
-
noInvalidDate: options.noInvalidDate ?? false
|
|
253
|
-
}
|
|
194
|
+
constraints: {}
|
|
254
195
|
}
|
|
255
196
|
if (Predicate.isDate(options.min)) {
|
|
256
197
|
out.constraints.min = options.min
|
|
@@ -258,69 +199,151 @@ export const makeDateConstraints = (options: {
|
|
|
258
199
|
if (Predicate.isDate(options.max)) {
|
|
259
200
|
out.constraints.max = options.max
|
|
260
201
|
}
|
|
202
|
+
if (Predicate.isBoolean(options.noInvalidDate)) {
|
|
203
|
+
out.constraints.noInvalidDate = options.noInvalidDate
|
|
204
|
+
}
|
|
261
205
|
return out
|
|
262
206
|
}
|
|
263
207
|
|
|
264
|
-
|
|
265
|
-
|
|
208
|
+
type Refinements = ReadonlyArray<SchemaAST.Refinement>
|
|
209
|
+
|
|
210
|
+
interface Base {
|
|
211
|
+
readonly path: ReadonlyArray<PropertyKey>
|
|
212
|
+
readonly refinements: Refinements
|
|
213
|
+
readonly annotations: ReadonlyArray<ArbitraryAnnotation<any, any>>
|
|
266
214
|
}
|
|
267
215
|
|
|
268
|
-
|
|
269
|
-
readonly
|
|
270
|
-
readonly
|
|
271
|
-
}, ast: AST.TupleType): ArrayConfig => {
|
|
272
|
-
return {
|
|
273
|
-
ast,
|
|
274
|
-
...makeArrayConstraints(options)
|
|
275
|
-
}
|
|
216
|
+
interface StringKeyword extends Base {
|
|
217
|
+
readonly _tag: "StringKeyword"
|
|
218
|
+
readonly constraints: ReadonlyArray<StringConstraints>
|
|
276
219
|
}
|
|
277
220
|
|
|
278
|
-
|
|
221
|
+
interface NumberKeyword extends Base {
|
|
222
|
+
readonly _tag: "NumberKeyword"
|
|
223
|
+
readonly constraints: ReadonlyArray<NumberConstraints>
|
|
224
|
+
}
|
|
279
225
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
226
|
+
interface BigIntKeyword extends Base {
|
|
227
|
+
readonly _tag: "BigIntKeyword"
|
|
228
|
+
readonly constraints: ReadonlyArray<BigIntConstraints>
|
|
229
|
+
}
|
|
284
230
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
231
|
+
interface DateFromSelf extends Base {
|
|
232
|
+
readonly _tag: "DateFromSelf"
|
|
233
|
+
readonly constraints: ReadonlyArray<DateConstraints>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface Declaration extends Base {
|
|
237
|
+
readonly _tag: "Declaration"
|
|
238
|
+
readonly typeParameters: ReadonlyArray<Description>
|
|
239
|
+
readonly ast: SchemaAST.AST
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
interface TupleType extends Base {
|
|
243
|
+
readonly _tag: "TupleType"
|
|
244
|
+
readonly constraints: ReadonlyArray<ArrayConstraints>
|
|
245
|
+
readonly elements: ReadonlyArray<{
|
|
246
|
+
readonly isOptional: boolean
|
|
247
|
+
readonly description: Description
|
|
248
|
+
}>
|
|
249
|
+
readonly rest: ReadonlyArray<Description>
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
interface TypeLiteral extends Base {
|
|
253
|
+
readonly _tag: "TypeLiteral"
|
|
254
|
+
readonly propertySignatures: ReadonlyArray<{
|
|
255
|
+
readonly isOptional: boolean
|
|
256
|
+
readonly name: PropertyKey
|
|
257
|
+
readonly value: Description
|
|
258
|
+
}>
|
|
259
|
+
readonly indexSignatures: ReadonlyArray<{
|
|
260
|
+
readonly parameter: Description
|
|
261
|
+
readonly value: Description
|
|
262
|
+
}>
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface Union extends Base {
|
|
266
|
+
readonly _tag: "Union"
|
|
267
|
+
readonly members: ReadonlyArray<Description>
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
interface Suspend extends Base {
|
|
271
|
+
readonly _tag: "Suspend"
|
|
272
|
+
readonly id: string
|
|
273
|
+
readonly ast: SchemaAST.AST
|
|
274
|
+
readonly description: () => Description
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface Ref extends Base {
|
|
278
|
+
readonly _tag: "Ref"
|
|
279
|
+
readonly id: string
|
|
280
|
+
readonly ast: SchemaAST.AST
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
interface NeverKeyword extends Base {
|
|
284
|
+
readonly _tag: "NeverKeyword"
|
|
285
|
+
readonly ast: SchemaAST.AST
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
interface Keyword extends Base {
|
|
289
|
+
readonly _tag: "Keyword"
|
|
290
|
+
readonly value:
|
|
291
|
+
| "UndefinedKeyword"
|
|
292
|
+
| "VoidKeyword"
|
|
293
|
+
| "UnknownKeyword"
|
|
294
|
+
| "AnyKeyword"
|
|
295
|
+
| "BooleanKeyword"
|
|
296
|
+
| "SymbolKeyword"
|
|
297
|
+
| "ObjectKeyword"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface Literal extends Base {
|
|
301
|
+
readonly _tag: "Literal"
|
|
302
|
+
readonly literal: SchemaAST.LiteralValue
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
interface UniqueSymbol extends Base {
|
|
306
|
+
readonly _tag: "UniqueSymbol"
|
|
307
|
+
readonly symbol: symbol
|
|
315
308
|
}
|
|
316
309
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
310
|
+
interface Enums extends Base {
|
|
311
|
+
readonly _tag: "Enums"
|
|
312
|
+
readonly enums: ReadonlyArray<readonly [string, string | number]>
|
|
313
|
+
readonly ast: SchemaAST.AST
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
interface TemplateLiteral extends Base {
|
|
317
|
+
readonly _tag: "TemplateLiteral"
|
|
318
|
+
readonly head: string
|
|
319
|
+
readonly spans: ReadonlyArray<{
|
|
320
|
+
readonly description: Description
|
|
321
|
+
readonly literal: string
|
|
322
|
+
}>
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
type Description =
|
|
326
|
+
| Declaration
|
|
327
|
+
| NeverKeyword
|
|
328
|
+
| Keyword
|
|
329
|
+
| Literal
|
|
330
|
+
| UniqueSymbol
|
|
331
|
+
| Enums
|
|
332
|
+
| TemplateLiteral
|
|
333
|
+
| StringKeyword
|
|
334
|
+
| NumberKeyword
|
|
335
|
+
| BigIntKeyword
|
|
336
|
+
| DateFromSelf
|
|
337
|
+
| TupleType
|
|
338
|
+
| TypeLiteral
|
|
339
|
+
| Union
|
|
340
|
+
| Suspend
|
|
341
|
+
| Ref
|
|
342
|
+
|
|
343
|
+
const getArbitraryAnnotation = SchemaAST.getAnnotation<ArbitraryAnnotation<any, any>>(SchemaAST.ArbitraryAnnotationId)
|
|
321
344
|
|
|
322
|
-
const getASTConstraints = (ast:
|
|
323
|
-
const TypeAnnotationId = ast.annotations[
|
|
345
|
+
const getASTConstraints = (ast: SchemaAST.AST) => {
|
|
346
|
+
const TypeAnnotationId = ast.annotations[SchemaAST.SchemaIdAnnotationId]
|
|
324
347
|
if (Predicate.isPropertyKey(TypeAnnotationId)) {
|
|
325
348
|
const out = ast.annotations[TypeAnnotationId]
|
|
326
349
|
if (Predicate.isReadonlyRecord(out)) {
|
|
@@ -329,323 +352,277 @@ const getASTConstraints = (ast: AST.AST) => {
|
|
|
329
352
|
}
|
|
330
353
|
}
|
|
331
354
|
|
|
355
|
+
const idMemoMap = globalValue(
|
|
356
|
+
Symbol.for("effect/Arbitrary/IdMemoMap"),
|
|
357
|
+
() => new Map<SchemaAST.AST, string>()
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
let counter = 0
|
|
361
|
+
|
|
362
|
+
function wrapGetDescription(
|
|
363
|
+
f: (ast: SchemaAST.AST, description: Description) => Description,
|
|
364
|
+
g: (ast: SchemaAST.AST, path: ReadonlyArray<PropertyKey>) => Description
|
|
365
|
+
): (ast: SchemaAST.AST, path: ReadonlyArray<PropertyKey>) => Description {
|
|
366
|
+
return (ast, path) => f(ast, g(ast, path))
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function parseMeta(ast: SchemaAST.AST): [SchemaAST.SchemaIdAnnotation | undefined, Record<string | symbol, unknown>] {
|
|
370
|
+
const jsonSchema = SchemaAST.getJSONSchemaAnnotation(ast).pipe(
|
|
371
|
+
Option.filter(Predicate.isReadonlyRecord),
|
|
372
|
+
Option.getOrUndefined
|
|
373
|
+
)
|
|
374
|
+
const schemaId = Option.getOrElse(SchemaAST.getSchemaIdAnnotation(ast), () => undefined)
|
|
375
|
+
const schemaParams = Option.fromNullable(schemaId).pipe(
|
|
376
|
+
Option.map((id) => ast.annotations[id]),
|
|
377
|
+
Option.filter(Predicate.isReadonlyRecord),
|
|
378
|
+
Option.getOrUndefined
|
|
379
|
+
)
|
|
380
|
+
return [schemaId, { ...schemaParams, ...jsonSchema }]
|
|
381
|
+
}
|
|
382
|
+
|
|
332
383
|
/** @internal */
|
|
333
|
-
export const
|
|
334
|
-
ast
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const TypeAnnotationId = ast.annotations[AST.SchemaIdAnnotationId]
|
|
341
|
-
if (TypeAnnotationId === schemaId_.DateFromSelfSchemaId) {
|
|
342
|
-
const c = getASTConstraints(ast)
|
|
343
|
-
if (c !== undefined) {
|
|
344
|
-
return new Deferred(makeDateConstraints(c))
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return new Succeed(go(ast, ctx, path))
|
|
348
|
-
}
|
|
349
|
-
case "Literal":
|
|
350
|
-
return new Succeed((fc) => fc.constant(ast.literal))
|
|
351
|
-
case "UniqueSymbol":
|
|
352
|
-
return new Succeed((fc) => fc.constant(ast.symbol))
|
|
353
|
-
case "UndefinedKeyword":
|
|
354
|
-
return new Succeed((fc) => fc.constant(undefined))
|
|
355
|
-
case "NeverKeyword":
|
|
356
|
-
throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
|
|
357
|
-
case "VoidKeyword":
|
|
358
|
-
case "UnknownKeyword":
|
|
359
|
-
case "AnyKeyword":
|
|
360
|
-
return new Succeed((fc) => fc.anything())
|
|
361
|
-
case "StringKeyword":
|
|
362
|
-
return new Deferred(constStringConstraints)
|
|
363
|
-
case "NumberKeyword":
|
|
364
|
-
return new Deferred(constNumberConstraints)
|
|
365
|
-
case "BooleanKeyword":
|
|
366
|
-
return new Succeed((fc) => fc.boolean())
|
|
367
|
-
case "BigIntKeyword":
|
|
368
|
-
return new Deferred(constBigIntConstraints)
|
|
369
|
-
case "SymbolKeyword":
|
|
370
|
-
return new Succeed((fc) => fc.string().map((s) => Symbol.for(s)))
|
|
371
|
-
case "ObjectKeyword":
|
|
372
|
-
return new Succeed((fc) => fc.oneof(fc.object(), fc.array(fc.anything())))
|
|
373
|
-
case "Enums": {
|
|
374
|
-
if (ast.enums.length === 0) {
|
|
375
|
-
throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(path))
|
|
384
|
+
export const getDescription = wrapGetDescription(
|
|
385
|
+
(ast, description) => {
|
|
386
|
+
const annotation = getArbitraryAnnotation(ast)
|
|
387
|
+
if (Option.isSome(annotation)) {
|
|
388
|
+
return {
|
|
389
|
+
...description,
|
|
390
|
+
annotations: [...description.annotations, annotation.value]
|
|
376
391
|
}
|
|
377
|
-
return new Succeed((fc) => fc.oneof(...ast.enums.map(([_, value]) => fc.constant(value))))
|
|
378
392
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
393
|
+
return description
|
|
394
|
+
},
|
|
395
|
+
(ast, path) => {
|
|
396
|
+
const [schemaId, meta] = parseMeta(ast)
|
|
397
|
+
switch (ast._tag) {
|
|
398
|
+
case "Refinement": {
|
|
399
|
+
const from = getDescription(ast.from, path)
|
|
400
|
+
switch (from._tag) {
|
|
401
|
+
case "StringKeyword":
|
|
402
|
+
return {
|
|
403
|
+
...from,
|
|
404
|
+
constraints: [...from.constraints, makeStringConstraints(meta)],
|
|
405
|
+
refinements: [...from.refinements, ast]
|
|
406
|
+
}
|
|
407
|
+
case "NumberKeyword": {
|
|
408
|
+
const c = schemaId === schemaId_.NonNaNSchemaId ?
|
|
409
|
+
makeNumberConstraints({ noNaN: true }) :
|
|
410
|
+
makeNumberConstraints({
|
|
411
|
+
isInteger: "type" in meta && meta.type === "integer",
|
|
412
|
+
noNaN: "type" in meta && meta.type === "number" ? true : undefined,
|
|
413
|
+
noDefaultInfinity: "type" in meta && meta.type === "number" ? true : undefined,
|
|
414
|
+
min: meta.exclusiveMinimum ?? meta.minimum,
|
|
415
|
+
minExcluded: "exclusiveMinimum" in meta ? true : undefined,
|
|
416
|
+
max: meta.exclusiveMaximum ?? meta.maximum,
|
|
417
|
+
maxExcluded: "exclusiveMaximum" in meta ? true : undefined
|
|
418
|
+
})
|
|
419
|
+
return {
|
|
420
|
+
...from,
|
|
421
|
+
constraints: [...from.constraints, c],
|
|
422
|
+
refinements: [...from.refinements, ast]
|
|
401
423
|
}
|
|
402
424
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
425
|
+
case "BigIntKeyword": {
|
|
426
|
+
const c = getASTConstraints(ast)
|
|
427
|
+
return {
|
|
428
|
+
...from,
|
|
429
|
+
constraints: c !== undefined ? [...from.constraints, makeBigIntConstraints(c)] : from.constraints,
|
|
430
|
+
refinements: [...from.refinements, ast]
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
case "TupleType":
|
|
434
|
+
return {
|
|
435
|
+
...from,
|
|
436
|
+
constraints: [
|
|
437
|
+
...from.constraints,
|
|
438
|
+
makeArrayConstraints({
|
|
439
|
+
minLength: meta.minItems,
|
|
440
|
+
maxLength: meta.maxItems
|
|
441
|
+
})
|
|
442
|
+
],
|
|
443
|
+
refinements: [...from.refinements, ast]
|
|
444
|
+
}
|
|
445
|
+
case "DateFromSelf":
|
|
446
|
+
return {
|
|
447
|
+
...from,
|
|
448
|
+
constraints: [...from.constraints, makeDateConstraints(meta)],
|
|
449
|
+
refinements: [...from.refinements, ast]
|
|
450
|
+
}
|
|
451
|
+
default:
|
|
452
|
+
return {
|
|
453
|
+
...from,
|
|
454
|
+
refinements: [...from.refinements, ast]
|
|
408
455
|
}
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
return fc.tuple(...components).map((spans) => spans.join(""))
|
|
412
456
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
case "Succeed": {
|
|
424
|
-
return new Succeed(from.lazyArbitrary, filters)
|
|
457
|
+
}
|
|
458
|
+
case "Declaration": {
|
|
459
|
+
if (schemaId === schemaId_.DateFromSelfSchemaId) {
|
|
460
|
+
return {
|
|
461
|
+
_tag: "DateFromSelf",
|
|
462
|
+
constraints: [makeDateConstraints(meta)],
|
|
463
|
+
path,
|
|
464
|
+
refinements: [],
|
|
465
|
+
annotations: []
|
|
466
|
+
}
|
|
425
467
|
}
|
|
426
|
-
|
|
427
|
-
|
|
468
|
+
return {
|
|
469
|
+
_tag: "Declaration",
|
|
470
|
+
typeParameters: ast.typeParameters.map((ast) => getDescription(ast, path)),
|
|
471
|
+
path,
|
|
472
|
+
refinements: [],
|
|
473
|
+
annotations: [],
|
|
474
|
+
ast
|
|
428
475
|
}
|
|
429
476
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
)
|
|
438
|
-
return new Succeed((fc) => {
|
|
439
|
-
const arbs: any = {}
|
|
440
|
-
const requiredKeys: Array<PropertyKey> = []
|
|
441
|
-
// ---------------------------------------------
|
|
442
|
-
// handle property signatures
|
|
443
|
-
// ---------------------------------------------
|
|
444
|
-
for (let i = 0; i < propertySignaturesTypes.length; i++) {
|
|
445
|
-
const ps = ast.propertySignatures[i]
|
|
446
|
-
const name = ps.name
|
|
447
|
-
if (!ps.isOptional) {
|
|
448
|
-
requiredKeys.push(name)
|
|
449
|
-
}
|
|
450
|
-
arbs[name] = propertySignaturesTypes[i](fc)
|
|
477
|
+
case "Literal": {
|
|
478
|
+
return {
|
|
479
|
+
_tag: "Literal",
|
|
480
|
+
literal: ast.literal,
|
|
481
|
+
path,
|
|
482
|
+
refinements: [],
|
|
483
|
+
annotations: []
|
|
451
484
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const item = fc.tuple(key, value)
|
|
461
|
-
/*
|
|
462
|
-
|
|
463
|
-
`getSuspendedArray` is used to generate less key/value pairs in
|
|
464
|
-
the context of a recursive schema. Without it, the following schema
|
|
465
|
-
would generate an big amount of values possibly leading to a stack
|
|
466
|
-
overflow:
|
|
467
|
-
|
|
468
|
-
```ts
|
|
469
|
-
type A = { [_: string]: A }
|
|
470
|
-
|
|
471
|
-
const schema = S.Record({ key: S.String, value: S.suspend((): S.Schema<A> => schema) })
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
*/
|
|
475
|
-
const arr = ctx.depthIdentifier !== undefined ?
|
|
476
|
-
getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, defaultSuspendedArrayConstraints) :
|
|
477
|
-
fc.array(item)
|
|
478
|
-
return arr.map((tuples) => ({ ...Object.fromEntries(tuples), ...o }))
|
|
479
|
-
})
|
|
485
|
+
}
|
|
486
|
+
case "UniqueSymbol": {
|
|
487
|
+
return {
|
|
488
|
+
_tag: "UniqueSymbol",
|
|
489
|
+
symbol: ast.symbol,
|
|
490
|
+
path,
|
|
491
|
+
refinements: [],
|
|
492
|
+
annotations: []
|
|
480
493
|
}
|
|
481
|
-
|
|
482
|
-
return output
|
|
483
|
-
})
|
|
484
|
-
}
|
|
485
|
-
case "Union": {
|
|
486
|
-
const types = ast.types.map((member) => go(member, ctx, path))
|
|
487
|
-
return new Succeed((fc) => fc.oneof(...types.map((arb) => arb(fc))))
|
|
488
|
-
}
|
|
489
|
-
case "Suspend": {
|
|
490
|
-
const memo = arbitraryMemoMap.get(ast)
|
|
491
|
-
if (memo) {
|
|
492
|
-
return new Succeed(memo)
|
|
493
494
|
}
|
|
494
|
-
|
|
495
|
-
return
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
495
|
+
case "Enums": {
|
|
496
|
+
return {
|
|
497
|
+
_tag: "Enums",
|
|
498
|
+
enums: ast.enums,
|
|
499
|
+
path,
|
|
500
|
+
refinements: [],
|
|
501
|
+
annotations: [],
|
|
502
|
+
ast
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
case "TemplateLiteral": {
|
|
506
|
+
return {
|
|
507
|
+
_tag: "TemplateLiteral",
|
|
508
|
+
head: ast.head,
|
|
509
|
+
spans: ast.spans.map((span) => ({
|
|
510
|
+
description: getDescription(span.type, path),
|
|
511
|
+
literal: span.literal
|
|
512
|
+
})),
|
|
513
|
+
path,
|
|
514
|
+
refinements: [],
|
|
515
|
+
annotations: []
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
case "StringKeyword":
|
|
519
|
+
return {
|
|
520
|
+
_tag: "StringKeyword",
|
|
521
|
+
constraints: [],
|
|
522
|
+
path,
|
|
523
|
+
refinements: [],
|
|
524
|
+
annotations: []
|
|
525
|
+
}
|
|
526
|
+
case "NumberKeyword":
|
|
527
|
+
return {
|
|
528
|
+
_tag: "NumberKeyword",
|
|
529
|
+
constraints: [],
|
|
530
|
+
path,
|
|
531
|
+
refinements: [],
|
|
532
|
+
annotations: []
|
|
533
|
+
}
|
|
534
|
+
case "BigIntKeyword":
|
|
535
|
+
return {
|
|
536
|
+
_tag: "BigIntKeyword",
|
|
537
|
+
constraints: [],
|
|
538
|
+
path,
|
|
539
|
+
refinements: [],
|
|
540
|
+
annotations: []
|
|
541
|
+
}
|
|
542
|
+
case "TupleType":
|
|
543
|
+
return {
|
|
544
|
+
_tag: "TupleType",
|
|
545
|
+
constraints: [],
|
|
546
|
+
elements: ast.elements.map((element, i) => ({
|
|
547
|
+
isOptional: element.isOptional,
|
|
548
|
+
description: getDescription(element.type, [...path, i])
|
|
549
|
+
})),
|
|
550
|
+
rest: ast.rest.map((element, i) => getDescription(element.type, [...path, i])),
|
|
551
|
+
path,
|
|
552
|
+
refinements: [],
|
|
553
|
+
annotations: []
|
|
554
|
+
}
|
|
555
|
+
case "TypeLiteral":
|
|
556
|
+
return {
|
|
557
|
+
_tag: "TypeLiteral",
|
|
558
|
+
propertySignatures: ast.propertySignatures.map((ps) => ({
|
|
559
|
+
isOptional: ps.isOptional,
|
|
560
|
+
name: ps.name,
|
|
561
|
+
value: getDescription(ps.type, [...path, ps.name])
|
|
562
|
+
})),
|
|
563
|
+
indexSignatures: ast.indexSignatures.map((is) => ({
|
|
564
|
+
parameter: getDescription(is.parameter, path),
|
|
565
|
+
value: getDescription(is.type, path)
|
|
566
|
+
})),
|
|
567
|
+
path,
|
|
568
|
+
refinements: [],
|
|
569
|
+
annotations: []
|
|
570
|
+
}
|
|
571
|
+
case "Union":
|
|
572
|
+
return {
|
|
573
|
+
_tag: "Union",
|
|
574
|
+
members: ast.types.map((member, i) => getDescription(member, [...path, i])),
|
|
575
|
+
path,
|
|
576
|
+
refinements: [],
|
|
577
|
+
annotations: []
|
|
578
|
+
}
|
|
579
|
+
case "Suspend": {
|
|
580
|
+
const memoId = idMemoMap.get(ast)
|
|
581
|
+
if (memoId !== undefined) {
|
|
582
|
+
return {
|
|
583
|
+
_tag: "Ref",
|
|
584
|
+
id: memoId,
|
|
585
|
+
ast,
|
|
586
|
+
path,
|
|
587
|
+
refinements: [],
|
|
588
|
+
annotations: []
|
|
554
589
|
}
|
|
555
|
-
return tuple
|
|
556
|
-
})
|
|
557
|
-
)
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// ---------------------------------------------
|
|
561
|
-
// handle rest element
|
|
562
|
-
// ---------------------------------------------
|
|
563
|
-
if (Arr.isNonEmptyReadonlyArray(rest)) {
|
|
564
|
-
const [head, ...tail] = rest
|
|
565
|
-
const item = head(fc)
|
|
566
|
-
output = output.chain((as) => {
|
|
567
|
-
const len = as.length
|
|
568
|
-
// We must adjust the constraints for the rest element
|
|
569
|
-
// because the elements might have generated some values
|
|
570
|
-
const restArrayConstraints = subtractElementsLength(constraints, len)
|
|
571
|
-
if (restArrayConstraints.maxLength === 0) {
|
|
572
|
-
return fc.constant(as)
|
|
573
590
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
S.NullOr(S.suspend((): S.Schema<A> => schema))
|
|
586
|
-
)
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
*/
|
|
590
|
-
const arr = ctx.depthIdentifier !== undefined
|
|
591
|
-
? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, restArrayConstraints)
|
|
592
|
-
: fc.array(item, restArrayConstraints)
|
|
593
|
-
if (len === 0) {
|
|
594
|
-
return arr
|
|
591
|
+
counter++
|
|
592
|
+
const id = `__id-${counter}__`
|
|
593
|
+
idMemoMap.set(ast, id)
|
|
594
|
+
return {
|
|
595
|
+
_tag: "Suspend",
|
|
596
|
+
id,
|
|
597
|
+
ast,
|
|
598
|
+
description: () => getDescription(ast.f(), path),
|
|
599
|
+
path,
|
|
600
|
+
refinements: [],
|
|
601
|
+
annotations: []
|
|
595
602
|
}
|
|
596
|
-
return arr.map((rest) => [...as, ...rest])
|
|
597
|
-
})
|
|
598
|
-
// ---------------------------------------------
|
|
599
|
-
// handle post rest elements
|
|
600
|
-
// ---------------------------------------------
|
|
601
|
-
for (let j = 0; j < tail.length; j++) {
|
|
602
|
-
output = output.chain((as) => tail[j](fc).map((a) => [...as, a]))
|
|
603
603
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
} else {
|
|
623
|
-
return makeNumberConstraints({
|
|
624
|
-
isInteger: "type" in jsonSchema && jsonSchema.type === "integer",
|
|
625
|
-
noNaN: "type" in jsonSchema && jsonSchema.type === "number" ? true : undefined,
|
|
626
|
-
noDefaultInfinity: "type" in jsonSchema && jsonSchema.type === "number" ? true : undefined,
|
|
627
|
-
min: jsonSchema.exclusiveMinimum ?? jsonSchema.minimum,
|
|
628
|
-
minExcluded: "exclusiveMinimum" in jsonSchema ? true : undefined,
|
|
629
|
-
max: jsonSchema.exclusiveMaximum ?? jsonSchema.maximum,
|
|
630
|
-
maxExcluded: "exclusiveMaximum" in jsonSchema ? true : undefined
|
|
631
|
-
})
|
|
604
|
+
case "Transformation":
|
|
605
|
+
return getDescription(ast.to, path)
|
|
606
|
+
case "NeverKeyword":
|
|
607
|
+
return {
|
|
608
|
+
_tag: "NeverKeyword",
|
|
609
|
+
path,
|
|
610
|
+
refinements: [],
|
|
611
|
+
annotations: [],
|
|
612
|
+
ast
|
|
613
|
+
}
|
|
614
|
+
default: {
|
|
615
|
+
return {
|
|
616
|
+
_tag: "Keyword",
|
|
617
|
+
value: ast._tag,
|
|
618
|
+
path,
|
|
619
|
+
refinements: [],
|
|
620
|
+
annotations: []
|
|
621
|
+
}
|
|
632
622
|
}
|
|
633
623
|
}
|
|
634
|
-
case "BigIntConstraints": {
|
|
635
|
-
const c = getASTConstraints(ast)
|
|
636
|
-
return c !== undefined ? makeBigIntConstraints(c) : undefined
|
|
637
|
-
}
|
|
638
|
-
case "DateConstraints": {
|
|
639
|
-
const c = getASTConstraints(ast)
|
|
640
|
-
return c !== undefined ? makeDateConstraints(c) : undefined
|
|
641
|
-
}
|
|
642
|
-
case "ArrayConstraints":
|
|
643
|
-
return makeArrayConstraints({
|
|
644
|
-
minLength: jsonSchema.minItems,
|
|
645
|
-
maxLength: jsonSchema.maxItems
|
|
646
|
-
})
|
|
647
624
|
}
|
|
648
|
-
|
|
625
|
+
)
|
|
649
626
|
|
|
650
627
|
function getMax(n1: Date | undefined, n2: Date | undefined): Date | undefined
|
|
651
628
|
function getMax(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
|
|
@@ -681,64 +658,423 @@ function mergePattern(pattern1: string | undefined, pattern2: string | undefined
|
|
|
681
658
|
return `(?:${pattern1})|(?:${pattern2})`
|
|
682
659
|
}
|
|
683
660
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
661
|
+
function mergeStringConstraints(c1: StringConstraints, c2: StringConstraints): StringConstraints {
|
|
662
|
+
return makeStringConstraints({
|
|
663
|
+
minLength: getMax(c1.constraints.minLength, c2.constraints.minLength),
|
|
664
|
+
maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength),
|
|
665
|
+
pattern: mergePattern(c1.pattern, c2.pattern)
|
|
666
|
+
})
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function buildStringConstraints(description: StringKeyword): StringConstraints | undefined {
|
|
670
|
+
return description.constraints.length === 0
|
|
671
|
+
? undefined
|
|
672
|
+
: description.constraints.reduce(mergeStringConstraints)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function mergeNumberConstraints(c1: NumberConstraints, c2: NumberConstraints): NumberConstraints {
|
|
676
|
+
return makeNumberConstraints({
|
|
677
|
+
isInteger: c1.isInteger || c2.isInteger,
|
|
678
|
+
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
679
|
+
minExcluded: getOr(c1.constraints.minExcluded, c2.constraints.minExcluded),
|
|
680
|
+
max: getMin(c1.constraints.max, c2.constraints.max),
|
|
681
|
+
maxExcluded: getOr(c1.constraints.maxExcluded, c2.constraints.maxExcluded),
|
|
682
|
+
noNaN: getOr(c1.constraints.noNaN, c2.constraints.noNaN),
|
|
683
|
+
noDefaultInfinity: getOr(c1.constraints.noDefaultInfinity, c2.constraints.noDefaultInfinity)
|
|
684
|
+
})
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function buildNumberConstraints(description: NumberKeyword): NumberConstraints | undefined {
|
|
688
|
+
return description.constraints.length === 0
|
|
689
|
+
? undefined
|
|
690
|
+
: description.constraints.reduce(mergeNumberConstraints)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function mergeBigIntConstraints(c1: BigIntConstraints, c2: BigIntConstraints): BigIntConstraints {
|
|
694
|
+
return makeBigIntConstraints({
|
|
695
|
+
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
696
|
+
max: getMin(c1.constraints.max, c2.constraints.max)
|
|
697
|
+
})
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function buildBigIntConstraints(description: BigIntKeyword): BigIntConstraints | undefined {
|
|
701
|
+
return description.constraints.length === 0
|
|
702
|
+
? undefined
|
|
703
|
+
: description.constraints.reduce(mergeBigIntConstraints)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function mergeDateConstraints(c1: DateConstraints, c2: DateConstraints): DateConstraints {
|
|
707
|
+
return makeDateConstraints({
|
|
708
|
+
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
709
|
+
max: getMin(c1.constraints.max, c2.constraints.max),
|
|
710
|
+
noInvalidDate: getOr(c1.constraints.noInvalidDate, c2.constraints.noInvalidDate)
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function buildDateConstraints(description: DateFromSelf): DateConstraints | undefined {
|
|
715
|
+
return description.constraints.length === 0
|
|
716
|
+
? undefined
|
|
717
|
+
: description.constraints.reduce(mergeDateConstraints)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const constArrayConstraints = makeArrayConstraints({})
|
|
721
|
+
|
|
722
|
+
function mergeArrayConstraints(c1: ArrayConstraints, c2: ArrayConstraints): ArrayConstraints {
|
|
723
|
+
return makeArrayConstraints({
|
|
724
|
+
minLength: getMax(c1.constraints.minLength, c2.constraints.minLength),
|
|
725
|
+
maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength)
|
|
726
|
+
})
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function buildArrayConstraints(description: TupleType): ArrayConstraints | undefined {
|
|
730
|
+
return description.constraints.length === 0
|
|
731
|
+
? undefined
|
|
732
|
+
: description.constraints.reduce(mergeArrayConstraints)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const arbitraryMemoMap = globalValue(
|
|
736
|
+
Symbol.for("effect/Arbitrary/arbitraryMemoMap"),
|
|
737
|
+
() => new WeakMap<SchemaAST.AST, LazyArbitrary<any>>()
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
function applyFilters(filters: ReadonlyArray<Predicate.Predicate<any>>, arb: LazyArbitrary<any>): LazyArbitrary<any> {
|
|
741
|
+
return (fc) => filters.reduce((arb, filter) => arb.filter(filter), arb(fc))
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function absurd(message: string): LazyArbitrary<any> {
|
|
745
|
+
return () => {
|
|
746
|
+
throw new Error(message)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function getContextConstraints(description: Description): ArbitraryGenerationContext["constraints"] {
|
|
751
|
+
switch (description._tag) {
|
|
752
|
+
case "StringKeyword":
|
|
753
|
+
return buildStringConstraints(description)
|
|
754
|
+
case "NumberKeyword":
|
|
755
|
+
return buildNumberConstraints(description)
|
|
756
|
+
case "BigIntKeyword":
|
|
757
|
+
return buildBigIntConstraints(description)
|
|
758
|
+
case "DateFromSelf":
|
|
759
|
+
return buildDateConstraints(description)
|
|
760
|
+
case "TupleType":
|
|
761
|
+
return buildArrayConstraints(description)
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function wrapGo(
|
|
766
|
+
f: (description: Description, ctx: ArbitraryGenerationContext, lazyArb: LazyArbitrary<any>) => LazyArbitrary<any>,
|
|
767
|
+
g: (description: Description, ctx: ArbitraryGenerationContext) => LazyArbitrary<any>
|
|
768
|
+
): (description: Description, ctx: ArbitraryGenerationContext) => LazyArbitrary<any> {
|
|
769
|
+
return (description, ctx) => f(description, ctx, g(description, ctx))
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const go = wrapGo(
|
|
773
|
+
(description, ctx, lazyArb) => {
|
|
774
|
+
const annotation: ArbitraryAnnotation<any, any> | undefined =
|
|
775
|
+
description.annotations[description.annotations.length - 1]
|
|
776
|
+
|
|
777
|
+
// error handling
|
|
778
|
+
if (annotation === undefined) {
|
|
779
|
+
switch (description._tag) {
|
|
780
|
+
case "Declaration":
|
|
781
|
+
case "NeverKeyword":
|
|
782
|
+
throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(description.path, description.ast))
|
|
783
|
+
case "Enums":
|
|
784
|
+
if (description.enums.length === 0) {
|
|
785
|
+
throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(description.path))
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const filters = description.refinements.map((ast) => (a: any) =>
|
|
791
|
+
Option.isNone(ast.filter(a, SchemaAST.defaultParseOption, ast))
|
|
792
|
+
)
|
|
793
|
+
if (annotation === undefined) {
|
|
794
|
+
return applyFilters(filters, lazyArb)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const constraints = getContextConstraints(description)
|
|
798
|
+
if (constraints !== undefined) {
|
|
799
|
+
ctx = { ...ctx, constraints }
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (description._tag === "Declaration") {
|
|
803
|
+
return applyFilters(filters, annotation(...description.typeParameters.map((p) => go(p, ctx)), ctx))
|
|
804
|
+
}
|
|
805
|
+
if (description.refinements.length > 0) {
|
|
806
|
+
// TODO(4.0): remove the `lazyArb` parameter
|
|
807
|
+
return applyFilters(filters, annotation(lazyArb, ctx))
|
|
808
|
+
}
|
|
809
|
+
return annotation(ctx)
|
|
810
|
+
},
|
|
811
|
+
(description, ctx) => {
|
|
812
|
+
switch (description._tag) {
|
|
813
|
+
case "DateFromSelf": {
|
|
814
|
+
const constraints = buildDateConstraints(description)
|
|
815
|
+
return (fc) => fc.date(constraints?.constraints)
|
|
816
|
+
}
|
|
817
|
+
case "Declaration":
|
|
818
|
+
case "NeverKeyword":
|
|
819
|
+
return absurd(`BUG: cannot generate an arbitrary for ${description._tag}`)
|
|
820
|
+
case "Literal":
|
|
821
|
+
return (fc) => fc.constant(description.literal)
|
|
822
|
+
case "UniqueSymbol":
|
|
823
|
+
return (fc) => fc.constant(description.symbol)
|
|
824
|
+
case "Keyword": {
|
|
825
|
+
switch (description.value) {
|
|
826
|
+
case "UndefinedKeyword":
|
|
827
|
+
return (fc) => fc.constant(undefined)
|
|
828
|
+
case "VoidKeyword":
|
|
829
|
+
case "UnknownKeyword":
|
|
830
|
+
case "AnyKeyword":
|
|
831
|
+
return (fc) => fc.anything()
|
|
832
|
+
case "BooleanKeyword":
|
|
833
|
+
return (fc) => fc.boolean()
|
|
834
|
+
case "SymbolKeyword":
|
|
835
|
+
return (fc) => fc.string().map((s) => Symbol.for(s))
|
|
836
|
+
case "ObjectKeyword":
|
|
837
|
+
return (fc) => fc.oneof(fc.object(), fc.array(fc.anything()))
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
case "Enums":
|
|
841
|
+
return (fc) => fc.oneof(...description.enums.map(([_, value]) => fc.constant(value)))
|
|
842
|
+
case "TemplateLiteral": {
|
|
843
|
+
return (fc) => {
|
|
844
|
+
const string = fc.string({ maxLength: 5 })
|
|
845
|
+
const number = fc.float({ noDefaultInfinity: true, noNaN: true })
|
|
846
|
+
|
|
847
|
+
const getTemplateLiteralArb = (description: TemplateLiteral) => {
|
|
848
|
+
const components: Array<FastCheck.Arbitrary<string | number>> = description.head !== ""
|
|
849
|
+
? [fc.constant(description.head)]
|
|
850
|
+
: []
|
|
851
|
+
|
|
852
|
+
const getTemplateLiteralSpanTypeArb = (
|
|
853
|
+
description: Description
|
|
854
|
+
): FastCheck.Arbitrary<string | number> => {
|
|
855
|
+
switch (description._tag) {
|
|
856
|
+
case "StringKeyword":
|
|
857
|
+
return string
|
|
858
|
+
case "NumberKeyword":
|
|
859
|
+
return number
|
|
860
|
+
case "Literal":
|
|
861
|
+
return fc.constant(String(description.literal))
|
|
862
|
+
case "Union":
|
|
863
|
+
return fc.oneof(...description.members.map(getTemplateLiteralSpanTypeArb))
|
|
864
|
+
case "TemplateLiteral":
|
|
865
|
+
return getTemplateLiteralArb(description)
|
|
866
|
+
default:
|
|
867
|
+
return fc.constant("")
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
description.spans.forEach((span) => {
|
|
872
|
+
components.push(getTemplateLiteralSpanTypeArb(span.description))
|
|
873
|
+
if (span.literal !== "") {
|
|
874
|
+
components.push(fc.constant(span.literal))
|
|
875
|
+
}
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
return fc.tuple(...components).map((spans) => spans.join(""))
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return getTemplateLiteralArb(description)
|
|
694
882
|
}
|
|
695
|
-
break
|
|
696
883
|
}
|
|
697
|
-
case "
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
884
|
+
case "StringKeyword": {
|
|
885
|
+
const constraints = buildStringConstraints(description)
|
|
886
|
+
const pattern = constraints?.pattern
|
|
887
|
+
return pattern !== undefined ?
|
|
888
|
+
(fc) => fc.stringMatching(new RegExp(pattern)) :
|
|
889
|
+
(fc) => fc.string(constraints?.constraints)
|
|
890
|
+
}
|
|
891
|
+
case "NumberKeyword": {
|
|
892
|
+
const constraints = buildNumberConstraints(description)
|
|
893
|
+
return constraints?.isInteger ?
|
|
894
|
+
(fc) => fc.integer(constraints.constraints) :
|
|
895
|
+
(fc) => fc.float(constraints?.constraints)
|
|
896
|
+
}
|
|
897
|
+
case "BigIntKeyword": {
|
|
898
|
+
const constraints = buildBigIntConstraints(description)
|
|
899
|
+
return (fc) => fc.bigInt(constraints?.constraints ?? {})
|
|
900
|
+
}
|
|
901
|
+
case "TupleType": {
|
|
902
|
+
const elements: Array<LazyArbitrary<any>> = []
|
|
903
|
+
let hasOptionals = false
|
|
904
|
+
for (const element of description.elements) {
|
|
905
|
+
elements.push(go(element.description, ctx))
|
|
906
|
+
if (element.isOptional) {
|
|
907
|
+
hasOptionals = true
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const rest = description.rest.map((d) => go(d, ctx))
|
|
911
|
+
return (fc) => {
|
|
912
|
+
// ---------------------------------------------
|
|
913
|
+
// handle elements
|
|
914
|
+
// ---------------------------------------------
|
|
915
|
+
let output = fc.tuple(...elements.map((arb) => arb(fc)))
|
|
916
|
+
if (hasOptionals) {
|
|
917
|
+
const indexes = fc.tuple(
|
|
918
|
+
...description.elements.map((element) => element.isOptional ? fc.boolean() : fc.constant(true))
|
|
919
|
+
)
|
|
920
|
+
output = output.chain((tuple) =>
|
|
921
|
+
indexes.map((booleans) => {
|
|
922
|
+
for (const [i, b] of booleans.reverse().entries()) {
|
|
923
|
+
if (!b) {
|
|
924
|
+
tuple.splice(booleans.length - i, 1)
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return tuple
|
|
928
|
+
})
|
|
929
|
+
)
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// ---------------------------------------------
|
|
933
|
+
// handle rest element
|
|
934
|
+
// ---------------------------------------------
|
|
935
|
+
if (Arr.isNonEmptyReadonlyArray(rest)) {
|
|
936
|
+
const constraints = buildArrayConstraints(description) ?? constArrayConstraints
|
|
937
|
+
const [head, ...tail] = rest
|
|
938
|
+
const item = head(fc)
|
|
939
|
+
output = output.chain((as) => {
|
|
940
|
+
const len = as.length
|
|
941
|
+
// We must adjust the constraints for the rest element
|
|
942
|
+
// because the elements might have generated some values
|
|
943
|
+
const restArrayConstraints = subtractElementsLength(constraints.constraints, len)
|
|
944
|
+
if (restArrayConstraints.maxLength === 0) {
|
|
945
|
+
return fc.constant(as)
|
|
946
|
+
}
|
|
947
|
+
/*
|
|
948
|
+
|
|
949
|
+
`getSuspendedArray` is used to generate less values in
|
|
950
|
+
the context of a recursive schema. Without it, the following schema
|
|
951
|
+
would generate an big amount of values possibly leading to a stack
|
|
952
|
+
overflow:
|
|
953
|
+
|
|
954
|
+
```ts
|
|
955
|
+
type A = ReadonlyArray<A | null>
|
|
956
|
+
|
|
957
|
+
const schema = S.Array(
|
|
958
|
+
S.NullOr(S.suspend((): S.Schema<A> => schema))
|
|
959
|
+
)
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
*/
|
|
963
|
+
const arr = ctx.depthIdentifier !== undefined
|
|
964
|
+
? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, restArrayConstraints)
|
|
965
|
+
: fc.array(item, restArrayConstraints)
|
|
966
|
+
if (len === 0) {
|
|
967
|
+
return arr
|
|
968
|
+
}
|
|
969
|
+
return arr.map((rest) => [...as, ...rest])
|
|
970
|
+
})
|
|
971
|
+
// ---------------------------------------------
|
|
972
|
+
// handle post rest elements
|
|
973
|
+
// ---------------------------------------------
|
|
974
|
+
for (let j = 0; j < tail.length; j++) {
|
|
975
|
+
output = output.chain((as) => tail[j](fc).map((a) => [...as, a]))
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
return output
|
|
708
980
|
}
|
|
709
|
-
break
|
|
710
981
|
}
|
|
711
|
-
case "
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
982
|
+
case "TypeLiteral": {
|
|
983
|
+
const propertySignatures: Array<LazyArbitrary<any>> = []
|
|
984
|
+
const requiredKeys: Array<PropertyKey> = []
|
|
985
|
+
for (const ps of description.propertySignatures) {
|
|
986
|
+
if (!ps.isOptional) {
|
|
987
|
+
requiredKeys.push(ps.name)
|
|
988
|
+
}
|
|
989
|
+
propertySignatures.push(go(ps.value, ctx))
|
|
990
|
+
}
|
|
991
|
+
const indexSignatures = description.indexSignatures.map((is) =>
|
|
992
|
+
[go(is.parameter, ctx), go(is.value, ctx)] as const
|
|
993
|
+
)
|
|
994
|
+
return (fc) => {
|
|
995
|
+
const pps: any = {}
|
|
996
|
+
for (let i = 0; i < propertySignatures.length; i++) {
|
|
997
|
+
const ps = description.propertySignatures[i]
|
|
998
|
+
pps[ps.name] = propertySignatures[i](fc)
|
|
999
|
+
}
|
|
1000
|
+
let output = fc.record<any, any>(pps, { requiredKeys })
|
|
1001
|
+
// ---------------------------------------------
|
|
1002
|
+
// handle index signatures
|
|
1003
|
+
// ---------------------------------------------
|
|
1004
|
+
for (let i = 0; i < indexSignatures.length; i++) {
|
|
1005
|
+
const key = indexSignatures[i][0](fc)
|
|
1006
|
+
const value = indexSignatures[i][1](fc)
|
|
1007
|
+
output = output.chain((o) => {
|
|
1008
|
+
const item = fc.tuple(key, value)
|
|
1009
|
+
/*
|
|
1010
|
+
|
|
1011
|
+
`getSuspendedArray` is used to generate less key/value pairs in
|
|
1012
|
+
the context of a recursive schema. Without it, the following schema
|
|
1013
|
+
would generate an big amount of values possibly leading to a stack
|
|
1014
|
+
overflow:
|
|
1015
|
+
|
|
1016
|
+
```ts
|
|
1017
|
+
type A = { [_: string]: A }
|
|
1018
|
+
|
|
1019
|
+
const schema = S.Record({ key: S.String, value: S.suspend((): S.Schema<A> => schema) })
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
*/
|
|
1023
|
+
const arr = ctx.depthIdentifier !== undefined ?
|
|
1024
|
+
getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, { maxLength: 2 }) :
|
|
1025
|
+
fc.array(item)
|
|
1026
|
+
return arr.map((tuples) => ({ ...Object.fromEntries(tuples), ...o }))
|
|
1027
|
+
})
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return output
|
|
717
1031
|
}
|
|
718
|
-
break
|
|
719
1032
|
}
|
|
720
|
-
case "
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1033
|
+
case "Union": {
|
|
1034
|
+
const members = description.members.map((member) => go(member, ctx))
|
|
1035
|
+
return (fc) => fc.oneof(...members.map((arb) => arb(fc)))
|
|
1036
|
+
}
|
|
1037
|
+
case "Suspend": {
|
|
1038
|
+
const memo = arbitraryMemoMap.get(description.ast)
|
|
1039
|
+
if (memo) {
|
|
1040
|
+
return memo
|
|
1041
|
+
}
|
|
1042
|
+
if (ctx.depthIdentifier === undefined) {
|
|
1043
|
+
ctx = { ...ctx, depthIdentifier: description.id }
|
|
727
1044
|
}
|
|
728
|
-
|
|
1045
|
+
const get = util_.memoizeThunk(() => {
|
|
1046
|
+
return go(description.description(), ctx)
|
|
1047
|
+
})
|
|
1048
|
+
const out: LazyArbitrary<any> = (fc) => fc.constant(null).chain(() => get()(fc))
|
|
1049
|
+
arbitraryMemoMap.set(description.ast, out)
|
|
1050
|
+
return out
|
|
729
1051
|
}
|
|
730
|
-
case "
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength)
|
|
735
|
-
}, c1.ast)
|
|
1052
|
+
case "Ref": {
|
|
1053
|
+
const memo = arbitraryMemoMap.get(description.ast)
|
|
1054
|
+
if (memo) {
|
|
1055
|
+
return memo
|
|
736
1056
|
}
|
|
737
|
-
|
|
1057
|
+
throw new Error(`BUG: Ref ${JSON.stringify(description.id)} not found`)
|
|
738
1058
|
}
|
|
739
1059
|
}
|
|
740
1060
|
}
|
|
741
|
-
|
|
1061
|
+
)
|
|
1062
|
+
|
|
1063
|
+
function subtractElementsLength(
|
|
1064
|
+
constraints: FastCheck.ArrayConstraints,
|
|
1065
|
+
len: number
|
|
1066
|
+
): FastCheck.ArrayConstraints {
|
|
1067
|
+
if (len === 0 || (constraints.minLength === undefined && constraints.maxLength === undefined)) {
|
|
1068
|
+
return constraints
|
|
1069
|
+
}
|
|
1070
|
+
const out = { ...constraints }
|
|
1071
|
+
if (out.minLength !== undefined) {
|
|
1072
|
+
out.minLength = Math.max(out.minLength - len, 0)
|
|
1073
|
+
}
|
|
1074
|
+
if (out.maxLength !== undefined) {
|
|
1075
|
+
out.maxLength = Math.max(out.maxLength - len, 0)
|
|
1076
|
+
}
|
|
1077
|
+
return out
|
|
742
1078
|
}
|
|
743
1079
|
|
|
744
1080
|
const getSuspendedArray = (
|
|
@@ -761,17 +1097,3 @@ const getSuspendedArray = (
|
|
|
761
1097
|
fc.array(item, constraints)
|
|
762
1098
|
)
|
|
763
1099
|
}
|
|
764
|
-
|
|
765
|
-
const getSuspendedContext = (
|
|
766
|
-
ctx: ArbitraryGenerationContext,
|
|
767
|
-
ast: AST.Suspend
|
|
768
|
-
): ArbitraryGenerationContext => {
|
|
769
|
-
if (ctx.depthIdentifier !== undefined) {
|
|
770
|
-
return ctx
|
|
771
|
-
}
|
|
772
|
-
const depthIdentifier = AST.getIdentifierAnnotation(ast).pipe(
|
|
773
|
-
Option.orElse(() => AST.getIdentifierAnnotation(ast.f())),
|
|
774
|
-
Option.getOrElse(() => "SuspendDefaultDepthIdentifier")
|
|
775
|
-
)
|
|
776
|
-
return { ...ctx, depthIdentifier }
|
|
777
|
-
}
|