effect 3.11.5 → 3.11.7
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 +357 -343
- package/dist/cjs/Arbitrary.js.map +1 -1
- package/dist/cjs/JSONSchema.js +157 -38
- package/dist/cjs/JSONSchema.js.map +1 -1
- package/dist/cjs/Schema.js +49 -33
- package/dist/cjs/Schema.js.map +1 -1
- package/dist/cjs/internal/schema/filters.js +24 -18
- package/dist/cjs/internal/schema/filters.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Arbitrary.d.ts +21 -1
- package/dist/dts/Arbitrary.d.ts.map +1 -1
- package/dist/dts/JSONSchema.d.ts +11 -2
- package/dist/dts/JSONSchema.d.ts.map +1 -1
- package/dist/dts/Schema.d.ts +46 -18
- package/dist/dts/Schema.d.ts.map +1 -1
- package/dist/esm/Arbitrary.js +351 -335
- package/dist/esm/Arbitrary.js.map +1 -1
- package/dist/esm/JSONSchema.js +157 -38
- package/dist/esm/JSONSchema.js.map +1 -1
- package/dist/esm/Schema.js +49 -33
- package/dist/esm/Schema.js.map +1 -1
- package/dist/esm/internal/schema/filters.js +23 -17
- package/dist/esm/internal/schema/filters.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Arbitrary.ts +410 -361
- package/src/JSONSchema.ts +185 -40
- package/src/Schema.ts +161 -74
- package/src/internal/schema/filters.ts +32 -17
- package/src/internal/version.ts +1 -1
package/src/Arbitrary.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as Option from "./Option.js"
|
|
|
11
11
|
import * as Predicate from "./Predicate.js"
|
|
12
12
|
import type * as Schema from "./Schema.js"
|
|
13
13
|
import * as AST from "./SchemaAST.js"
|
|
14
|
+
import type * as Types from "./Types.js"
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @category model
|
|
@@ -25,8 +26,9 @@ export interface LazyArbitrary<A> {
|
|
|
25
26
|
* @since 3.10.0
|
|
26
27
|
*/
|
|
27
28
|
export interface ArbitraryGenerationContext {
|
|
28
|
-
readonly depthIdentifier?: string
|
|
29
29
|
readonly maxDepth: number
|
|
30
|
+
readonly depthIdentifier?: string
|
|
31
|
+
readonly constraints?: StringConstraints | NumberConstraints | BigIntConstraints | ArrayConstraints
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -59,58 +61,196 @@ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): FastCheck.Arbitra
|
|
|
59
61
|
|
|
60
62
|
const getArbitraryAnnotation = AST.getAnnotation<ArbitraryAnnotation<any, any>>(AST.ArbitraryAnnotationId)
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
ast: AST.Refinement,
|
|
64
|
-
ctx: Context,
|
|
65
|
-
path: ReadonlyArray<PropertyKey>
|
|
66
|
-
) => {
|
|
67
|
-
const constraints = combineConstraints(ctx.constraints, getConstraints(ast))
|
|
68
|
-
return go(ast.from, constraints ? { ...ctx, constraints } : ctx, path)
|
|
69
|
-
}
|
|
64
|
+
type Op = Succeed | Deferred
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Represents an arbitrary with optional filters.
|
|
68
|
+
*/
|
|
69
|
+
class Succeed {
|
|
70
|
+
readonly _tag = "Succeed"
|
|
71
|
+
constructor(
|
|
72
|
+
readonly lazyArbitrary: LazyArbitrary<any>,
|
|
73
|
+
readonly filters: Array<Predicate.Predicate<any>> = []
|
|
74
|
+
) {}
|
|
75
|
+
|
|
76
|
+
toLazyArbitrary(): LazyArbitrary<any> {
|
|
77
|
+
return (fc) => {
|
|
78
|
+
let out = this.lazyArbitrary(fc)
|
|
79
|
+
for (const f of this.filters) {
|
|
80
|
+
out = out.filter(f)
|
|
81
|
+
}
|
|
82
|
+
return out
|
|
83
|
+
}
|
|
77
84
|
}
|
|
78
|
-
const depthIdentifier = AST.getIdentifierAnnotation(ast).pipe(
|
|
79
|
-
Option.orElse(() => AST.getIdentifierAnnotation(ast.f())),
|
|
80
|
-
Option.getOrElse(() => "SuspendDefaultDepthIdentifier")
|
|
81
|
-
)
|
|
82
|
-
return { ...ctx, depthIdentifier }
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Represents a deferred arbitrary value generator with optional filters.
|
|
89
|
+
*/
|
|
90
|
+
class Deferred {
|
|
91
|
+
readonly _tag = "Deferred"
|
|
92
|
+
constructor(
|
|
93
|
+
readonly config: Config,
|
|
94
|
+
readonly filters: Array<Predicate.Predicate<any>> = []
|
|
95
|
+
) {}
|
|
96
|
+
|
|
97
|
+
toLazyArbitrary(ctx: ArbitraryGenerationContext, path: ReadonlyArray<PropertyKey>): LazyArbitrary<any> {
|
|
98
|
+
const config = this.config
|
|
99
|
+
switch (config._tag) {
|
|
100
|
+
case "StringConstraints": {
|
|
101
|
+
const pattern = config.pattern
|
|
102
|
+
return pattern !== undefined ?
|
|
103
|
+
(fc) => fc.stringMatching(new RegExp(pattern)) :
|
|
104
|
+
(fc) => fc.string(config.constraints)
|
|
105
|
+
}
|
|
106
|
+
case "NumberConstraints": {
|
|
107
|
+
return config.isInteger ?
|
|
108
|
+
(fc) => fc.integer(config.constraints) :
|
|
109
|
+
(fc) => fc.float(config.constraints)
|
|
110
|
+
}
|
|
111
|
+
case "BigIntConstraints":
|
|
112
|
+
return (fc) => fc.bigInt(config.constraints)
|
|
113
|
+
case "ArrayConstraints":
|
|
114
|
+
return goTupleType(config.ast, ctx, path, config.constraints)
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
|
-
return fc.oneof(
|
|
101
|
-
{ maxDepth, depthIdentifier },
|
|
102
|
-
fc.constant([]),
|
|
103
|
-
fc.array(item, { minLength, maxLength })
|
|
104
|
-
)
|
|
105
117
|
}
|
|
106
118
|
|
|
107
|
-
interface
|
|
108
|
-
readonly
|
|
119
|
+
interface StringConstraints {
|
|
120
|
+
readonly _tag: "StringConstraints"
|
|
121
|
+
readonly constraints: FastCheck.StringSharedConstraints
|
|
122
|
+
readonly pattern?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
126
|
+
export const makeStringConstraints = (options: {
|
|
127
|
+
readonly minLength?: number | undefined
|
|
128
|
+
readonly maxLength?: number | undefined
|
|
129
|
+
readonly pattern?: string | undefined
|
|
130
|
+
}): StringConstraints => {
|
|
131
|
+
const out: Types.Mutable<StringConstraints> = {
|
|
132
|
+
_tag: "StringConstraints",
|
|
133
|
+
constraints: {}
|
|
134
|
+
}
|
|
135
|
+
if (Predicate.isNumber(options.minLength)) {
|
|
136
|
+
out.constraints.minLength = options.minLength
|
|
137
|
+
}
|
|
138
|
+
if (Predicate.isNumber(options.maxLength)) {
|
|
139
|
+
out.constraints.maxLength = options.maxLength
|
|
140
|
+
}
|
|
141
|
+
if (Predicate.isString(options.pattern)) {
|
|
142
|
+
out.pattern = options.pattern
|
|
143
|
+
}
|
|
144
|
+
return out
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface NumberConstraints {
|
|
148
|
+
readonly _tag: "NumberConstraints"
|
|
149
|
+
readonly constraints: FastCheck.FloatConstraints
|
|
150
|
+
readonly isInteger: boolean
|
|
109
151
|
}
|
|
110
152
|
|
|
153
|
+
/** @internal */
|
|
154
|
+
export const makeNumberConstraints = (options: {
|
|
155
|
+
readonly isInteger?: boolean | undefined
|
|
156
|
+
readonly min?: number | undefined
|
|
157
|
+
readonly minExcluded?: boolean | undefined
|
|
158
|
+
readonly max?: number | undefined
|
|
159
|
+
readonly maxExcluded?: boolean | undefined
|
|
160
|
+
readonly noNaN?: boolean | undefined
|
|
161
|
+
readonly noDefaultInfinity?: boolean | undefined
|
|
162
|
+
}): NumberConstraints => {
|
|
163
|
+
const out: Types.Mutable<NumberConstraints> = {
|
|
164
|
+
_tag: "NumberConstraints",
|
|
165
|
+
constraints: {},
|
|
166
|
+
isInteger: options.isInteger ?? false
|
|
167
|
+
}
|
|
168
|
+
if (Predicate.isNumber(options.min)) {
|
|
169
|
+
out.constraints.min = Math.fround(options.min)
|
|
170
|
+
}
|
|
171
|
+
if (Predicate.isBoolean(options.minExcluded)) {
|
|
172
|
+
out.constraints.minExcluded = options.minExcluded
|
|
173
|
+
}
|
|
174
|
+
if (Predicate.isNumber(options.max)) {
|
|
175
|
+
out.constraints.max = Math.fround(options.max)
|
|
176
|
+
}
|
|
177
|
+
if (Predicate.isBoolean(options.maxExcluded)) {
|
|
178
|
+
out.constraints.maxExcluded = options.maxExcluded
|
|
179
|
+
}
|
|
180
|
+
if (Predicate.isBoolean(options.noNaN)) {
|
|
181
|
+
out.constraints.noNaN = options.noNaN
|
|
182
|
+
}
|
|
183
|
+
if (Predicate.isBoolean(options.noDefaultInfinity)) {
|
|
184
|
+
out.constraints.noDefaultInfinity = options.noDefaultInfinity
|
|
185
|
+
}
|
|
186
|
+
return out
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface BigIntConstraints {
|
|
190
|
+
readonly _tag: "BigIntConstraints"
|
|
191
|
+
readonly constraints: FastCheck.BigIntConstraints
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** @internal */
|
|
195
|
+
export const makeBigIntConstraints = (options: {
|
|
196
|
+
readonly min?: bigint | undefined
|
|
197
|
+
readonly max?: bigint | undefined
|
|
198
|
+
}): BigIntConstraints => {
|
|
199
|
+
const out: Types.Mutable<BigIntConstraints> = {
|
|
200
|
+
_tag: "BigIntConstraints",
|
|
201
|
+
constraints: {}
|
|
202
|
+
}
|
|
203
|
+
if (Predicate.isBigInt(options.min)) {
|
|
204
|
+
out.constraints.min = options.min
|
|
205
|
+
}
|
|
206
|
+
if (Predicate.isBigInt(options.max)) {
|
|
207
|
+
out.constraints.max = options.max
|
|
208
|
+
}
|
|
209
|
+
return out
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface ArrayConstraints {
|
|
213
|
+
readonly _tag: "ArrayConstraints"
|
|
214
|
+
readonly constraints: FastCheck.ArrayConstraints
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @internal */
|
|
218
|
+
export const makeArrayConstraints = (options: {
|
|
219
|
+
readonly minLength?: number | undefined
|
|
220
|
+
readonly maxLength?: number | undefined
|
|
221
|
+
}): ArrayConstraints => {
|
|
222
|
+
const out: Types.Mutable<ArrayConstraints> = {
|
|
223
|
+
_tag: "ArrayConstraints",
|
|
224
|
+
constraints: {}
|
|
225
|
+
}
|
|
226
|
+
if (Predicate.isNumber(options.minLength)) {
|
|
227
|
+
out.constraints.minLength = options.minLength
|
|
228
|
+
}
|
|
229
|
+
if (Predicate.isNumber(options.maxLength)) {
|
|
230
|
+
out.constraints.maxLength = options.maxLength
|
|
231
|
+
}
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
interface ArrayConfig extends ArrayConstraints {
|
|
236
|
+
readonly ast: AST.TupleType
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const makeArrayConfig = (options: {
|
|
240
|
+
readonly minLength?: number | undefined
|
|
241
|
+
readonly maxLength?: number | undefined
|
|
242
|
+
}, ast: AST.TupleType): ArrayConfig => {
|
|
243
|
+
return {
|
|
244
|
+
ast,
|
|
245
|
+
...makeArrayConstraints(options)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
type Config = StringConstraints | NumberConstraints | BigIntConstraints | ArrayConfig
|
|
250
|
+
|
|
111
251
|
const go = (
|
|
112
252
|
ast: AST.AST,
|
|
113
|
-
ctx:
|
|
253
|
+
ctx: ArbitraryGenerationContext,
|
|
114
254
|
path: ReadonlyArray<PropertyKey>
|
|
115
255
|
): LazyArbitrary<any> => {
|
|
116
256
|
const hook = getArbitraryAnnotation(ast)
|
|
@@ -118,70 +258,73 @@ const go = (
|
|
|
118
258
|
switch (ast._tag) {
|
|
119
259
|
case "Declaration":
|
|
120
260
|
return hook.value(...ast.typeParameters.map((p) => go(p, ctx, path)), ctx)
|
|
121
|
-
case "Refinement":
|
|
122
|
-
|
|
261
|
+
case "Refinement": {
|
|
262
|
+
const op = toOp(ast, ctx, path)
|
|
263
|
+
ctx = op._tag === "Deferred" ? { ...ctx, constraints: op.config } : ctx
|
|
264
|
+
const from = go(ast.from, ctx, path)
|
|
265
|
+
return new Succeed(hook.value(from, ctx), op.filters).toLazyArbitrary()
|
|
266
|
+
}
|
|
123
267
|
default:
|
|
124
268
|
return hook.value(ctx)
|
|
125
269
|
}
|
|
126
270
|
}
|
|
271
|
+
if (AST.isDeclaration(ast)) {
|
|
272
|
+
throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
|
|
273
|
+
}
|
|
274
|
+
const op = toOp(ast, ctx, path)
|
|
275
|
+
switch (op._tag) {
|
|
276
|
+
case "Succeed":
|
|
277
|
+
return op.toLazyArbitrary()
|
|
278
|
+
case "Deferred":
|
|
279
|
+
return new Succeed(op.toLazyArbitrary(ctx, path), op.filters).toLazyArbitrary()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const constStringConstraints = makeStringConstraints({})
|
|
284
|
+
const constNumberConstraints = makeNumberConstraints({})
|
|
285
|
+
const constBigIntConstraints = makeBigIntConstraints({})
|
|
286
|
+
|
|
287
|
+
/** @internal */
|
|
288
|
+
export const toOp = (
|
|
289
|
+
ast: AST.AST,
|
|
290
|
+
ctx: ArbitraryGenerationContext,
|
|
291
|
+
path: ReadonlyArray<PropertyKey>
|
|
292
|
+
): Op => {
|
|
127
293
|
switch (ast._tag) {
|
|
128
|
-
case "Declaration":
|
|
129
|
-
|
|
130
|
-
}
|
|
294
|
+
case "Declaration":
|
|
295
|
+
return new Succeed(go(ast, ctx, path))
|
|
131
296
|
case "Literal":
|
|
132
|
-
return (fc) => fc.constant(ast.literal)
|
|
297
|
+
return new Succeed((fc) => fc.constant(ast.literal))
|
|
133
298
|
case "UniqueSymbol":
|
|
134
|
-
return (fc) => fc.constant(ast.symbol)
|
|
299
|
+
return new Succeed((fc) => fc.constant(ast.symbol))
|
|
135
300
|
case "UndefinedKeyword":
|
|
136
|
-
return (fc) => fc.constant(undefined)
|
|
301
|
+
return new Succeed((fc) => fc.constant(undefined))
|
|
137
302
|
case "NeverKeyword":
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
303
|
+
throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
|
|
304
|
+
case "VoidKeyword":
|
|
141
305
|
case "UnknownKeyword":
|
|
142
306
|
case "AnyKeyword":
|
|
143
|
-
|
|
144
|
-
return (fc) => fc.anything()
|
|
307
|
+
return new Succeed((fc) => fc.anything())
|
|
145
308
|
case "StringKeyword":
|
|
146
|
-
return (
|
|
147
|
-
if (ctx.constraints) {
|
|
148
|
-
switch (ctx.constraints._tag) {
|
|
149
|
-
case "StringConstraints":
|
|
150
|
-
return fc.string(ctx.constraints.constraints)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return fc.string()
|
|
154
|
-
}
|
|
309
|
+
return new Deferred(constStringConstraints)
|
|
155
310
|
case "NumberKeyword":
|
|
156
|
-
return (
|
|
157
|
-
if (ctx.constraints) {
|
|
158
|
-
switch (ctx.constraints._tag) {
|
|
159
|
-
case "NumberConstraints":
|
|
160
|
-
return fc.float(ctx.constraints.constraints)
|
|
161
|
-
case "IntegerConstraints":
|
|
162
|
-
return fc.integer(ctx.constraints.constraints)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return fc.float()
|
|
166
|
-
}
|
|
311
|
+
return new Deferred(constNumberConstraints)
|
|
167
312
|
case "BooleanKeyword":
|
|
168
|
-
return (fc) => fc.boolean()
|
|
313
|
+
return new Succeed((fc) => fc.boolean())
|
|
169
314
|
case "BigIntKeyword":
|
|
170
|
-
return (
|
|
171
|
-
if (ctx.constraints) {
|
|
172
|
-
switch (ctx.constraints._tag) {
|
|
173
|
-
case "BigIntConstraints":
|
|
174
|
-
return fc.bigInt(ctx.constraints.constraints)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return fc.bigInt()
|
|
178
|
-
}
|
|
315
|
+
return new Deferred(constBigIntConstraints)
|
|
179
316
|
case "SymbolKeyword":
|
|
180
|
-
return (fc) => fc.string().map((s) => Symbol.for(s))
|
|
317
|
+
return new Succeed((fc) => fc.string().map((s) => Symbol.for(s)))
|
|
181
318
|
case "ObjectKeyword":
|
|
182
|
-
return (fc) => fc.oneof(fc.object(), fc.array(fc.anything()))
|
|
183
|
-
case "
|
|
184
|
-
|
|
319
|
+
return new Succeed((fc) => fc.oneof(fc.object(), fc.array(fc.anything())))
|
|
320
|
+
case "Enums": {
|
|
321
|
+
if (ast.enums.length === 0) {
|
|
322
|
+
throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(path))
|
|
323
|
+
}
|
|
324
|
+
return new Succeed((fc) => fc.oneof(...ast.enums.map(([_, value]) => fc.constant(value))))
|
|
325
|
+
}
|
|
326
|
+
case "TemplateLiteral":
|
|
327
|
+
return new Succeed((fc) => {
|
|
185
328
|
const string = fc.string({ maxLength: 5 })
|
|
186
329
|
const number = fc.float({ noDefaultInfinity: true }).filter((n) => !Number.isNaN(n))
|
|
187
330
|
|
|
@@ -208,72 +351,30 @@ const go = (
|
|
|
208
351
|
})
|
|
209
352
|
|
|
210
353
|
return fc.tuple(...components).map((spans) => spans.join(""))
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, ctx, path))
|
|
224
|
-
return (fc) => {
|
|
225
|
-
// ---------------------------------------------
|
|
226
|
-
// handle elements
|
|
227
|
-
// ---------------------------------------------
|
|
228
|
-
let output = fc.tuple(...elements.map((arb) => arb(fc)))
|
|
229
|
-
if (hasOptionals) {
|
|
230
|
-
const indexes = fc.tuple(
|
|
231
|
-
...ast.elements.map((element) => element.isOptional ? fc.boolean() : fc.constant(true))
|
|
232
|
-
)
|
|
233
|
-
output = output.chain((tuple) =>
|
|
234
|
-
indexes.map((booleans) => {
|
|
235
|
-
for (const [i, b] of booleans.reverse().entries()) {
|
|
236
|
-
if (!b) {
|
|
237
|
-
tuple.splice(booleans.length - i, 1)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return tuple
|
|
241
|
-
})
|
|
242
|
-
)
|
|
354
|
+
})
|
|
355
|
+
case "Refinement": {
|
|
356
|
+
const from = toOp(ast.from, ctx, path)
|
|
357
|
+
const filters: Op["filters"] = [
|
|
358
|
+
...from.filters,
|
|
359
|
+
(a) => Option.isNone(ast.filter(a, AST.defaultParseOption, ast))
|
|
360
|
+
]
|
|
361
|
+
switch (from._tag) {
|
|
362
|
+
case "Succeed": {
|
|
363
|
+
return new Succeed(from.lazyArbitrary, filters)
|
|
243
364
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// handle rest element
|
|
247
|
-
// ---------------------------------------------
|
|
248
|
-
if (Arr.isNonEmptyReadonlyArray(rest)) {
|
|
249
|
-
const [head, ...tail] = rest
|
|
250
|
-
const item = head(fc)
|
|
251
|
-
const constraints: FastCheck.ArrayConstraints | undefined =
|
|
252
|
-
ctx.constraints && ctx.constraints._tag === "ArrayConstraints"
|
|
253
|
-
? ctx.constraints.constraints
|
|
254
|
-
: undefined
|
|
255
|
-
output = output.chain((as) => {
|
|
256
|
-
return (ctx.depthIdentifier !== undefined
|
|
257
|
-
? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, constraints)
|
|
258
|
-
: fc.array(item, constraints)).map((rest) => [...as, ...rest])
|
|
259
|
-
})
|
|
260
|
-
// ---------------------------------------------
|
|
261
|
-
// handle post rest elements
|
|
262
|
-
// ---------------------------------------------
|
|
263
|
-
for (let j = 0; j < tail.length; j++) {
|
|
264
|
-
output = output.chain((as) => tail[j](fc).map((a) => [...as, a]))
|
|
265
|
-
}
|
|
365
|
+
case "Deferred": {
|
|
366
|
+
return new Deferred(merge(from.config, getConstraints(from.config._tag, ast)), filters)
|
|
266
367
|
}
|
|
267
|
-
|
|
268
|
-
return output
|
|
269
368
|
}
|
|
270
369
|
}
|
|
370
|
+
case "TupleType":
|
|
371
|
+
return new Deferred(makeArrayConfig({}, ast))
|
|
271
372
|
case "TypeLiteral": {
|
|
272
373
|
const propertySignaturesTypes = ast.propertySignatures.map((ps) => go(ps.type, ctx, path.concat(ps.name)))
|
|
273
374
|
const indexSignatures = ast.indexSignatures.map((is) =>
|
|
274
375
|
[go(is.parameter, ctx, path), go(is.type, ctx, path)] as const
|
|
275
376
|
)
|
|
276
|
-
return (fc) => {
|
|
377
|
+
return new Succeed((fc) => {
|
|
277
378
|
const arbs: any = {}
|
|
278
379
|
const requiredKeys: Array<PropertyKey> = []
|
|
279
380
|
// ---------------------------------------------
|
|
@@ -304,274 +405,222 @@ const go = (
|
|
|
304
405
|
}
|
|
305
406
|
|
|
306
407
|
return output
|
|
307
|
-
}
|
|
408
|
+
})
|
|
308
409
|
}
|
|
309
410
|
case "Union": {
|
|
310
411
|
const types = ast.types.map((member) => go(member, ctx, path))
|
|
311
|
-
return (fc) => fc.oneof(...types.map((arb) => arb(fc)))
|
|
312
|
-
}
|
|
313
|
-
case "Enums": {
|
|
314
|
-
if (ast.enums.length === 0) {
|
|
315
|
-
throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(path))
|
|
316
|
-
}
|
|
317
|
-
return (fc) => fc.oneof(...ast.enums.map(([_, value]) => fc.constant(value)))
|
|
318
|
-
}
|
|
319
|
-
case "Refinement": {
|
|
320
|
-
const from = getRefinementFromArbitrary(ast, ctx, path)
|
|
321
|
-
return (fc) => from(fc).filter((a) => Option.isNone(ast.filter(a, AST.defaultParseOption, ast)))
|
|
412
|
+
return new Succeed((fc) => fc.oneof(...types.map((arb) => arb(fc))))
|
|
322
413
|
}
|
|
323
414
|
case "Suspend": {
|
|
324
415
|
const get = util_.memoizeThunk(() => {
|
|
325
416
|
return go(ast.f(), getSuspendedContext(ctx, ast), path)
|
|
326
417
|
})
|
|
327
|
-
return (fc) => fc.constant(null).chain(() => get()(fc))
|
|
418
|
+
return new Succeed((fc) => fc.constant(null).chain(() => get()(fc)))
|
|
328
419
|
}
|
|
329
420
|
case "Transformation":
|
|
330
|
-
return go(ast.to, ctx, path)
|
|
421
|
+
return new Succeed(go(ast.to, ctx, path))
|
|
331
422
|
}
|
|
332
423
|
}
|
|
333
424
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
if (Predicate.isNumber(options.max)) {
|
|
349
|
-
this.constraints.max = Math.fround(options.max)
|
|
350
|
-
}
|
|
351
|
-
if (Predicate.isBoolean(options.noNaN)) {
|
|
352
|
-
this.constraints.noNaN = options.noNaN
|
|
353
|
-
}
|
|
354
|
-
if (Predicate.isBoolean(options.noDefaultInfinity)) {
|
|
355
|
-
this.constraints.noDefaultInfinity = options.noDefaultInfinity
|
|
425
|
+
const goTupleType = (
|
|
426
|
+
ast: AST.TupleType,
|
|
427
|
+
ctx: ArbitraryGenerationContext,
|
|
428
|
+
path: ReadonlyArray<PropertyKey>,
|
|
429
|
+
constraints: FastCheck.ArrayConstraints
|
|
430
|
+
): LazyArbitrary<any> => {
|
|
431
|
+
const elements: Array<LazyArbitrary<any>> = []
|
|
432
|
+
let hasOptionals = false
|
|
433
|
+
let i = 0
|
|
434
|
+
for (const element of ast.elements) {
|
|
435
|
+
elements.push(go(element.type, ctx, path.concat(i++)))
|
|
436
|
+
if (element.isOptional) {
|
|
437
|
+
hasOptionals = true
|
|
356
438
|
}
|
|
357
439
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
440
|
+
const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, ctx, path))
|
|
441
|
+
return (fc) => {
|
|
442
|
+
// ---------------------------------------------
|
|
443
|
+
// handle elements
|
|
444
|
+
// ---------------------------------------------
|
|
445
|
+
let output = fc.tuple(...elements.map((arb) => arb(fc)))
|
|
446
|
+
if (hasOptionals) {
|
|
447
|
+
const indexes = fc.tuple(
|
|
448
|
+
...ast.elements.map((element) => element.isOptional ? fc.boolean() : fc.constant(true))
|
|
449
|
+
)
|
|
450
|
+
output = output.chain((tuple) =>
|
|
451
|
+
indexes.map((booleans) => {
|
|
452
|
+
for (const [i, b] of booleans.reverse().entries()) {
|
|
453
|
+
if (!b) {
|
|
454
|
+
tuple.splice(booleans.length - i, 1)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return tuple
|
|
458
|
+
})
|
|
459
|
+
)
|
|
374
460
|
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
461
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
462
|
+
// ---------------------------------------------
|
|
463
|
+
// handle rest element
|
|
464
|
+
// ---------------------------------------------
|
|
465
|
+
if (Arr.isNonEmptyReadonlyArray(rest)) {
|
|
466
|
+
const [head, ...tail] = rest
|
|
467
|
+
const item = head(fc)
|
|
468
|
+
output = output.chain((as) => {
|
|
469
|
+
return (ctx.depthIdentifier !== undefined
|
|
470
|
+
? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, constraints)
|
|
471
|
+
: fc.array(item, constraints)).map((rest) => [...as, ...rest])
|
|
472
|
+
})
|
|
473
|
+
// ---------------------------------------------
|
|
474
|
+
// handle post rest elements
|
|
475
|
+
// ---------------------------------------------
|
|
476
|
+
for (let j = 0; j < tail.length; j++) {
|
|
477
|
+
output = output.chain((as) => tail[j](fc).map((a) => [...as, a]))
|
|
478
|
+
}
|
|
392
479
|
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
480
|
|
|
396
|
-
|
|
397
|
-
export class ArrayConstraints {
|
|
398
|
-
readonly _tag = "ArrayConstraints"
|
|
399
|
-
readonly constraints: FastCheck.ArrayConstraints
|
|
400
|
-
constructor(options: {
|
|
401
|
-
readonly minLength?: number | undefined
|
|
402
|
-
readonly maxLength?: number | undefined
|
|
403
|
-
}) {
|
|
404
|
-
this.constraints = {}
|
|
405
|
-
if (Predicate.isNumber(options.minLength)) {
|
|
406
|
-
this.constraints.minLength = options.minLength
|
|
407
|
-
}
|
|
408
|
-
if (Predicate.isNumber(options.maxLength)) {
|
|
409
|
-
this.constraints.maxLength = options.maxLength
|
|
410
|
-
}
|
|
481
|
+
return output
|
|
411
482
|
}
|
|
412
483
|
}
|
|
413
484
|
|
|
414
|
-
|
|
415
|
-
export class BigIntConstraints {
|
|
416
|
-
readonly _tag = "BigIntConstraints"
|
|
417
|
-
readonly constraints: FastCheck.BigIntConstraints
|
|
418
|
-
constructor(options: {
|
|
419
|
-
readonly min?: bigint | undefined
|
|
420
|
-
readonly max?: bigint | undefined
|
|
421
|
-
}) {
|
|
422
|
-
this.constraints = {}
|
|
423
|
-
if (Predicate.isBigInt(options.min)) {
|
|
424
|
-
this.constraints.min = options.min
|
|
425
|
-
}
|
|
426
|
-
if (Predicate.isBigInt(options.max)) {
|
|
427
|
-
this.constraints.max = options.max
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
485
|
+
type Constraints = StringConstraints | NumberConstraints | BigIntConstraints | ArrayConstraints
|
|
431
486
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
| StringConstraints
|
|
436
|
-
| IntegerConstraints
|
|
437
|
-
| ArrayConstraints
|
|
438
|
-
| BigIntConstraints
|
|
487
|
+
const getConstraints = (_tag: Constraints["_tag"], ast: AST.Refinement): Constraints | undefined => {
|
|
488
|
+
const TypeAnnotationId: any = ast.annotations[AST.SchemaIdAnnotationId]
|
|
489
|
+
const jsonSchema: Record<string, any> = Option.getOrElse(AST.getJSONSchemaAnnotation(ast), () => ({}))
|
|
439
490
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
// bigint
|
|
459
|
-
case filters_.GreaterThanBigintSchemaId:
|
|
460
|
-
case filters_.GreaterThanOrEqualToBigIntSchemaId:
|
|
461
|
-
case filters_.LessThanBigIntSchemaId:
|
|
462
|
-
case filters_.LessThanOrEqualToBigIntSchemaId:
|
|
463
|
-
case filters_.BetweenBigintSchemaId: {
|
|
464
|
-
const constraints: any = ast.annotations[TypeAnnotationId]
|
|
465
|
-
return new BigIntConstraints(constraints)
|
|
491
|
+
switch (_tag) {
|
|
492
|
+
case "StringConstraints":
|
|
493
|
+
return makeStringConstraints(jsonSchema)
|
|
494
|
+
case "NumberConstraints": {
|
|
495
|
+
switch (TypeAnnotationId) {
|
|
496
|
+
case filters_.NonNaNSchemaId:
|
|
497
|
+
return makeNumberConstraints({ noNaN: true })
|
|
498
|
+
default:
|
|
499
|
+
return makeNumberConstraints({
|
|
500
|
+
isInteger: "type" in jsonSchema && jsonSchema.type === "integer",
|
|
501
|
+
noNaN: "type" in jsonSchema && jsonSchema.type === "number" ? true : undefined,
|
|
502
|
+
noDefaultInfinity: "type" in jsonSchema && jsonSchema.type === "number" ? true : undefined,
|
|
503
|
+
min: jsonSchema.exclusiveMinimum ?? jsonSchema.minimum,
|
|
504
|
+
minExcluded: "exclusiveMinimum" in jsonSchema ? true : undefined,
|
|
505
|
+
max: jsonSchema.exclusiveMaximum ?? jsonSchema.maximum,
|
|
506
|
+
maxExcluded: "exclusiveMaximum" in jsonSchema ? true : undefined
|
|
507
|
+
})
|
|
508
|
+
}
|
|
466
509
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
case
|
|
470
|
-
|
|
471
|
-
return new StringConstraints(jsonSchema)
|
|
472
|
-
// array
|
|
473
|
-
case filters_.MinItemsSchemaId:
|
|
474
|
-
case filters_.MaxItemsSchemaId:
|
|
475
|
-
case filters_.ItemsCountSchemaId:
|
|
476
|
-
return new ArrayConstraints({
|
|
510
|
+
case "BigIntConstraints":
|
|
511
|
+
return makeBigIntConstraints(ast.annotations[TypeAnnotationId] as any)
|
|
512
|
+
case "ArrayConstraints":
|
|
513
|
+
return makeArrayConstraints({
|
|
477
514
|
minLength: jsonSchema.minItems,
|
|
478
515
|
maxLength: jsonSchema.maxItems
|
|
479
516
|
})
|
|
480
517
|
}
|
|
481
518
|
}
|
|
482
519
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
520
|
+
function getMax(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
|
|
521
|
+
function getMax(n1: number | undefined, n2: number | undefined): number | undefined
|
|
522
|
+
function getMax(
|
|
523
|
+
n1: bigint | number | undefined,
|
|
524
|
+
n2: bigint | number | undefined
|
|
525
|
+
): bigint | number | undefined {
|
|
526
|
+
return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n2 : n1
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function getMin(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
|
|
530
|
+
function getMin(n1: number | undefined, n2: number | undefined): number | undefined
|
|
531
|
+
function getMin(
|
|
532
|
+
n1: bigint | number | undefined,
|
|
533
|
+
n2: bigint | number | undefined
|
|
534
|
+
): bigint | number | undefined {
|
|
535
|
+
return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n1 : n2
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const getOr = (a: boolean | undefined, b: boolean | undefined): boolean | undefined => {
|
|
539
|
+
return a === undefined ? b : b === undefined ? a : a || b
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const merge = (c1: Config, c2: Constraints | undefined): Config => {
|
|
543
|
+
if (c2) {
|
|
544
|
+
switch (c1._tag) {
|
|
545
|
+
case "StringConstraints": {
|
|
546
|
+
if (c2._tag === "StringConstraints") {
|
|
547
|
+
return makeStringConstraints({
|
|
499
548
|
minLength: getMax(c1.constraints.minLength, c2.constraints.minLength),
|
|
500
|
-
maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength)
|
|
549
|
+
maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength),
|
|
550
|
+
pattern: c1.pattern ?? c2.pattern
|
|
501
551
|
})
|
|
552
|
+
}
|
|
553
|
+
break
|
|
502
554
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
case "NumberConstraints":
|
|
508
|
-
return new NumberConstraints({
|
|
555
|
+
case "NumberConstraints": {
|
|
556
|
+
if (c2._tag === "NumberConstraints") {
|
|
557
|
+
return makeNumberConstraints({
|
|
558
|
+
isInteger: c1.isInteger || c2.isInteger,
|
|
509
559
|
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
560
|
+
minExcluded: getOr(c1.constraints.minExcluded, c2.constraints.minExcluded),
|
|
510
561
|
max: getMin(c1.constraints.max, c2.constraints.max),
|
|
562
|
+
maxExcluded: getOr(c1.constraints.maxExcluded, c2.constraints.maxExcluded),
|
|
511
563
|
noNaN: getOr(c1.constraints.noNaN, c2.constraints.noNaN),
|
|
512
564
|
noDefaultInfinity: getOr(c1.constraints.noDefaultInfinity, c2.constraints.noDefaultInfinity)
|
|
513
565
|
})
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
517
|
-
max: getMin(c1.constraints.max, c2.constraints.max)
|
|
518
|
-
})
|
|
566
|
+
}
|
|
567
|
+
break
|
|
519
568
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
switch (c2._tag) {
|
|
524
|
-
case "BigIntConstraints":
|
|
525
|
-
return new BigIntConstraints({
|
|
569
|
+
case "BigIntConstraints": {
|
|
570
|
+
if (c2._tag === "BigIntConstraints") {
|
|
571
|
+
return makeBigIntConstraints({
|
|
526
572
|
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
527
573
|
max: getMin(c1.constraints.max, c2.constraints.max)
|
|
528
574
|
})
|
|
575
|
+
}
|
|
576
|
+
break
|
|
529
577
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
switch (c2._tag) {
|
|
534
|
-
case "StringConstraints":
|
|
535
|
-
return new StringConstraints({
|
|
578
|
+
case "ArrayConstraints": {
|
|
579
|
+
if (c2._tag === "ArrayConstraints") {
|
|
580
|
+
return makeArrayConfig({
|
|
536
581
|
minLength: getMax(c1.constraints.minLength, c2.constraints.minLength),
|
|
537
582
|
maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength)
|
|
538
|
-
})
|
|
539
|
-
}
|
|
540
|
-
break
|
|
541
|
-
}
|
|
542
|
-
case "IntegerConstraints": {
|
|
543
|
-
switch (c2._tag) {
|
|
544
|
-
case "NumberConstraints":
|
|
545
|
-
case "IntegerConstraints": {
|
|
546
|
-
return new IntegerConstraints({
|
|
547
|
-
min: getMax(c1.constraints.min, c2.constraints.min),
|
|
548
|
-
max: getMin(c1.constraints.max, c2.constraints.max)
|
|
549
|
-
})
|
|
583
|
+
}, c1.ast)
|
|
550
584
|
}
|
|
585
|
+
break
|
|
551
586
|
}
|
|
552
|
-
break
|
|
553
587
|
}
|
|
554
588
|
}
|
|
589
|
+
return c1
|
|
555
590
|
}
|
|
556
591
|
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
592
|
+
const getSuspendedArray = (
|
|
593
|
+
fc: typeof FastCheck,
|
|
594
|
+
depthIdentifier: string,
|
|
595
|
+
maxDepth: number,
|
|
596
|
+
item: FastCheck.Arbitrary<any>,
|
|
597
|
+
constraints?: FastCheck.ArrayConstraints
|
|
598
|
+
) => {
|
|
599
|
+
let minLength = 1
|
|
600
|
+
let maxLength = 2
|
|
601
|
+
if (constraints && constraints.minLength !== undefined && constraints.minLength > minLength) {
|
|
602
|
+
minLength = constraints.minLength
|
|
603
|
+
if (minLength > maxLength) {
|
|
604
|
+
maxLength = minLength
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return fc.oneof(
|
|
608
|
+
{ maxDepth, depthIdentifier },
|
|
609
|
+
fc.constant([]),
|
|
610
|
+
fc.array(item, { minLength, maxLength })
|
|
611
|
+
)
|
|
568
612
|
}
|
|
569
613
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
614
|
+
const getSuspendedContext = (
|
|
615
|
+
ctx: ArbitraryGenerationContext,
|
|
616
|
+
ast: AST.Suspend
|
|
617
|
+
): ArbitraryGenerationContext => {
|
|
618
|
+
if (ctx.depthIdentifier !== undefined) {
|
|
619
|
+
return ctx
|
|
620
|
+
}
|
|
621
|
+
const depthIdentifier = AST.getIdentifierAnnotation(ast).pipe(
|
|
622
|
+
Option.orElse(() => AST.getIdentifierAnnotation(ast.f())),
|
|
623
|
+
Option.getOrElse(() => "SuspendDefaultDepthIdentifier")
|
|
624
|
+
)
|
|
625
|
+
return { ...ctx, depthIdentifier }
|
|
577
626
|
}
|