yup
Version:
Dead simple Object schema validation
1,589 lines (1,540 loc) • 76.2 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var propertyExpr = require('property-expr');
var tinyCase = require('tiny-case');
var toposort = require('toposort');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var toposort__default = /*#__PURE__*/_interopDefaultLegacy(toposort);
const toString = Object.prototype.toString;
const errorToString = Error.prototype.toString;
const regExpToString = RegExp.prototype.toString;
const symbolToString = typeof Symbol !== 'undefined' ? Symbol.prototype.toString : () => '';
const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
function printNumber(val) {
if (val != +val) return 'NaN';
const isNegativeZero = val === 0 && 1 / val < 0;
return isNegativeZero ? '-0' : '' + val;
}
function printSimpleValue(val, quoteStrings = false) {
if (val == null || val === true || val === false) return '' + val;
const typeOf = typeof val;
if (typeOf === 'number') return printNumber(val);
if (typeOf === 'string') return quoteStrings ? `"${val}"` : val;
if (typeOf === 'function') return '[Function ' + (val.name || 'anonymous') + ']';
if (typeOf === 'symbol') return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
const tag = toString.call(val).slice(8, -1);
if (tag === 'Date') return isNaN(val.getTime()) ? '' + val : val.toISOString(val);
if (tag === 'Error' || val instanceof Error) return '[' + errorToString.call(val) + ']';
if (tag === 'RegExp') return regExpToString.call(val);
return null;
}
function printValue(value, quoteStrings) {
let result = printSimpleValue(value, quoteStrings);
if (result !== null) return result;
return JSON.stringify(value, function (key, value) {
let result = printSimpleValue(this[key], quoteStrings);
if (result !== null) return result;
return value;
}, 2);
}
function toArray(value) {
return value == null ? [] : [].concat(value);
}
let _Symbol$toStringTag, _Symbol$hasInstance, _Symbol$toStringTag2;
let strReg = /\$\{\s*(\w+)\s*\}/g;
_Symbol$toStringTag = Symbol.toStringTag;
class ValidationErrorNoStack {
constructor(errorOrErrors, value, field, type) {
this.name = void 0;
this.message = void 0;
this.value = void 0;
this.path = void 0;
this.type = void 0;
this.params = void 0;
this.errors = void 0;
this.inner = void 0;
this[_Symbol$toStringTag] = 'Error';
this.name = 'ValidationError';
this.value = value;
this.path = field;
this.type = type;
this.errors = [];
this.inner = [];
toArray(errorOrErrors).forEach(err => {
if (ValidationError.isError(err)) {
this.errors.push(...err.errors);
const innerErrors = err.inner.length ? err.inner : [err];
this.inner.push(...innerErrors);
} else {
this.errors.push(err);
}
});
this.message = this.errors.length > 1 ? `${this.errors.length} errors occurred` : this.errors[0];
}
}
_Symbol$hasInstance = Symbol.hasInstance;
_Symbol$toStringTag2 = Symbol.toStringTag;
class ValidationError extends Error {
static formatError(message, params) {
// Attempt to make the path more friendly for error message interpolation.
const path = params.label || params.path || 'this';
// Store the original path under `originalPath` so it isn't lost to custom
// message functions; e.g., ones provided in `setLocale()` calls.
params = Object.assign({}, params, {
path,
originalPath: params.path
});
if (typeof message === 'string') return message.replace(strReg, (_, key) => printValue(params[key]));
if (typeof message === 'function') return message(params);
return message;
}
static isError(err) {
return err && err.name === 'ValidationError';
}
constructor(errorOrErrors, value, field, type, disableStack) {
const errorNoStack = new ValidationErrorNoStack(errorOrErrors, value, field, type);
if (disableStack) {
return errorNoStack;
}
super();
this.value = void 0;
this.path = void 0;
this.type = void 0;
this.params = void 0;
this.errors = [];
this.inner = [];
this[_Symbol$toStringTag2] = 'Error';
this.name = errorNoStack.name;
this.message = errorNoStack.message;
this.type = errorNoStack.type;
this.value = errorNoStack.value;
this.path = errorNoStack.path;
this.errors = errorNoStack.errors;
this.inner = errorNoStack.inner;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ValidationError);
}
}
static [_Symbol$hasInstance](inst) {
return ValidationErrorNoStack[Symbol.hasInstance](inst) || super[Symbol.hasInstance](inst);
}
}
let mixed = {
default: '${path} is invalid',
required: '${path} is a required field',
defined: '${path} must be defined',
notNull: '${path} cannot be null',
oneOf: '${path} must be one of the following values: ${values}',
notOneOf: '${path} must not be one of the following values: ${values}',
notType: ({
path,
type,
value,
originalValue
}) => {
const castMsg = originalValue != null && originalValue !== value ? ` (cast from the value \`${printValue(originalValue, true)}\`).` : '.';
return type !== 'mixed' ? `${path} must be a \`${type}\` type, ` + `but the final value was: \`${printValue(value, true)}\`` + castMsg : `${path} must match the configured type. ` + `The validated value was: \`${printValue(value, true)}\`` + castMsg;
}
};
let string = {
length: '${path} must be exactly ${length} characters',
min: '${path} must be at least ${min} characters',
max: '${path} must be at most ${max} characters',
matches: '${path} must match the following: "${regex}"',
email: '${path} must be a valid email',
url: '${path} must be a valid URL',
uuid: '${path} must be a valid UUID',
datetime: '${path} must be a valid ISO date-time',
datetime_precision: '${path} must be a valid ISO date-time with a sub-second precision of exactly ${precision} digits',
datetime_offset: '${path} must be a valid ISO date-time with UTC "Z" timezone',
trim: '${path} must be a trimmed string',
lowercase: '${path} must be a lowercase string',
uppercase: '${path} must be a upper case string'
};
let number = {
min: '${path} must be greater than or equal to ${min}',
max: '${path} must be less than or equal to ${max}',
lessThan: '${path} must be less than ${less}',
moreThan: '${path} must be greater than ${more}',
positive: '${path} must be a positive number',
negative: '${path} must be a negative number',
integer: '${path} must be an integer'
};
let date = {
min: '${path} field must be later than ${min}',
max: '${path} field must be at earlier than ${max}'
};
let boolean = {
isValue: '${path} field must be ${value}'
};
let object = {
noUnknown: '${path} field has unspecified keys: ${unknown}',
exact: '${path} object contains unknown properties: ${properties}'
};
let array = {
min: '${path} field must have at least ${min} items',
max: '${path} field must have less than or equal to ${max} items',
length: '${path} must have ${length} items'
};
let tuple = {
notType: params => {
const {
path,
value,
spec
} = params;
const typeLen = spec.types.length;
if (Array.isArray(value)) {
if (value.length < typeLen) return `${path} tuple value has too few items, expected a length of ${typeLen} but got ${value.length} for value: \`${printValue(value, true)}\``;
if (value.length > typeLen) return `${path} tuple value has too many items, expected a length of ${typeLen} but got ${value.length} for value: \`${printValue(value, true)}\``;
}
return ValidationError.formatError(mixed.notType, params);
}
};
var locale = Object.assign(Object.create(null), {
mixed,
string,
number,
date,
object,
array,
boolean,
tuple
});
const isSchema = obj => obj && obj.__isYupSchema__;
class Condition {
static fromOptions(refs, config) {
if (!config.then && !config.otherwise) throw new TypeError('either `then:` or `otherwise:` is required for `when()` conditions');
let {
is,
then,
otherwise
} = config;
let check = typeof is === 'function' ? is : (...values) => values.every(value => value === is);
return new Condition(refs, (values, schema) => {
var _branch;
let branch = check(...values) ? then : otherwise;
return (_branch = branch == null ? void 0 : branch(schema)) != null ? _branch : schema;
});
}
constructor(refs, builder) {
this.fn = void 0;
this.refs = refs;
this.refs = refs;
this.fn = builder;
}
resolve(base, options) {
let values = this.refs.map(ref =>
// TODO: ? operator here?
ref.getValue(options == null ? void 0 : options.value, options == null ? void 0 : options.parent, options == null ? void 0 : options.context));
let schema = this.fn(values, base, options);
if (schema === undefined ||
// @ts-ignore this can be base
schema === base) {
return base;
}
if (!isSchema(schema)) throw new TypeError('conditions must return a schema object');
return schema.resolve(options);
}
}
const prefixes = {
context: '$',
value: '.'
};
function create$9(key, options) {
return new Reference(key, options);
}
class Reference {
constructor(key, options = {}) {
this.key = void 0;
this.isContext = void 0;
this.isValue = void 0;
this.isSibling = void 0;
this.path = void 0;
this.getter = void 0;
this.map = void 0;
if (typeof key !== 'string') throw new TypeError('ref must be a string, got: ' + key);
this.key = key.trim();
if (key === '') throw new TypeError('ref must be a non-empty string');
this.isContext = this.key[0] === prefixes.context;
this.isValue = this.key[0] === prefixes.value;
this.isSibling = !this.isContext && !this.isValue;
let prefix = this.isContext ? prefixes.context : this.isValue ? prefixes.value : '';
this.path = this.key.slice(prefix.length);
this.getter = this.path && propertyExpr.getter(this.path, true);
this.map = options.map;
}
getValue(value, parent, context) {
let result = this.isContext ? context : this.isValue ? value : parent;
if (this.getter) result = this.getter(result || {});
if (this.map) result = this.map(result);
return result;
}
/**
*
* @param {*} value
* @param {Object} options
* @param {Object=} options.context
* @param {Object=} options.parent
*/
cast(value, options) {
return this.getValue(value, options == null ? void 0 : options.parent, options == null ? void 0 : options.context);
}
resolve() {
return this;
}
describe() {
return {
type: 'ref',
key: this.key
};
}
toString() {
return `Ref(${this.key})`;
}
static isRef(value) {
return value && value.__isYupRef;
}
}
// @ts-ignore
Reference.prototype.__isYupRef = true;
const isAbsent = value => value == null;
function createValidation(config) {
function validate({
value,
path = '',
options,
originalValue,
schema
}, panic, next) {
const {
name,
test,
params,
message,
skipAbsent
} = config;
let {
parent,
context,
abortEarly = schema.spec.abortEarly,
disableStackTrace = schema.spec.disableStackTrace
} = options;
function resolve(item) {
return Reference.isRef(item) ? item.getValue(value, parent, context) : item;
}
function createError(overrides = {}) {
const nextParams = Object.assign({
value,
originalValue,
label: schema.spec.label,
path: overrides.path || path,
spec: schema.spec,
disableStackTrace: overrides.disableStackTrace || disableStackTrace
}, params, overrides.params);
for (const key of Object.keys(nextParams)) nextParams[key] = resolve(nextParams[key]);
const error = new ValidationError(ValidationError.formatError(overrides.message || message, nextParams), value, nextParams.path, overrides.type || name, nextParams.disableStackTrace);
error.params = nextParams;
return error;
}
const invalid = abortEarly ? panic : next;
let ctx = {
path,
parent,
type: name,
from: options.from,
createError,
resolve,
options,
originalValue,
schema
};
const handleResult = validOrError => {
if (ValidationError.isError(validOrError)) invalid(validOrError);else if (!validOrError) invalid(createError());else next(null);
};
const handleError = err => {
if (ValidationError.isError(err)) invalid(err);else panic(err);
};
const shouldSkip = skipAbsent && isAbsent(value);
if (shouldSkip) {
return handleResult(true);
}
let result;
try {
var _result;
result = test.call(ctx, value, ctx);
if (typeof ((_result = result) == null ? void 0 : _result.then) === 'function') {
if (options.sync) {
throw new Error(`Validation test of type: "${ctx.type}" returned a Promise during a synchronous validate. ` + `This test will finish after the validate call has returned`);
}
return Promise.resolve(result).then(handleResult, handleError);
}
} catch (err) {
handleError(err);
return;
}
handleResult(result);
}
validate.OPTIONS = config;
return validate;
}
function getIn(schema, path, value, context = value) {
let parent, lastPart, lastPartDebug;
// root path: ''
if (!path) return {
parent,
parentPath: path,
schema
};
propertyExpr.forEach(path, (_part, isBracket, isArray) => {
let part = isBracket ? _part.slice(1, _part.length - 1) : _part;
schema = schema.resolve({
context,
parent,
value
});
let isTuple = schema.type === 'tuple';
let idx = isArray ? parseInt(part, 10) : 0;
if (schema.innerType || isTuple) {
if (isTuple && !isArray) throw new Error(`Yup.reach cannot implicitly index into a tuple type. the path part "${lastPartDebug}" must contain an index to the tuple element, e.g. "${lastPartDebug}[0]"`);
if (value && idx >= value.length) {
throw new Error(`Yup.reach cannot resolve an array item at index: ${_part}, in the path: ${path}. ` + `because there is no value at that index. `);
}
parent = value;
value = value && value[idx];
schema = isTuple ? schema.spec.types[idx] : schema.innerType;
}
// sometimes the array index part of a path doesn't exist: "nested.arr.child"
// in these cases the current part is the next schema and should be processed
// in this iteration. For cases where the index signature is included this
// check will fail and we'll handle the `child` part on the next iteration like normal
if (!isArray) {
if (!schema.fields || !schema.fields[part]) throw new Error(`The schema does not contain the path: ${path}. ` + `(failed at: ${lastPartDebug} which is a type: "${schema.type}")`);
parent = value;
value = value && value[part];
schema = schema.fields[part];
}
lastPart = part;
lastPartDebug = isBracket ? '[' + _part + ']' : '.' + _part;
});
return {
schema,
parent,
parentPath: lastPart
};
}
function reach(obj, path, value, context) {
return getIn(obj, path, value, context).schema;
}
class ReferenceSet extends Set {
describe() {
const description = [];
for (const item of this.values()) {
description.push(Reference.isRef(item) ? item.describe() : item);
}
return description;
}
resolveAll(resolve) {
let result = [];
for (const item of this.values()) {
result.push(resolve(item));
}
return result;
}
clone() {
return new ReferenceSet(this.values());
}
merge(newItems, removeItems) {
const next = this.clone();
newItems.forEach(value => next.add(value));
removeItems.forEach(value => next.delete(value));
return next;
}
}
// tweaked from https://github.com/Kelin2025/nanoclone/blob/0abeb7635bda9b68ef2277093f76dbe3bf3948e1/src/index.js
function clone(src, seen = new Map()) {
if (isSchema(src) || !src || typeof src !== 'object') return src;
if (seen.has(src)) return seen.get(src);
let copy;
if (src instanceof Date) {
// Date
copy = new Date(src.getTime());
seen.set(src, copy);
} else if (src instanceof RegExp) {
// RegExp
copy = new RegExp(src);
seen.set(src, copy);
} else if (Array.isArray(src)) {
// Array
copy = new Array(src.length);
seen.set(src, copy);
for (let i = 0; i < src.length; i++) copy[i] = clone(src[i], seen);
} else if (src instanceof Map) {
// Map
copy = new Map();
seen.set(src, copy);
for (const [k, v] of src.entries()) copy.set(k, clone(v, seen));
} else if (src instanceof Set) {
// Set
copy = new Set();
seen.set(src, copy);
for (const v of src) copy.add(clone(v, seen));
} else if (src instanceof Object) {
// Object
copy = {};
seen.set(src, copy);
for (const [k, v] of Object.entries(src)) copy[k] = clone(v, seen);
} else {
throw Error(`Unable to clone ${src}`);
}
return copy;
}
// If `CustomSchemaMeta` isn't extended with any keys, we'll fall back to a
// loose Record definition allowing free form usage.
class Schema {
constructor(options) {
this.type = void 0;
this.deps = [];
this.tests = void 0;
this.transforms = void 0;
this.conditions = [];
this._mutate = void 0;
this.internalTests = {};
this._whitelist = new ReferenceSet();
this._blacklist = new ReferenceSet();
this.exclusiveTests = Object.create(null);
this._typeCheck = void 0;
this.spec = void 0;
this.tests = [];
this.transforms = [];
this.withMutation(() => {
this.typeError(mixed.notType);
});
this.type = options.type;
this._typeCheck = options.check;
this.spec = Object.assign({
strip: false,
strict: false,
abortEarly: true,
recursive: true,
disableStackTrace: false,
nullable: false,
optional: true,
coerce: true
}, options == null ? void 0 : options.spec);
this.withMutation(s => {
s.nonNullable();
});
}
// TODO: remove
get _type() {
return this.type;
}
clone(spec) {
if (this._mutate) {
if (spec) Object.assign(this.spec, spec);
return this;
}
// if the nested value is a schema we can skip cloning, since
// they are already immutable
const next = Object.create(Object.getPrototypeOf(this));
// @ts-expect-error this is readonly
next.type = this.type;
next._typeCheck = this._typeCheck;
next._whitelist = this._whitelist.clone();
next._blacklist = this._blacklist.clone();
next.internalTests = Object.assign({}, this.internalTests);
next.exclusiveTests = Object.assign({}, this.exclusiveTests);
// @ts-expect-error this is readonly
next.deps = [...this.deps];
next.conditions = [...this.conditions];
next.tests = [...this.tests];
next.transforms = [...this.transforms];
next.spec = clone(Object.assign({}, this.spec, spec));
return next;
}
label(label) {
let next = this.clone();
next.spec.label = label;
return next;
}
meta(...args) {
if (args.length === 0) return this.spec.meta;
let next = this.clone();
next.spec.meta = Object.assign(next.spec.meta || {}, args[0]);
return next;
}
withMutation(fn) {
let before = this._mutate;
this._mutate = true;
let result = fn(this);
this._mutate = before;
return result;
}
concat(schema) {
if (!schema || schema === this) return this;
if (schema.type !== this.type && this.type !== 'mixed') throw new TypeError(`You cannot \`concat()\` schema's of different types: ${this.type} and ${schema.type}`);
let base = this;
let combined = schema.clone();
const mergedSpec = Object.assign({}, base.spec, combined.spec);
combined.spec = mergedSpec;
combined.internalTests = Object.assign({}, base.internalTests, combined.internalTests);
// manually merge the blacklist/whitelist (the other `schema` takes
// precedence in case of conflicts)
combined._whitelist = base._whitelist.merge(schema._whitelist, schema._blacklist);
combined._blacklist = base._blacklist.merge(schema._blacklist, schema._whitelist);
// start with the current tests
combined.tests = base.tests;
combined.exclusiveTests = base.exclusiveTests;
// manually add the new tests to ensure
// the deduping logic is consistent
combined.withMutation(next => {
schema.tests.forEach(fn => {
next.test(fn.OPTIONS);
});
});
combined.transforms = [...base.transforms, ...combined.transforms];
return combined;
}
isType(v) {
if (v == null) {
if (this.spec.nullable && v === null) return true;
if (this.spec.optional && v === undefined) return true;
return false;
}
return this._typeCheck(v);
}
resolve(options) {
let schema = this;
if (schema.conditions.length) {
let conditions = schema.conditions;
schema = schema.clone();
schema.conditions = [];
schema = conditions.reduce((prevSchema, condition) => condition.resolve(prevSchema, options), schema);
schema = schema.resolve(options);
}
return schema;
}
resolveOptions(options) {
var _options$strict, _options$abortEarly, _options$recursive, _options$disableStack;
return Object.assign({}, options, {
from: options.from || [],
strict: (_options$strict = options.strict) != null ? _options$strict : this.spec.strict,
abortEarly: (_options$abortEarly = options.abortEarly) != null ? _options$abortEarly : this.spec.abortEarly,
recursive: (_options$recursive = options.recursive) != null ? _options$recursive : this.spec.recursive,
disableStackTrace: (_options$disableStack = options.disableStackTrace) != null ? _options$disableStack : this.spec.disableStackTrace
});
}
/**
* Run the configured transform pipeline over an input value.
*/
cast(value, options = {}) {
let resolvedSchema = this.resolve(Object.assign({
value
}, options));
let allowOptionality = options.assert === 'ignore-optionality';
let result = resolvedSchema._cast(value, options);
if (options.assert !== false && !resolvedSchema.isType(result)) {
if (allowOptionality && isAbsent(result)) {
return result;
}
let formattedValue = printValue(value);
let formattedResult = printValue(result);
throw new TypeError(`The value of ${options.path || 'field'} could not be cast to a value ` + `that satisfies the schema type: "${resolvedSchema.type}". \n\n` + `attempted value: ${formattedValue} \n` + (formattedResult !== formattedValue ? `result of cast: ${formattedResult}` : ''));
}
return result;
}
_cast(rawValue, options) {
let value = rawValue === undefined ? rawValue : this.transforms.reduce((prevValue, fn) => fn.call(this, prevValue, rawValue, this), rawValue);
if (value === undefined) {
value = this.getDefault(options);
}
return value;
}
_validate(_value, options = {}, panic, next) {
let {
path,
originalValue = _value,
strict = this.spec.strict
} = options;
let value = _value;
if (!strict) {
value = this._cast(value, Object.assign({
assert: false
}, options));
}
let initialTests = [];
for (let test of Object.values(this.internalTests)) {
if (test) initialTests.push(test);
}
this.runTests({
path,
value,
originalValue,
options,
tests: initialTests
}, panic, initialErrors => {
// even if we aren't ending early we can't proceed further if the types aren't correct
if (initialErrors.length) {
return next(initialErrors, value);
}
this.runTests({
path,
value,
originalValue,
options,
tests: this.tests
}, panic, next);
});
}
/**
* Executes a set of validations, either schema, produced Tests or a nested
* schema validate result.
*/
runTests(runOptions, panic, next) {
let fired = false;
let {
tests,
value,
originalValue,
path,
options
} = runOptions;
let panicOnce = arg => {
if (fired) return;
fired = true;
panic(arg, value);
};
let nextOnce = arg => {
if (fired) return;
fired = true;
next(arg, value);
};
let count = tests.length;
let nestedErrors = [];
if (!count) return nextOnce([]);
let args = {
value,
originalValue,
path,
options,
schema: this
};
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
test(args, panicOnce, function finishTestRun(err) {
if (err) {
Array.isArray(err) ? nestedErrors.push(...err) : nestedErrors.push(err);
}
if (--count <= 0) {
nextOnce(nestedErrors);
}
});
}
}
asNestedTest({
key,
index,
parent,
parentPath,
originalParent,
options
}) {
const k = key != null ? key : index;
if (k == null) {
throw TypeError('Must include `key` or `index` for nested validations');
}
const isIndex = typeof k === 'number';
let value = parent[k];
const testOptions = Object.assign({}, options, {
// Nested validations fields are always strict:
// 1. parent isn't strict so the casting will also have cast inner values
// 2. parent is strict in which case the nested values weren't cast either
strict: true,
parent,
value,
originalValue: originalParent[k],
// FIXME: tests depend on `index` being passed around deeply,
// we should not let the options.key/index bleed through
key: undefined,
// index: undefined,
[isIndex ? 'index' : 'key']: k,
path: isIndex || k.includes('.') ? `${parentPath || ''}[${isIndex ? k : `"${k}"`}]` : (parentPath ? `${parentPath}.` : '') + key
});
return (_, panic, next) => this.resolve(testOptions)._validate(value, testOptions, panic, next);
}
validate(value, options) {
var _options$disableStack2;
let schema = this.resolve(Object.assign({}, options, {
value
}));
let disableStackTrace = (_options$disableStack2 = options == null ? void 0 : options.disableStackTrace) != null ? _options$disableStack2 : schema.spec.disableStackTrace;
return new Promise((resolve, reject) => schema._validate(value, options, (error, parsed) => {
if (ValidationError.isError(error)) error.value = parsed;
reject(error);
}, (errors, validated) => {
if (errors.length) reject(new ValidationError(errors, validated, undefined, undefined, disableStackTrace));else resolve(validated);
}));
}
validateSync(value, options) {
var _options$disableStack3;
let schema = this.resolve(Object.assign({}, options, {
value
}));
let result;
let disableStackTrace = (_options$disableStack3 = options == null ? void 0 : options.disableStackTrace) != null ? _options$disableStack3 : schema.spec.disableStackTrace;
schema._validate(value, Object.assign({}, options, {
sync: true
}), (error, parsed) => {
if (ValidationError.isError(error)) error.value = parsed;
throw error;
}, (errors, validated) => {
if (errors.length) throw new ValidationError(errors, value, undefined, undefined, disableStackTrace);
result = validated;
});
return result;
}
isValid(value, options) {
return this.validate(value, options).then(() => true, err => {
if (ValidationError.isError(err)) return false;
throw err;
});
}
isValidSync(value, options) {
try {
this.validateSync(value, options);
return true;
} catch (err) {
if (ValidationError.isError(err)) return false;
throw err;
}
}
_getDefault(options) {
let defaultValue = this.spec.default;
if (defaultValue == null) {
return defaultValue;
}
return typeof defaultValue === 'function' ? defaultValue.call(this, options) : clone(defaultValue);
}
getDefault(options
// If schema is defaulted we know it's at least not undefined
) {
let schema = this.resolve(options || {});
return schema._getDefault(options);
}
default(def) {
if (arguments.length === 0) {
return this._getDefault();
}
let next = this.clone({
default: def
});
return next;
}
strict(isStrict = true) {
return this.clone({
strict: isStrict
});
}
nullability(nullable, message) {
const next = this.clone({
nullable
});
next.internalTests.nullable = createValidation({
message,
name: 'nullable',
test(value) {
return value === null ? this.schema.spec.nullable : true;
}
});
return next;
}
optionality(optional, message) {
const next = this.clone({
optional
});
next.internalTests.optionality = createValidation({
message,
name: 'optionality',
test(value) {
return value === undefined ? this.schema.spec.optional : true;
}
});
return next;
}
optional() {
return this.optionality(true);
}
defined(message = mixed.defined) {
return this.optionality(false, message);
}
nullable() {
return this.nullability(true);
}
nonNullable(message = mixed.notNull) {
return this.nullability(false, message);
}
required(message = mixed.required) {
return this.clone().withMutation(next => next.nonNullable(message).defined(message));
}
notRequired() {
return this.clone().withMutation(next => next.nullable().optional());
}
transform(fn) {
let next = this.clone();
next.transforms.push(fn);
return next;
}
/**
* Adds a test function to the schema's queue of tests.
* tests can be exclusive or non-exclusive.
*
* - exclusive tests, will replace any existing tests of the same name.
* - non-exclusive: can be stacked
*
* If a non-exclusive test is added to a schema with an exclusive test of the same name
* the exclusive test is removed and further tests of the same name will be stacked.
*
* If an exclusive test is added to a schema with non-exclusive tests of the same name
* the previous tests are removed and further tests of the same name will replace each other.
*/
test(...args) {
let opts;
if (args.length === 1) {
if (typeof args[0] === 'function') {
opts = {
test: args[0]
};
} else {
opts = args[0];
}
} else if (args.length === 2) {
opts = {
name: args[0],
test: args[1]
};
} else {
opts = {
name: args[0],
message: args[1],
test: args[2]
};
}
if (opts.message === undefined) opts.message = mixed.default;
if (typeof opts.test !== 'function') throw new TypeError('`test` is a required parameters');
let next = this.clone();
let validate = createValidation(opts);
let isExclusive = opts.exclusive || opts.name && next.exclusiveTests[opts.name] === true;
if (opts.exclusive) {
if (!opts.name) throw new TypeError('Exclusive tests must provide a unique `name` identifying the test');
}
if (opts.name) next.exclusiveTests[opts.name] = !!opts.exclusive;
next.tests = next.tests.filter(fn => {
if (fn.OPTIONS.name === opts.name) {
if (isExclusive) return false;
if (fn.OPTIONS.test === validate.OPTIONS.test) return false;
}
return true;
});
next.tests.push(validate);
return next;
}
when(keys, options) {
if (!Array.isArray(keys) && typeof keys !== 'string') {
options = keys;
keys = '.';
}
let next = this.clone();
let deps = toArray(keys).map(key => new Reference(key));
deps.forEach(dep => {
// @ts-ignore readonly array
if (dep.isSibling) next.deps.push(dep.key);
});
next.conditions.push(typeof options === 'function' ? new Condition(deps, options) : Condition.fromOptions(deps, options));
return next;
}
typeError(message) {
let next = this.clone();
next.internalTests.typeError = createValidation({
message,
name: 'typeError',
skipAbsent: true,
test(value) {
if (!this.schema._typeCheck(value)) return this.createError({
params: {
type: this.schema.type
}
});
return true;
}
});
return next;
}
oneOf(enums, message = mixed.oneOf) {
let next = this.clone();
enums.forEach(val => {
next._whitelist.add(val);
next._blacklist.delete(val);
});
next.internalTests.whiteList = createValidation({
message,
name: 'oneOf',
skipAbsent: true,
test(value) {
let valids = this.schema._whitelist;
let resolved = valids.resolveAll(this.resolve);
return resolved.includes(value) ? true : this.createError({
params: {
values: Array.from(valids).join(', '),
resolved
}
});
}
});
return next;
}
notOneOf(enums, message = mixed.notOneOf) {
let next = this.clone();
enums.forEach(val => {
next._blacklist.add(val);
next._whitelist.delete(val);
});
next.internalTests.blacklist = createValidation({
message,
name: 'notOneOf',
test(value) {
let invalids = this.schema._blacklist;
let resolved = invalids.resolveAll(this.resolve);
if (resolved.includes(value)) return this.createError({
params: {
values: Array.from(invalids).join(', '),
resolved
}
});
return true;
}
});
return next;
}
strip(strip = true) {
let next = this.clone();
next.spec.strip = strip;
return next;
}
/**
* Return a serialized description of the schema including validations, flags, types etc.
*
* @param options Provide any needed context for resolving runtime schema alterations (lazy, when conditions, etc).
*/
describe(options) {
const next = (options ? this.resolve(options) : this).clone();
const {
label,
meta,
optional,
nullable
} = next.spec;
const description = {
meta,
label,
optional,
nullable,
default: next.getDefault(options),
type: next.type,
oneOf: next._whitelist.describe(),
notOneOf: next._blacklist.describe(),
tests: next.tests.map(fn => ({
name: fn.OPTIONS.name,
params: fn.OPTIONS.params
})).filter((n, idx, list) => list.findIndex(c => c.name === n.name) === idx)
};
return description;
}
}
// @ts-expect-error
Schema.prototype.__isYupSchema__ = true;
for (const method of ['validate', 'validateSync']) Schema.prototype[`${method}At`] = function (path, value, options = {}) {
const {
parent,
parentPath,
schema
} = getIn(this, path, value, options.context);
return schema[method](parent && parent[parentPath], Object.assign({}, options, {
parent,
path
}));
};
for (const alias of ['equals', 'is']) Schema.prototype[alias] = Schema.prototype.oneOf;
for (const alias of ['not', 'nope']) Schema.prototype[alias] = Schema.prototype.notOneOf;
const returnsTrue = () => true;
function create$8(spec) {
return new MixedSchema(spec);
}
class MixedSchema extends Schema {
constructor(spec) {
super(typeof spec === 'function' ? {
type: 'mixed',
check: spec
} : Object.assign({
type: 'mixed',
check: returnsTrue
}, spec));
}
}
create$8.prototype = MixedSchema.prototype;
function create$7() {
return new BooleanSchema();
}
class BooleanSchema extends Schema {
constructor() {
super({
type: 'boolean',
check(v) {
if (v instanceof Boolean) v = v.valueOf();
return typeof v === 'boolean';
}
});
this.withMutation(() => {
this.transform((value, _raw, ctx) => {
if (ctx.spec.coerce && !ctx.isType(value)) {
if (/^(true|1)$/i.test(String(value))) return true;
if (/^(false|0)$/i.test(String(value))) return false;
}
return value;
});
});
}
isTrue(message = boolean.isValue) {
return this.test({
message,
name: 'is-value',
exclusive: true,
params: {
value: 'true'
},
test(value) {
return isAbsent(value) || value === true;
}
});
}
isFalse(message = boolean.isValue) {
return this.test({
message,
name: 'is-value',
exclusive: true,
params: {
value: 'false'
},
test(value) {
return isAbsent(value) || value === false;
}
});
}
default(def) {
return super.default(def);
}
defined(msg) {
return super.defined(msg);
}
optional() {
return super.optional();
}
required(msg) {
return super.required(msg);
}
notRequired() {
return super.notRequired();
}
nullable() {
return super.nullable();
}
nonNullable(msg) {
return super.nonNullable(msg);
}
strip(v) {
return super.strip(v);
}
}
create$7.prototype = BooleanSchema.prototype;
/**
* This file is a modified version of the file from the following repository:
* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
* NON-CONFORMANT EDITION.
* © 2011 Colin Snover <http://zetafleet.com>
* Released under MIT license.
*/
// prettier-ignore
// 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
const isoReg = /^(\d{4}|[+-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,.](\d{1,}))?)?(?:(Z)|([+-])(\d{2})(?::?(\d{2}))?)?)?$/;
function parseIsoDate(date) {
const struct = parseDateStruct(date);
if (!struct) return Date.parse ? Date.parse(date) : Number.NaN;
// timestamps without timezone identifiers should be considered local time
if (struct.z === undefined && struct.plusMinus === undefined) {
return new Date(struct.year, struct.month, struct.day, struct.hour, struct.minute, struct.second, struct.millisecond).valueOf();
}
let totalMinutesOffset = 0;
if (struct.z !== 'Z' && struct.plusMinus !== undefined) {
totalMinutesOffset = struct.hourOffset * 60 + struct.minuteOffset;
if (struct.plusMinus === '+') totalMinutesOffset = 0 - totalMinutesOffset;
}
return Date.UTC(struct.year, struct.month, struct.day, struct.hour, struct.minute + totalMinutesOffset, struct.second, struct.millisecond);
}
function parseDateStruct(date) {
var _regexResult$7$length, _regexResult$;
const regexResult = isoReg.exec(date);
if (!regexResult) return null;
// use of toNumber() avoids NaN timestamps caused by “undefined”
// values being passed to Date constructor
return {
year: toNumber(regexResult[1]),
month: toNumber(regexResult[2], 1) - 1,
day: toNumber(regexResult[3], 1),
hour: toNumber(regexResult[4]),
minute: toNumber(regexResult[5]),
second: toNumber(regexResult[6]),
millisecond: regexResult[7] ?
// allow arbitrary sub-second precision beyond milliseconds
toNumber(regexResult[7].substring(0, 3)) : 0,
precision: (_regexResult$7$length = (_regexResult$ = regexResult[7]) == null ? void 0 : _regexResult$.length) != null ? _regexResult$7$length : undefined,
z: regexResult[8] || undefined,
plusMinus: regexResult[9] || undefined,
hourOffset: toNumber(regexResult[10]),
minuteOffset: toNumber(regexResult[11])
};
}
function toNumber(str, defaultValue = 0) {
return Number(str) || defaultValue;
}
// Taken from HTML spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
let rEmail =
// eslint-disable-next-line
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
let rUrl =
// eslint-disable-next-line
/^((https?|ftp):)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
// eslint-disable-next-line
let rUUID = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
let yearMonthDay = '^\\d{4}-\\d{2}-\\d{2}';
let hourMinuteSecond = '\\d{2}:\\d{2}:\\d{2}';
let zOrOffset = '(([+-]\\d{2}(:?\\d{2})?)|Z)';
let rIsoDateTime = new RegExp(`${yearMonthDay}T${hourMinuteSecond}(\\.\\d+)?${zOrOffset}$`);
let isTrimmed = value => isAbsent(value) || value === value.trim();
let objStringTag = {}.toString();
function create$6() {
return new StringSchema();
}
class StringSchema extends Schema {
constructor() {
super({
type: 'string',
check(value) {
if (value instanceof String) value = value.valueOf();
return typeof value === 'string';
}
});
this.withMutation(() => {
this.transform((value, _raw, ctx) => {
if (!ctx.spec.coerce || ctx.isType(value)) return value;
// don't ever convert arrays
if (Array.isArray(value)) return value;
const strValue = value != null && value.toString ? value.toString() : value;
// no one wants plain objects converted to [Object object]
if (strValue === objStringTag) return value;
return strValue;
});
});
}
required(message) {
return super.required(message).withMutation(schema => schema.test({
message: message || mixed.required,
name: 'required',
skipAbsent: true,
test: value => !!value.length
}));
}
notRequired() {
return super.notRequired().withMutation(schema => {
schema.tests = schema.tests.filter(t => t.OPTIONS.name !== 'required');
return schema;
});
}
length(length, message = string.length) {
return this.test({
message,
name: 'length',
exclusive: true,
params: {
length
},
skipAbsent: true,
test(value) {
return value.length === this.resolve(length);
}
});
}
min(min, message = string.min) {
return this.test({
message,
name: 'min',
exclusive: true,
params: {
min
},
skipAbsent: true,
test(value) {
return value.length >= this.resolve(min);
}
});
}
max(max, message = string.max) {
return this.test({
name: 'max',
exclusive: true,
message,
params: {
max
},
skipAbsent: true,
test(value) {
return value.length <= this.resolve(max);
}
});
}
matches(regex, options) {
let excludeEmptyString = false;
let message;
let name;
if (options) {
if (typeof options === 'object') {
({
excludeEmptyString = false,
message,
name
} = options);
} else {
message = options;
}
}
return this.test({
name: name || 'matches',
message: message || string.matches,
params: {
regex
},
skipAbsent: true,
test: value => value === '' && excludeEmptyString || value.search(regex) !== -1
});
}
email(message = string.email) {
return this.matches(rEmail, {
name: 'email',
message,
excludeEmptyString: true
});
}
url(message = string.url) {
return this.matches(rUrl, {
name: 'url',
message,
excludeEmptyString: true
});
}
uuid(message = string.uuid) {
return this.matches(rUUID, {
name: 'uuid',
message,
excludeEmptyString: false
});
}
datetime(options) {
let message = '';
let allowOffset;
let precision;
if (options) {
if (typeof options === 'object') {
({
message = '',
allowOffset = false,
precision = undefined
} = options);
} else {
message = options;
}
}
return this.matches(rIsoDateTime, {
name: 'datetime',
message: message || string.datetime,
excludeEmptyString: true
}).test({
name: 'datetime_offset',
message: message || string.datetime_offset,
params: {
allowOffset
},
skipAbsent: true,
test: value => {
if (!value || allowOffset) return true;
const struct = parseDateStruct(value);
if (!struct) return false;
return !!struct.z;
}
}).test({
name: 'datetime_precision',
message: message || string.datetime_precision,
params: {
precision
},
skipAbsent: true,
test: value => {
if (!value || precision == undefined) return true;
const struct = parseDateStruct(value);
if (!struct) return false;
return struct.precision === precision;
}
});
}
//-- transforms --
ensure() {
return this.default('').transform(val => val === null ? '' : val);
}
trim(message = string.trim) {
return this.transform(val => val != null ? val.trim() : val).test({
message,
name: 'trim',
test: isTrimmed
});
}
lowercase(message = string.lowercase) {
return this.transform(value => !isAbsent(value) ? value.toLowerCase() : value).test({
message,
name: 'string_case',
exclusive: true,
skipAbsent: true,
test: value => isAbsent(value) || value === value.toLowerCase()
});
}
uppercase(message = string.uppercase) {
return this.transform(value => !isAbsent(value) ? value.toUpperCase() : value).test({
message,
name: 'string_case',
exclusive: true,
skipAbsent: true,
test: value => isAbsent(value) || value === value.toUpperCase()
});
}
}
create$6.prototype = StringSchema.prototype;
//
// String Interfaces
//
let isNaN$1 = value => value != +value;
function create$5() {
return new NumberSchema();
}
class NumberSchema extends Schema {
constructor() {
super({
type: 'number',
check(value) {
if (value instanceof Number) value = value.valueOf();
return typeof value === 'number' && !isNaN$1(value);
}
});
this.withMutation(() => {
this.transform((value, _raw, ctx) => {
if (!ctx.spec.coerce) return value;
let parsed = value;
if (typeof parsed === 'string') {
parsed = parsed.replace(/\s/g, '');
if (parsed === '') return NaN;
// don't use parseFloat to avoid positives on alpha-numeric strings
parsed = +parsed;
}
// null -> NaN isn't useful; treat all nulls as null and let it fail on
// nullability check vs TypeErrors
if (ctx.isType(parsed) || parsed === null) return parsed;
return parseFloat(parsed);
});
});
}
min(min, message = number.min) {
return this.test({
message,
name: 'min',
exclusive: true,
params: {
min
},
skipAbsent: true,
test(value) {
return value >= this.resolve(min);
}
});
}
max(max, message = number.max) {
return this.test({
message,
name: 'max',
exclusive: true,
params: {
max
},
skipAbsent: true,
test(value) {
return value <= this.resolve(max);
}
});
}
lessThan(less, message = number.lessThan) {
return this.test({
message,
name: 'max',
exclusive: true,
params: {
less
},
skipAbsent: true,
test(value) {
return value < this.resolve(less);
}
});
}
moreThan(more, message = number.moreThan) {
return this.test({
message,
name: 'min',
exclusive: true,
params: {
more
},
skipAbsent: true,
test(value) {
return value > this.resolve(more);
}
});
}
positive(msg = number.positive) {
return this.moreThan(0, msg);
}
negative(msg = number.negative) {
return this.lessThan(0, msg);
}
integer(message = number.integer) {
return this.test({
name: 'integer',
message,
skipAbsent: true,
test: val => Number.isInteger(val)
});
}
truncate() {
return this.transform(value => !isAbsent(value) ? value | 0 : value);
}
round(method) {
var _method;
let avail = ['ceil', 'floor', 'round', 'trunc'];
method = ((_method = method) == null ? void 0 : _method.toLowerCase()) || 'round';
// this exists for symemtry with the new Math.trunc
if (method === 'trunc') return this.truncate();
if (avail.indexOf(method.toLowerCase()) === -1) throw new TypeError('Only valid options for round() are: ' + avail.join