(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Superstruct = {})); })(this, (function (exports) { 'use strict'; /** * A `StructFailure` represents a single specific failure in validation. */ /** * `StructError` objects are thrown (or returned) when validation fails. * * Validation logic is design to exit early for maximum performance. The error * represents the first error encountered during validation. For more detail, * the `error.failures` property is a generator function that can be run to * continue validation and receive all the failures in the data. */ class StructError extends TypeError { constructor(failure, failures) { let cached; const { message, explanation, ...rest } = failure; const { path } = failure; const msg = path.length === 0 ? message : `At path: ${path.join('.')} -- ${message}`; super(explanation ?? msg); if (explanation != null) this.cause = msg; Object.assign(this, rest); this.name = this.constructor.name; this.failures = () => { return (cached ?? (cached = [failure, ...failures()])); }; } } /** * Check if a value is an iterator. */ function isIterable(x) { return isObject(x) && typeof x[Symbol.iterator] === 'function'; } /** * Check if a value is a plain object. */ function isObject(x) { return typeof x === 'object' && x != null; } /** * Check if a value is a non-array object. */ function isNonArrayObject(x) { return isObject(x) && !Array.isArray(x); } /** * Check if a value is a plain object. */ function isPlainObject(x) { if (Object.prototype.toString.call(x) !== '[object Object]') { return false; } const prototype = Object.getPrototypeOf(x); return prototype === null || prototype === Object.prototype; } /** * Return a value as a printable string. */ function print(value) { if (typeof value === 'symbol') { return value.toString(); } return typeof value === 'string' ? JSON.stringify(value) : `${value}`; } /** * Shifts (removes and returns) the first value from the `input` iterator. * Like `Array.prototype.shift()` but for an `Iterator`. */ function shiftIterator(input) { const { done, value } = input.next(); return done ? undefined : value; } /** * Convert a single validation result to a failure. */ function toFailure(result, context, struct, value) { if (result === true) { return; } else if (result === false) { result = {}; } else if (typeof result === 'string') { result = { message: result }; } const { path, branch } = context; const { type } = struct; const { refinement, message = `Expected a value of type \`${type}\`${refinement ? ` with refinement \`${refinement}\`` : ''}, but received: \`${print(value)}\``, } = result; return { value, type, refinement, key: path[path.length - 1], path, branch, ...result, message, }; } /** * Convert a validation result to an iterable of failures. */ function* toFailures(result, context, struct, value) { if (!isIterable(result)) { result = [result]; } for (const r of result) { const failure = toFailure(r, context, struct, value); if (failure) { yield failure; } } } /** * Check a value against a struct, traversing deeply into nested values, and * returning an iterator of failures or success. */ function* run(value, struct, options = {}) { const { path = [], branch = [value], coerce = false, mask = false } = options; const ctx = { path, branch, mask }; if (coerce) { value = struct.coercer(value, ctx); } let status = 'valid'; for (const failure of struct.validator(value, ctx)) { failure.explanation = options.message; status = 'not_valid'; yield [failure, undefined]; } for (let [k, v, s] of struct.entries(value, ctx)) { const ts = run(v, s, { path: k === undefined ? path : [...path, k], branch: k === undefined ? branch : [...branch, v], coerce, mask, message: options.message, }); for (const t of ts) { if (t[0]) { status = t[0].refinement != null ? 'not_refined' : 'not_valid'; yield [t[0], undefined]; } else if (coerce) { v = t[1]; if (k === undefined) { value = v; } else if (value instanceof Map) { value.set(k, v); } else if (value instanceof Set) { value.add(v); } else if (isObject(value)) { if (v !== undefined || k in value) value[k] = v; } } } } if (status !== 'not_valid') { for (const failure of struct.refiner(value, ctx)) { failure.explanation = options.message; status = 'not_refined'; yield [failure, undefined]; } } if (status === 'valid') { yield [undefined, value]; } } /** * `Struct` objects encapsulate the validation logic for a specific type of * values. Once constructed, you use the `assert`, `is` or `validate` helpers to * validate unknown input data against the struct. */ class Struct { constructor(props) { const { type, schema, validator, refiner, coercer = (value) => value, entries = function* () { }, } = props; this.type = type; this.schema = schema; this.entries = entries; this.coercer = coercer; if (validator) { this.validator = (value, context) => { const result = validator(value, context); return toFailures(result, context, this, value); }; } else { this.validator = () => []; } if (refiner) { this.refiner = (value, context) => { const result = refiner(value, context); return toFailures(result, context, this, value); }; } else { this.refiner = () => []; } } /** * Assert that a value passes the struct's validation, throwing if it doesn't. */ assert(value, message) { return assert(value, this, message); } /** * Create a value with the struct's coercion logic, then validate it. */ create(value, message) { return create(value, this, message); } /** * Check if a value passes the struct's validation. */ is(value) { return is(value, this); } /** * Mask a value, coercing and validating it, but returning only the subset of * properties defined by the struct's schema. Masking applies recursively to * props of `object` structs only. */ mask(value, message) { return mask(value, this, message); } /** * Validate a value with the struct's validation logic, returning a tuple * representing the result. * * You may optionally pass `true` for the `coerce` argument to coerce * the value before attempting to validate it. If you do, the result will * contain the coerced result when successful. Also, `mask` will turn on * masking of the unknown `object` props recursively if passed. */ validate(value, options = {}) { return validate(value, this, options); } } /** * Assert that a value passes a struct, throwing if it doesn't. */ function assert(value, struct, message) { const result = validate(value, struct, { message }); if (result[0]) { throw result[0]; } } /** * Create a value with the coercion logic of struct and validate it. */ function create(value, struct, message) { const result = validate(value, struct, { coerce: true, message }); if (result[0]) { throw result[0]; } else { return result[1]; } } /** * Mask a value, returning only the subset of properties defined by a struct. */ function mask(value, struct, message) { const result = validate(value, struct, { coerce: true, mask: true, message }); if (result[0]) { throw result[0]; } else { return result[1]; } } /** * Check if a value passes a struct. */ function is(value, struct) { const result = validate(value, struct); return !result[0]; } /** * Validate a value against a struct, returning an error if invalid, or the * value (with potential coercion) if valid. */ function validate(value, struct, options = {}) { const tuples = run(value, struct, options); const tuple = shiftIterator(tuples); if (tuple[0]) { const error = new StructError(tuple[0], function* () { for (const t of tuples) { if (t[0]) { yield t[0]; } } }); return [error, undefined]; } else { const v = tuple[1]; return [undefined, v]; } } function assign(...Structs) { const isType = Structs[0].type === 'type'; const schemas = Structs.map((s) => s.schema); const schema = Object.assign({}, ...schemas); return isType ? type(schema) : object(schema); } /** * Define a new struct type with a custom validation function. */ function define(name, validator) { return new Struct({ type: name, schema: null, validator }); } /** * Create a new struct based on an existing struct, but the value is allowed to * be `undefined`. `log` will be called if the value is not `undefined`. */ function deprecated(struct, log) { return new Struct({ ...struct, refiner: (value, ctx) => value === undefined || struct.refiner(value, ctx), validator(value, ctx) { if (value === undefined) { return true; } else { log(value, ctx); return struct.validator(value, ctx); } }, }); } /** * Create a struct with dynamic validation logic. * * The callback will receive the value currently being validated, and must * return a struct object to validate it with. This can be useful to model * validation logic that changes based on its input. */ function dynamic(fn) { return new Struct({ type: 'dynamic', schema: null, *entries(value, ctx) { const struct = fn(value, ctx); yield* struct.entries(value, ctx); }, validator(value, ctx) { const struct = fn(value, ctx); return struct.validator(value, ctx); }, coercer(value, ctx) { const struct = fn(value, ctx); return struct.coercer(value, ctx); }, refiner(value, ctx) { const struct = fn(value, ctx); return struct.refiner(value, ctx); }, }); } /** * Create a struct with lazily evaluated validation logic. * * The first time validation is run with the struct, the callback will be called * and must return a struct object to use. This is useful for cases where you * want to have self-referential structs for nested data structures to avoid a * circular definition problem. */ function lazy(fn) { let struct; return new Struct({ type: 'lazy', schema: null, *entries(value, ctx) { struct ?? (struct = fn()); yield* struct.entries(value, ctx); }, validator(value, ctx) { struct ?? (struct = fn()); return struct.validator(value, ctx); }, coercer(value, ctx) { struct ?? (struct = fn()); return struct.coercer(value, ctx); }, refiner(value, ctx) { struct ?? (struct = fn()); return struct.refiner(value, ctx); }, }); } /** * Create a new struct based on an existing object struct, but excluding * specific properties. * * Like TypeScript's `Omit` utility. */ function omit(struct, keys) { const { schema } = struct; const subschema = { ...schema }; for (const key of keys) { delete subschema[key]; } switch (struct.type) { case 'type': return type(subschema); default: return object(subschema); } } /** * Create a new struct based on an existing object struct, but with all of its * properties allowed to be `undefined`. * * Like TypeScript's `Partial` utility. */ function partial(struct) { const isStruct = struct instanceof Struct; const schema = isStruct ? { ...struct.schema } : { ...struct }; for (const key in schema) { schema[key] = optional(schema[key]); } if (isStruct && struct.type === 'type') { return type(schema); } return object(schema); } /** * Create a new struct based on an existing object struct, but only including * specific properties. * * Like TypeScript's `Pick` utility. */ function pick(struct, keys) { const { schema } = struct; const subschema = {}; for (const key of keys) { subschema[key] = schema[key]; } switch (struct.type) { case 'type': return type(subschema); default: return object(subschema); } } /** * Define a new struct type with a custom validation function. * * @deprecated This function has been renamed to `define`. */ function struct(name, validator) { console.warn('superstruct@0.11 - The `struct` helper has been renamed to `define`.'); return define(name, validator); } /** * Ensure that any value passes validation. */ function any() { return define('any', () => true); } function array(Element) { return new Struct({ type: 'array', schema: Element, *entries(value) { if (Element && Array.isArray(value)) { for (const [i, v] of value.entries()) { yield [i, v, Element]; } } }, coercer(value) { return Array.isArray(value) ? value.slice() : value; }, validator(value) { return (Array.isArray(value) || `Expected an array value, but received: ${print(value)}`); }, }); } /** * Ensure that a value is a bigint. */ function bigint() { return define('bigint', (value) => { return typeof value === 'bigint'; }); } /** * Ensure that a value is a boolean. */ function boolean() { return define('boolean', (value) => { return typeof value === 'boolean'; }); } /** * Ensure that a value is a valid `Date`. * * Note: this also ensures that the value is *not* an invalid `Date` object, * which can occur when parsing a date fails but still returns a `Date`. */ function date() { return define('date', (value) => { return ((value instanceof Date && !isNaN(value.getTime())) || `Expected a valid \`Date\` object, but received: ${print(value)}`); }); } function enums(values) { const schema = {}; const description = values.map((v) => print(v)).join(); for (const key of values) { schema[key] = key; } return new Struct({ type: 'enums', schema, validator(value) { return (values.includes(value) || `Expected one of \`${description}\`, but received: ${print(value)}`); }, }); } /** * Ensure that a value is a function. */ function func() { return define('func', (value) => { return (typeof value === 'function' || `Expected a function, but received: ${print(value)}`); }); } /** * Ensure that a value is an instance of a specific class. */ function instance(Class) { return define('instance', (value) => { return (value instanceof Class || `Expected a \`${Class.name}\` instance, but received: ${print(value)}`); }); } /** * Ensure that a value is an integer. */ function integer() { return define('integer', (value) => { return ((typeof value === 'number' && !isNaN(value) && Number.isInteger(value)) || `Expected an integer, but received: ${print(value)}`); }); } /** * Ensure that a value matches all of a set of types. */ function intersection(Structs) { return new Struct({ type: 'intersection', schema: null, *entries(value, ctx) { for (const S of Structs) { yield* S.entries(value, ctx); } }, *validator(value, ctx) { for (const S of Structs) { yield* S.validator(value, ctx); } }, *refiner(value, ctx) { for (const S of Structs) { yield* S.refiner(value, ctx); } }, }); } function literal(constant) { const description = print(constant); const t = typeof constant; return new Struct({ type: 'literal', schema: t === 'string' || t === 'number' || t === 'boolean' ? constant : null, validator(value) { return (value === constant || `Expected the literal \`${description}\`, but received: ${print(value)}`); }, }); } function map(Key, Value) { return new Struct({ type: 'map', schema: null, *entries(value) { if (Key && Value && value instanceof Map) { for (const [k, v] of value.entries()) { yield [k, k, Key]; yield [k, v, Value]; } } }, coercer(value) { return value instanceof Map ? new Map(value) : value; }, validator(value) { return (value instanceof Map || `Expected a \`Map\` object, but received: ${print(value)}`); }, }); } /** * Ensure that no value ever passes validation. */ function never() { return define('never', () => false); } /** * Augment an existing struct to allow `null` values. */ function nullable(struct) { return new Struct({ ...struct, validator: (value, ctx) => value === null || struct.validator(value, ctx), refiner: (value, ctx) => value === null || struct.refiner(value, ctx), }); } /** * Ensure that a value is a number. */ function number() { return define('number', (value) => { return ((typeof value === 'number' && !isNaN(value)) || `Expected a number, but received: ${print(value)}`); }); } function object(schema) { const knowns = schema ? Object.keys(schema) : []; const Never = never(); return new Struct({ type: 'object', schema: schema ? schema : null, *entries(value) { if (schema && isObject(value)) { const unknowns = new Set(Object.keys(value)); for (const key of knowns) { unknowns.delete(key); yield [key, value[key], schema[key]]; } for (const key of unknowns) { yield [key, value[key], Never]; } } }, validator(value) { return (isNonArrayObject(value) || `Expected an object, but received: ${print(value)}`); }, coercer(value, ctx) { if (!isNonArrayObject(value)) { return value; } const coerced = { ...value }; // The `object` struct has special behaviour enabled by the mask flag. // When masking, properties that are not in the schema are deleted from // the coerced object instead of eventually failing validaiton. if (ctx.mask && schema) { for (const key in coerced) { if (schema[key] === undefined) { delete coerced[key]; } } } return coerced; }, }); } /** * Augment a struct to allow `undefined` values. */ function optional(struct) { return new Struct({ ...struct, validator: (value, ctx) => value === undefined || struct.validator(value, ctx), refiner: (value, ctx) => value === undefined || struct.refiner(value, ctx), }); } /** * Ensure that a value is an object with keys and values of specific types, but * without ensuring any specific shape of properties. * * Like TypeScript's `Record` utility. */ function record(Key, Value) { return new Struct({ type: 'record', schema: null, *entries(value) { if (isObject(value)) { for (const k in value) { const v = value[k]; yield [k, k, Key]; yield [k, v, Value]; } } }, validator(value) { return (isNonArrayObject(value) || `Expected an object, but received: ${print(value)}`); }, coercer(value) { return isNonArrayObject(value) ? { ...value } : value; }, }); } /** * Ensure that a value is a `RegExp`. * * Note: this does not test the value against the regular expression! For that * you need to use the `pattern()` refinement. */ function regexp() { return define('regexp', (value) => { return value instanceof RegExp; }); } function set(Element) { return new Struct({ type: 'set', schema: null, *entries(value) { if (Element && value instanceof Set) { for (const v of value) { yield [v, v, Element]; } } }, coercer(value) { return value instanceof Set ? new Set(value) : value; }, validator(value) { return (value instanceof Set || `Expected a \`Set\` object, but received: ${print(value)}`); }, }); } /** * Ensure that a value is a string. */ function string() { return define('string', (value) => { return (typeof value === 'string' || `Expected a string, but received: ${print(value)}`); }); } /** * Ensure that a value is a tuple of a specific length, and that each of its * elements is of a specific type. */ function tuple(Structs) { const Never = never(); return new Struct({ type: 'tuple', schema: null, *entries(value) { if (Array.isArray(value)) { const length = Math.max(Structs.length, value.length); for (let i = 0; i < length; i++) { yield [i, value[i], Structs[i] || Never]; } } }, validator(value) { return (Array.isArray(value) || `Expected an array, but received: ${print(value)}`); }, coercer(value) { return Array.isArray(value) ? value.slice() : value; }, }); } /** * Ensure that a value has a set of known properties of specific types. * * Note: Unrecognized properties are allowed and untouched. This is similar to * how TypeScript's structural typing works. */ function type(schema) { const keys = Object.keys(schema); return new Struct({ type: 'type', schema, *entries(value) { if (isObject(value)) { for (const k of keys) { yield [k, value[k], schema[k]]; } } }, validator(value) { return (isNonArrayObject(value) || `Expected an object, but received: ${print(value)}`); }, coercer(value) { return isNonArrayObject(value) ? { ...value } : value; }, }); } /** * Ensure that a value matches one of a set of types. */ function union(Structs) { const description = Structs.map((s) => s.type).join(' | '); return new Struct({ type: 'union', schema: null, coercer(value, ctx) { for (const S of Structs) { const [error, coerced] = S.validate(value, { coerce: true, mask: ctx.mask, }); if (!error) { return coerced; } } return value; }, validator(value, ctx) { const failures = []; for (const S of Structs) { const [...tuples] = run(value, S, ctx); const [first] = tuples; if (!first[0]) { return []; } else { for (const [failure] of tuples) { if (failure) { failures.push(failure); } } } } return [ `Expected the value to satisfy a union of \`${description}\`, but received: ${print(value)}`, ...failures, ]; }, }); } /** * Ensure that any value passes validation, without widening its type to `any`. */ function unknown() { return define('unknown', () => true); } /** * Augment a `Struct` to add an additional coercion step to its input. * * This allows you to transform input data before validating it, to increase the * likelihood that it passes validation—for example for default values, parsing * different formats, etc. * * Note: You must use `create(value, Struct)` on the value to have the coercion * take effect! Using simply `assert()` or `is()` will not use coercion. */ function coerce(struct, condition, coercer) { return new Struct({ ...struct, coercer: (value, ctx) => { return is(value, condition) ? struct.coercer(coercer(value, ctx), ctx) : struct.coercer(value, ctx); }, }); } /** * Augment a struct to replace `undefined` values with a default. * * Note: You must use `create(value, Struct)` on the value to have the coercion * take effect! Using simply `assert()` or `is()` will not use coercion. */ function defaulted(struct, fallback, options = {}) { return coerce(struct, unknown(), (x) => { const f = typeof fallback === 'function' ? fallback() : fallback; if (x === undefined) { return f; } if (!options.strict && isPlainObject(x) && isPlainObject(f)) { const ret = { ...x }; let changed = false; for (const key in f) { if (ret[key] === undefined) { ret[key] = f[key]; changed = true; } } if (changed) { return ret; } } return x; }); } /** * Augment a struct to trim string inputs. * * Note: You must use `create(value, Struct)` on the value to have the coercion * take effect! Using simply `assert()` or `is()` will not use coercion. */ function trimmed(struct) { return coerce(struct, string(), (x) => x.trim()); } /** * Ensure that a string, array, map, or set is empty. */ function empty(struct) { return refine(struct, 'empty', (value) => { const size = getSize(value); return (size === 0 || `Expected an empty ${struct.type} but received one with a size of \`${size}\``); }); } function getSize(value) { if (value instanceof Map || value instanceof Set) { return value.size; } else { return value.length; } } /** * Ensure that a number or date is below a threshold. */ function max(struct, threshold, options = {}) { const { exclusive } = options; return refine(struct, 'max', (value) => { return exclusive ? value < threshold : value <= threshold || `Expected a ${struct.type} less than ${exclusive ? '' : 'or equal to '}${threshold} but received \`${value}\``; }); } /** * Ensure that a number or date is above a threshold. */ function min(struct, threshold, options = {}) { const { exclusive } = options; return refine(struct, 'min', (value) => { return exclusive ? value > threshold : value >= threshold || `Expected a ${struct.type} greater than ${exclusive ? '' : 'or equal to '}${threshold} but received \`${value}\``; }); } /** * Ensure that a string, array, map or set is not empty. */ function nonempty(struct) { return refine(struct, 'nonempty', (value) => { const size = getSize(value); return (size > 0 || `Expected a nonempty ${struct.type} but received an empty one`); }); } /** * Ensure that a string matches a regular expression. */ function pattern(struct, regexp) { return refine(struct, 'pattern', (value) => { return (regexp.test(value) || `Expected a ${struct.type} matching \`/${regexp.source}/\` but received "${value}"`); }); } /** * Ensure that a string, array, number, date, map, or set has a size (or length, or time) between `min` and `max`. */ function size(struct, min, max = min) { const expected = `Expected a ${struct.type}`; const of = min === max ? `of \`${min}\`` : `between \`${min}\` and \`${max}\``; return refine(struct, 'size', (value) => { if (typeof value === 'number' || value instanceof Date) { return ((min <= value && value <= max) || `${expected} ${of} but received \`${value}\``); } else if (value instanceof Map || value instanceof Set) { const { size } = value; return ((min <= size && size <= max) || `${expected} with a size ${of} but received one with a size of \`${size}\``); } else { const { length } = value; return ((min <= length && length <= max) || `${expected} with a length ${of} but received one with a length of \`${length}\``); } }); } /** * Augment a `Struct` to add an additional refinement to the validation. * * The refiner function is guaranteed to receive a value of the struct's type, * because the struct's existing validation will already have passed. This * allows you to layer additional validation on top of existing structs. */ function refine(struct, name, refiner) { return new Struct({ ...struct, *refiner(value, ctx) { yield* struct.refiner(value, ctx); const result = refiner(value, ctx); const failures = toFailures(result, ctx, struct, value); for (const failure of failures) { yield { ...failure, refinement: name }; } }, }); } exports.Struct = Struct; exports.StructError = StructError; exports.any = any; exports.array = array; exports.assert = assert; exports.assign = assign; exports.bigint = bigint; exports.boolean = boolean; exports.coerce = coerce; exports.create = create; exports.date = date; exports.defaulted = defaulted; exports.define = define; exports.deprecated = deprecated; exports.dynamic = dynamic; exports.empty = empty; exports.enums = enums; exports.func = func; exports.instance = instance; exports.integer = integer; exports.intersection = intersection; exports.is = is; exports.lazy = lazy; exports.literal = literal; exports.map = map; exports.mask = mask; exports.max = max; exports.min = min; exports.never = never; exports.nonempty = nonempty; exports.nullable = nullable; exports.number = number; exports.object = object; exports.omit = omit; exports.optional = optional; exports.partial = partial; exports.pattern = pattern; exports.pick = pick; exports.record = record; exports.refine = refine; exports.regexp = regexp; exports.set = set; exports.size = size; exports.string = string; exports.struct = struct; exports.trimmed = trimmed; exports.tuple = tuple; exports.type = type; exports.union = union; exports.unknown = unknown; exports.validate = validate; })); //# sourceMappingURL=index.cjs.map