|
|
@@ -3,6 +3,7 @@ import {DataType} from './properties/index.js';
|
|
|
import {getCtorName} from '../../utils/index.js';
|
|
|
import {isPureObject} from '../../utils/index.js';
|
|
|
import {InvalidArgumentError} from '../../errors/index.js';
|
|
|
+import {PropertyValidatorRegistry} from './properties/index.js';
|
|
|
import {ModelDefinitionUtils} from './model-definition-utils.js';
|
|
|
|
|
|
/**
|
|
|
@@ -15,8 +16,9 @@ export class ModelDataValidator extends Service {
|
|
|
* @param {string} modelName
|
|
|
* @param {object} modelData
|
|
|
* @param {boolean} isPartial
|
|
|
+ * @returns {Promise<void>}
|
|
|
*/
|
|
|
- validate(modelName, modelData, isPartial = false) {
|
|
|
+ async validate(modelName, modelData, isPartial = false) {
|
|
|
if (!isPureObject(modelData))
|
|
|
throw new InvalidArgumentError(
|
|
|
'The data of the model %v should be an Object, but %v given.',
|
|
|
@@ -28,16 +30,16 @@ export class ModelDataValidator extends Service {
|
|
|
ModelDefinitionUtils,
|
|
|
).getPropertiesDefinitionInBaseModelHierarchy(modelName);
|
|
|
const propNames = Object.keys(isPartial ? modelData : propDefs);
|
|
|
- propNames.forEach(propName => {
|
|
|
+ for (const propName of propNames) {
|
|
|
const propDef = propDefs[propName];
|
|
|
- if (!propDef) return;
|
|
|
- this._validatePropertyValue(
|
|
|
+ if (!propDef) continue;
|
|
|
+ await this._validatePropertyValue(
|
|
|
modelName,
|
|
|
propName,
|
|
|
propDef,
|
|
|
modelData[propName],
|
|
|
);
|
|
|
- });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -47,8 +49,9 @@ export class ModelDataValidator extends Service {
|
|
|
* @param {string} propName
|
|
|
* @param {string|object} propDef
|
|
|
* @param {*} propValue
|
|
|
+ * @returns {Promise<void>}
|
|
|
*/
|
|
|
- _validatePropertyValue(modelName, propName, propDef, propValue) {
|
|
|
+ async _validatePropertyValue(modelName, propName, propDef, propValue) {
|
|
|
// undefined and null
|
|
|
if (propValue == null) {
|
|
|
const isRequired =
|
|
|
@@ -62,7 +65,19 @@ export class ModelDataValidator extends Service {
|
|
|
);
|
|
|
}
|
|
|
// DataType
|
|
|
- this._validatePropertyValueType(modelName, propName, propDef, propValue);
|
|
|
+ await this._validatePropertyValueType(
|
|
|
+ modelName,
|
|
|
+ propName,
|
|
|
+ propDef,
|
|
|
+ propValue,
|
|
|
+ );
|
|
|
+ // PropertyValidators
|
|
|
+ await this._validateByPropertyValidators(
|
|
|
+ modelName,
|
|
|
+ propName,
|
|
|
+ propDef,
|
|
|
+ propValue,
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -73,9 +88,9 @@ export class ModelDataValidator extends Service {
|
|
|
* @param {string|object} propDef
|
|
|
* @param {*} propValue
|
|
|
* @param {boolean} isArrayValue
|
|
|
- * @returns {void}
|
|
|
+ * @returns {Promise<void>}
|
|
|
*/
|
|
|
- _validatePropertyValueType(
|
|
|
+ async _validatePropertyValueType(
|
|
|
modelName,
|
|
|
propName,
|
|
|
propDef,
|
|
|
@@ -123,7 +138,7 @@ export class ModelDataValidator extends Service {
|
|
|
// ARRAY
|
|
|
case DataType.ARRAY:
|
|
|
if (!Array.isArray(propValue)) throw createError('an Array');
|
|
|
- propValue.forEach(value =>
|
|
|
+ const arrayItemsValidationPromises = propValue.map(async value =>
|
|
|
this._validatePropertyValueType(
|
|
|
modelName,
|
|
|
propName,
|
|
|
@@ -132,13 +147,74 @@ export class ModelDataValidator extends Service {
|
|
|
true,
|
|
|
),
|
|
|
);
|
|
|
+ await Promise.all(arrayItemsValidationPromises);
|
|
|
break;
|
|
|
// OBJECT
|
|
|
case DataType.OBJECT:
|
|
|
if (!isPureObject(propValue)) throw createError('an Object');
|
|
|
if (typeof propDef === 'object' && propDef.model)
|
|
|
- this.validate(propDef.model, propValue);
|
|
|
+ await this.validate(propDef.model, propValue);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Validate by property validators.
|
|
|
+ *
|
|
|
+ * @param {string} modelName
|
|
|
+ * @param {string} propName
|
|
|
+ * @param {string|object} propDef
|
|
|
+ * @param {*} propValue
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ */
|
|
|
+ async _validateByPropertyValidators(modelName, propName, propDef, propValue) {
|
|
|
+ if (typeof propDef === 'string' || propDef.validate == null) return;
|
|
|
+ const options = propDef.validate;
|
|
|
+ const propertyValidatorRegistry = this.getService(
|
|
|
+ PropertyValidatorRegistry,
|
|
|
+ );
|
|
|
+ const createError = validatorName =>
|
|
|
+ new InvalidArgumentError(
|
|
|
+ 'The property %v of the model %v has an invalid value %v ' +
|
|
|
+ 'that caught by the validator %v.',
|
|
|
+ propName,
|
|
|
+ modelName,
|
|
|
+ propValue,
|
|
|
+ validatorName,
|
|
|
+ );
|
|
|
+ const container = this.container;
|
|
|
+ const validateBy = async (validatorName, validatorOptions = undefined) => {
|
|
|
+ const validator = propertyValidatorRegistry.getValidator(validatorName);
|
|
|
+ const context = {validatorName, modelName, propName, propDef, container};
|
|
|
+ const valid = await validator(propValue, validatorOptions, context);
|
|
|
+ if (valid !== true) throw createError(validatorName);
|
|
|
+ };
|
|
|
+ if (options && typeof options === 'string') {
|
|
|
+ await validateBy(options);
|
|
|
+ } else if (Array.isArray(options)) {
|
|
|
+ const validationPromises = options.map(validatorName =>
|
|
|
+ validateBy(validatorName),
|
|
|
+ );
|
|
|
+ await Promise.all(validationPromises);
|
|
|
+ } else if (options !== null && typeof options === 'object') {
|
|
|
+ const validationPromises = [];
|
|
|
+ Object.keys(options).forEach(validatorName => {
|
|
|
+ if (Object.prototype.hasOwnProperty.call(options, validatorName)) {
|
|
|
+ const validatorOptions = options[validatorName];
|
|
|
+ const validationPromise = validateBy(validatorName, validatorOptions);
|
|
|
+ validationPromises.push(validationPromise);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ await Promise.all(validationPromises);
|
|
|
+ } else {
|
|
|
+ throw new InvalidArgumentError(
|
|
|
+ 'The provided option "validate" of the property %v in the model %v ' +
|
|
|
+ 'should be a non-empty String, an Array of String or an Object, ' +
|
|
|
+ 'but %v given.',
|
|
|
+ propName,
|
|
|
+ modelName,
|
|
|
+ options,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|