@naturalcycles/nodejs-lib
Version:
Standard library for Node.js
140 lines (139 loc) • 4.95 kB
JavaScript
;
/*
* Does 2 things:
* 1. Validates the value according to Schema passed.
* 2. Converts the value (also according to Schema).
*
* "Converts" mean e.g trims all strings from leading/trailing spaces.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.convert = exports.undefinedIfInvalid = exports.isValid = exports.getValidationResult = exports.validate = void 0;
const js_lib_1 = require("@naturalcycles/js-lib");
const joi_validation_error_1 = require("./joi.validation.error");
// Strip colors in production (for e.g Sentry reporting)
// const stripColors = process.env.NODE_ENV === 'production' || !!process.env.GAE_INSTANCE
// Currently colors do more bad than good, so let's strip them always for now
const stripColors = true;
const defaultOptions = {
abortEarly: false,
convert: true,
allowUnknown: true,
stripUnknown: {
objects: true,
// true: it will SILENTLY strip invalid values from arrays. Very dangerous! Can lead to data loss!
// false: it will THROW validation error if any of array items is invalid
// Q: is it invalid if it has unknown properties?
// A: no, unknown properties are just stripped (in both 'false' and 'true' states), array is still valid
// Q: will it strip or keep unknown properties in array items?..
// A: strip
arrays: false, // let's be very careful with that! https://github.com/hapijs/joi/issues/658
},
presence: 'required',
// errors: {
// stack: true,
// }
};
/**
* Validates with Joi.
* Throws JoiValidationError if invalid.
* Returns *converted* value.
*
* If `schema` is undefined - returns value as is.
*/
function validate(value, schema, objectName, options = {}) {
const { value: returnValue, error } = getValidationResult(value, schema, objectName, options);
if (error) {
throw error;
}
return returnValue;
}
exports.validate = validate;
/**
* Validates with Joi.
* Returns JoiValidationResult with converted value and error (if any).
* Does not throw.
*
* If `schema` is undefined - returns value as is.
*/
function getValidationResult(value, schema, objectName, options = {}) {
if (!schema)
return { value };
const { value: returnValue, error } = schema.validate(value, {
...defaultOptions,
...options,
});
const vr = {
value: returnValue,
};
if (error) {
vr.error = createError(value, error, objectName);
}
return vr;
}
exports.getValidationResult = getValidationResult;
/**
* Convenience function that returns true if !error.
*/
function isValid(value, schema) {
if (!schema)
return { value };
const { error } = schema.validate(value, defaultOptions);
return !error;
}
exports.isValid = isValid;
function undefinedIfInvalid(value, schema) {
if (!schema)
return { value };
const { value: returnValue, error } = schema.validate(value, defaultOptions);
return error ? undefined : returnValue;
}
exports.undefinedIfInvalid = undefinedIfInvalid;
/**
* Will do joi-convertation, regardless of error/validity of value.
*
* @returns converted value
*/
function convert(value, schema) {
if (!schema)
return value;
const { value: returnValue } = schema.validate(value, defaultOptions);
return returnValue;
}
exports.convert = convert;
function createError(value, err, objectName) {
if (!err)
return undefined;
const tokens = [];
const objectId = (0, js_lib_1._isObject)(value) ? value['id'] : undefined;
if (objectId || objectName) {
objectName = objectName || value?.constructor?.name;
tokens.push('Invalid ' + [objectName, objectId].filter(Boolean).join('.'));
}
const annotation = err.annotate(stripColors);
if (annotation.length > 4000) {
// Annotation message is too big and will be replaced by stringified `error.details` instead
tokens.push((0, js_lib_1._truncateMiddle)(annotation, 4000, `\n... ${(0, js_lib_1._hb)(annotation.length)} message truncated ...\n`));
// Up to 5 `details`
tokens.push(...err.details.slice(0, 5).map(i => `${i.message} @ .${i.path.join('.')}`));
if (err.details.length > 5)
tokens.push(`... ${err.details.length} errors`);
}
else {
tokens.push(annotation);
}
const msg = tokens.join('\n');
const data = {
joiValidationErrorItems: err.details,
...(objectName && { joiValidationObjectName: objectName }),
...(objectId && { joiValidationObjectId: objectId }),
};
// Make annotation non-enumerable, to not get it automatically printed,
// but still accessible
Object.defineProperty(data, 'annotation', {
writable: true,
configurable: true,
enumerable: false,
value: annotation,
});
return new joi_validation_error_1.JoiValidationError(msg, data);
}