zod-fast-check
Advanced tools
Comparing version 0.9.1 to 0.10.0
@@ -112,3 +112,4 @@ "use strict"; | ||
let maxLength = null; | ||
let hasRegexCheck = false; | ||
let hasUnsupportedCheck = false; | ||
const mappings = []; | ||
for (const check of schema._def.checks) { | ||
@@ -122,2 +123,17 @@ switch (check.kind) { | ||
break; | ||
case "length": | ||
minLength = check.value; | ||
maxLength = check.value; | ||
break; | ||
case "startsWith": | ||
mappings.push((s) => check.value + s); | ||
break; | ||
case "endsWith": | ||
mappings.push((s) => s + check.value); | ||
break; | ||
case "trim": | ||
// No special handling needed for inputs. | ||
break; | ||
case "cuid": | ||
return createCuidArb(); | ||
case "uuid": | ||
@@ -129,5 +145,6 @@ return fast_check_1.default.uuid(); | ||
return fast_check_1.default.webUrl(); | ||
case "regex": | ||
hasRegexCheck = true; | ||
break; | ||
case "datetime": | ||
return createDatetimeStringArb(schema, check); | ||
default: | ||
hasUnsupportedCheck = true; | ||
} | ||
@@ -137,7 +154,10 @@ } | ||
maxLength = 2 * minLength + 10; | ||
const unfiltered = fast_check_1.default.string({ | ||
let unfiltered = fast_check_1.default.string({ | ||
minLength, | ||
maxLength, | ||
}); | ||
if (hasRegexCheck) { | ||
for (let mapping of mappings) { | ||
unfiltered = unfiltered.map(mapping); | ||
} | ||
if (hasUnsupportedCheck) { | ||
return filterArbitraryBySchema(unfiltered, schema, path); | ||
@@ -152,3 +172,4 @@ } | ||
let max = Number.MAX_SAFE_INTEGER; | ||
let isInt = false; | ||
let isFinite = false; | ||
let multipleOf = null; | ||
for (const check of schema._def.checks) { | ||
@@ -160,14 +181,27 @@ switch (check.kind) { | ||
case "max": | ||
isFinite = true; | ||
max = Math.min(max, check.inclusive ? check.value : check.value - 0.001); | ||
break; | ||
case "int": | ||
isInt = true; | ||
multipleOf !== null && multipleOf !== void 0 ? multipleOf : (multipleOf = 1); | ||
break; | ||
case "finite": | ||
isFinite = true; | ||
break; | ||
case "multipleOf": | ||
multipleOf = (multipleOf !== null && multipleOf !== void 0 ? multipleOf : 1) * check.value; | ||
break; | ||
} | ||
} | ||
if (isInt) { | ||
return fast_check_1.default.integer({ min, max }); | ||
if (multipleOf !== null) { | ||
const factor = multipleOf; | ||
return fast_check_1.default | ||
.integer({ | ||
min: Math.ceil(min / factor), | ||
max: Math.floor(max / factor), | ||
}) | ||
.map((x) => x * factor); | ||
} | ||
else { | ||
return fast_check_1.default.double({ | ||
const finiteArb = fast_check_1.default.double({ | ||
min, | ||
@@ -180,2 +214,8 @@ max, | ||
}); | ||
if (isFinite) { | ||
return finiteArb; | ||
} | ||
else { | ||
return fast_check_1.default.oneof(finiteArb, fast_check_1.default.constant(Infinity)); | ||
} | ||
} | ||
@@ -323,2 +363,5 @@ }, | ||
}, | ||
ZodSymbol() { | ||
return fast_check_1.default.string().map((s) => Symbol(s)); | ||
}, | ||
}; | ||
@@ -338,2 +381,44 @@ class ZodFastCheckError extends Error { | ||
} | ||
// based on the rough spec provided here: https://github.com/paralleldrive/cuid | ||
function createCuidArb() { | ||
return fast_check_1.default | ||
.tuple(fast_check_1.default.hexaString({ minLength: 8, maxLength: 8 }), fast_check_1.default | ||
.integer({ min: 0, max: 9999 }) | ||
.map((n) => n.toString().padStart(4, "0")), fast_check_1.default.hexaString({ minLength: 4, maxLength: 4 }), fast_check_1.default.hexaString({ minLength: 8, maxLength: 8 })) | ||
.map(([timestamp, counter, fingerprint, random]) => "c" + timestamp + counter + fingerprint + random); | ||
} | ||
function createDatetimeStringArb(schema, check) { | ||
let arb = fast_check_1.default | ||
.date({ | ||
min: new Date("0000-01-01T00:00:00Z"), | ||
max: new Date("9999-12-31T23:59:59Z"), | ||
}) | ||
.map((date) => date.toISOString()); | ||
if (check.precision === 0) { | ||
arb = arb.map((utcIsoDatetime) => utcIsoDatetime.replace(/\.\d+Z$/, `Z`)); | ||
} | ||
else if (check.precision !== null) { | ||
const precision = check.precision; | ||
arb = arb.chain((utcIsoDatetime) => fast_check_1.default | ||
.integer({ min: 0, max: Math.pow(10, precision) - 1 }) | ||
.map((x) => x.toString().padStart(precision, "0")) | ||
.map((fractionalDigits) => utcIsoDatetime.replace(/\.\d+Z$/, `.${fractionalDigits}Z`))); | ||
} | ||
if (check.offset) { | ||
// Add an arbitrary timezone offset on, if the schema supports it. | ||
// UTC−12:00 is the furthest behind UTC, UTC+14:00 is the furthest ahead. | ||
// This does not generate offsets for half-hour and 15 minute timezones. | ||
arb = arb.chain((utcIsoDatetime) => fast_check_1.default.integer({ min: -12, max: +14 }).map((offsetHours) => { | ||
if (offsetHours === 0) { | ||
return utcIsoDatetime; | ||
} | ||
else { | ||
const sign = offsetHours > 0 ? "+" : "-"; | ||
const paddedHours = Math.abs(offsetHours).toString().padStart(2, "0"); | ||
return utcIsoDatetime.replace(/Z$/, `${sign}${paddedHours}:00`); | ||
} | ||
})); | ||
} | ||
return arb; | ||
} | ||
/** | ||
@@ -340,0 +425,0 @@ * Returns a type guard which filters one member from a union type. |
{ | ||
"name": "zod-fast-check", | ||
"version": "0.9.1", | ||
"version": "0.10.0", | ||
"description": "Generate fast-check arbitraries from Zod schemas.", | ||
@@ -5,0 +5,0 @@ "main": "dist/zod-fast-check.js", |
@@ -91,3 +91,3 @@ # zod-fast-check | ||
✅ string (including email, UUID and URL) | ||
✅ string (including email, datetime, UUID and URL) | ||
✅ number | ||
@@ -100,2 +100,3 @@ ✅ nan | ||
✅ null | ||
✅ symbol | ||
✅ array | ||
@@ -123,2 +124,4 @@ ✅ object | ||
✅ refinements (see below) | ||
✅ pipe | ||
✅ catch | ||
❌ intersection | ||
@@ -125,0 +128,0 @@ ❌ lazy |
26261
491
133