@amaui/ui-react
Version:
UI for React
204 lines (184 loc) • 8.25 kB
JavaScript
import is from '@amaui/utils/is';
import isValid from '@amaui/utils/isValid';
import { capitalize, cleanValue, equalDeep, merge, stringify } from '@amaui/utils';
import { ValidationError } from '@amaui/errors';
export const onValidateError = (options, model, message) => {
const error = new ValidationError(message);
// Entire model message
if (options.message !== undefined) error.message = options.message;
// model item message
else if (model.message !== undefined) error.message = model.message;
// error
throw error;
};
const validate = async (model, property, form, options_) => {
const options = merge(options_ && is('object', options_) ? options_ : {}, {
uriDecode: true,
parse: true
});
const name = is('function', model.propertyNameUpdate) ? model.propertyNameUpdate(model.name) : model.capitalize !== false ? capitalize(model.name) : model.name;
const value = model.value;
// value validate
// with options above
// required
if (model.required) {
const response = value;
if (response === undefined) onValidateError(options, model, model.messages?.required || `${name} is required`);
}
// only validate
// if value is not undefined
// as it is optional
if (value === undefined) return;
// is
if (model.is !== undefined) {
const is_ = (is('array', model.is) ? model.is : [model.is]).filter(Boolean);
for (const item of is_) {
const itemType = item?.type || item;
const itemOptions = item?.options || undefined;
const response = is(itemType, value, itemOptions);
if (!response) onValidateError(options, model, model.messages?.is || `${name} has to be a valid ${cleanValue(itemType)}`);
}
}
// is valid
if (model.isValid !== undefined) {
const isValid_ = (is('array', model.isValid) ? model.isValid : [model.isValid]).filter(Boolean);
for (const item of isValid_) {
const itemType = item?.type || item;
const itemOptions = item?.options || undefined;
const response = isValid(itemType, value, itemOptions);
if (!response) onValidateError(options, model, model.messages?.isValid || `${name} has to be a valid ${cleanValue(itemType)}`);
}
}
// of
if (model.of !== undefined) {
const of_ = (is('array', model.of) ? model.of : [model.of]).filter(Boolean);
if (is('array', value)) {
const response = value.every(valueItem => {
return of_.some(item => {
const itemType = item?.type || item;
const itemOptions = item?.options || undefined;
return is(itemType, valueItem, itemOptions);
});
});
if (!response) onValidateError(options, model, model.messages?.of || `${name} items have to be one of ${of_.map(item => item?.type || item).join(', ')}`);
}
}
// ofValid
if (model.ofValid !== undefined) {
const ofValid = (is('array', model.ofValid) ? model.ofValid : [model.ofValid]).filter(Boolean);
if (is('array', value)) {
const response = value.every(valueItem => {
return ofValid.some(item => {
const itemType = item?.type || item;
const itemOptions = item?.options || undefined;
return isValid(itemType, valueItem, itemOptions);
});
});
if (!response) onValidateError(options, model, model.messages?.ofValid || `${name} items have to be one of valid ${ofValid.map(item => item?.type || item).join(', ')}`);
}
}
// equal
if (model.equal !== undefined) {
const response = value === model.equal;
if (!response) onValidateError(options, model, model.messages?.equal || `${name} has to be equal to ${stringify(model.equal)}`);
}
// not equal
if (model.notEqual !== undefined) {
const response = value !== model.equal;
if (!response) onValidateError(options, model, model.messages?.notEqual || `${name} has to not be equal to ${stringify(model.equal)}`);
}
// equal deep
if (model.equalDeep !== undefined) {
const response = equalDeep(value, model.equalDeep);
if (!response) onValidateError(options, model, model.messages?.equalDeep || `${name} has to be equal to ${stringify(model.equalDeep)}`);
}
// not equal deep
if (model.notEqualDeep !== undefined) {
const response = !equalDeep(value, model.notEqualDeep);
if (!response) onValidateError(options, model, model.messages?.notEqualDeep || `${name} has to not be equal to ${stringify(model.notEqualDeep)}`);
}
// some
if (is('array', model.some)) {
let response;
if (is('string', value)) {
response = !!model.some.find(item => equalDeep(value, item));
if (!response) onValidateError(options, model, model.messages?.some || `${name} has to be one of ${model.some.map(item => stringify(item)).join(', ')}`);
} else if (is('array', value)) {
response = value.some(item => !!model.some.find(item_ => equalDeep(item, item_)));
if (!response) onValidateError(options, model, model.messages?.some || `${name} has to include some of ${model.some.map(item => stringify(item)).join(', ')}`);
}
}
// in
// every
const every = model.in || model.every;
if (is('array', every)) {
let response;
if (is('string', value)) {
response = !!every.find(item => equalDeep(value, item));
if (!response) onValidateError(options, model, model.messages?.in || model.messages?.every || `${name} has to be one of ${every.map(item => stringify(item)).join(', ')}`);
} else if (is('array', value)) {
response = value.every(item => !!every.find(item_ => equalDeep(item, item_)));
if (!response) onValidateError(options, model, model.messages?.in || model.messages?.every || `${name} has to include one of ${every.map(item => stringify(item)).join(', ')}`);
}
}
// properties
if (is('array', model.properties)) {
const allowed = model.properties;
const keys = Object.keys(value);
const response = keys.every(item => allowed.includes(item));
if (!response) onValidateError(options, model, model.messages?.properties || `${name} allowed properties are ${allowed.join(', ')}`);
}
// not properties
if (is('array', model.notProperties)) {
const notAllowed = model.notProperties;
const keys = Object.keys(value);
const response = keys.every(item => !notAllowed.includes(item));
if (!response) onValidateError(options, model, model.messages?.notProperties || `${name} includes not allowed property. Not allowed properties are ${notAllowed.join(', ')}`);
}
// min
// max
// length
if (![undefined, null].includes(value) && (is('number', model.min) || is('number', model.max) || is('number', model.length))) {
let length = value;
// object
if (is('object', value)) length = Object.keys(value).length;
// number
else if (is('number', value)) length = value;
// string, array, map, etc.
else {
length = value?.length !== undefined ? value?.length : value?.size;
}
if (is('number', model.min)) {
const response = length >= model.min;
if (!response) onValidateError(options, model, model.messages?.min || `${name} has to be minimum ${model.min}`);
}
if (is('number', model.max)) {
const response = length <= model.max;
if (!response) onValidateError(options, model, model.messages?.max || `${name} can be maximum ${model.max}`);
}
if (is('number', model.length)) {
const response = length === model.length;
if (!response) onValidateError(options, model, model.messages?.length || `${name} has to be exactly ${model.length} in length/size`);
}
}
// method
const methods = (is('array', model.method) ? model.method : [model.method]).filter(Boolean);
for (const method_ of methods) {
try {
// either throw error or Promise.reject or return false
const response = await method_(value, {
form,
object: model,
property,
options
});
if (response !== undefined) {
if (!response) throw new ValidationError(`${name} is invalid`);
}
} catch (error) {
const messageValue = error?.message !== undefined ? error.message : error;
onValidateError(options, model, model.messages?.method || messageValue);
}
}
};
export default validate;