flattens validations on backend, ensures validations still run even if field is not required
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const validations = require('../fields/validations');
|
||||
const validations = require('../validation/validations');
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const passport = require('passport');
|
||||
const executePolicy = require('../executePolicy');
|
||||
const executeFieldHooks = require('../../fields/executeHooks');
|
||||
const { validateCreate } = require('../../fields/validateCreate');
|
||||
const validateCreate = require('../../validation/validateCreate');
|
||||
|
||||
const register = async (args) => {
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,7 @@ import Label from '../../Label';
|
||||
import Button from '../../../elements/Button';
|
||||
import CopyToClipboard from '../../../elements/CopyToClipboard';
|
||||
import generateAPIKey from './generateAPIKey';
|
||||
import { text } from '../../../../../fields/validations';
|
||||
import { text } from '../../../../../validation/validations';
|
||||
import useForm from '../../Form/useForm';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import withCondition from '../../withCondition';
|
||||
import Error from '../../Error';
|
||||
import { checkbox } from '../../../../../fields/validations';
|
||||
import { checkbox } from '../../../../../validation/validations';
|
||||
import Check from '../../../icons/Check';
|
||||
|
||||
import './index.scss';
|
||||
@@ -28,6 +28,11 @@ const Checkbox = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
@@ -38,7 +43,7 @@ const Checkbox = (props) => {
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import DatePicker from '../../../elements/DatePicker';
|
||||
@@ -6,7 +6,7 @@ import withCondition from '../../withCondition';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { date } from '../../../../../fields/validations';
|
||||
import { date } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -31,24 +31,27 @@ const DateTime = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
setValue,
|
||||
formProcessing,
|
||||
} = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
baseClass,
|
||||
showError && `${baseClass}--has-error`,
|
||||
formProcessing && 'processing',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
@@ -72,7 +75,7 @@ const DateTime = (props) => {
|
||||
<div className={`${baseClass}__input-wrapper`}>
|
||||
<DatePicker
|
||||
{...props}
|
||||
onChange={readOnly || formProcessing ? setValue : undefined}
|
||||
onChange={readOnly ? setValue : undefined}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
@@ -90,6 +93,7 @@ DateTime.defaultProps = {
|
||||
width: undefined,
|
||||
style: {},
|
||||
path: '',
|
||||
readOnly: false,
|
||||
};
|
||||
|
||||
DateTime.propTypes = {
|
||||
@@ -103,6 +107,7 @@ DateTime.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
readOnly: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withCondition(DateTime);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import withCondition from '../../withCondition';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { email } from '../../../../../fields/validations';
|
||||
import { email } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -26,6 +26,11 @@ const Email = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
@@ -36,7 +41,7 @@ const Email = (props) => {
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useRenderedFields } from '../../RenderFields';
|
||||
import { useLocale } from '../../../utilities/Locale';
|
||||
import Error from '../../Error';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import { flexible } from '../../../../../fields/validations';
|
||||
import { flexible } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -44,11 +44,11 @@ const Flexible = (props) => {
|
||||
const validationResult = validate(
|
||||
value,
|
||||
{
|
||||
minRows, maxRows, singularLabel, blocks,
|
||||
minRows, maxRows, singularLabel, blocks, required,
|
||||
},
|
||||
);
|
||||
return validationResult;
|
||||
}, [validate, maxRows, minRows, singularLabel, blocks]);
|
||||
}, [validate, maxRows, minRows, singularLabel, blocks, required]);
|
||||
|
||||
const {
|
||||
showError,
|
||||
|
||||
@@ -4,7 +4,7 @@ import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import withCondition from '../../withCondition';
|
||||
import { number } from '../../../../../fields/validations';
|
||||
import { number } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import withCondition from '../../withCondition';
|
||||
import { password } from '../../../../../fields/validations';
|
||||
import { password } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -24,6 +24,11 @@ const Password = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
@@ -35,7 +40,7 @@ const Password = (props) => {
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import useFieldType from '../../useFieldType';
|
||||
@@ -6,7 +6,7 @@ import withCondition from '../../withCondition';
|
||||
import Error from '../../Error';
|
||||
import Label from '../../Label';
|
||||
import RadioInput from './RadioInput';
|
||||
import { radio } from '../../../../../fields/validations';
|
||||
import { radio } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -27,6 +27,11 @@ const RadioGroup = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
@@ -37,7 +42,7 @@ const RadioGroup = (props) => {
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { Component, useState, useEffect } from 'react';
|
||||
import React, {
|
||||
Component, useState, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Cookies from 'universal-cookie';
|
||||
import some from 'async-some';
|
||||
@@ -8,7 +10,7 @@ import ReactSelect from '../../../elements/ReactSelect';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { relationship } from '../../../../../fields/validations';
|
||||
import { relationship } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -304,12 +306,18 @@ const RelationshipFieldType = (props) => {
|
||||
const hasMultipleRelations = Array.isArray(relationTo);
|
||||
const dataToInitialize = initialData || defaultValue;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
|
||||
const fieldType = useFieldType({
|
||||
...props,
|
||||
path: path || name,
|
||||
initialData: formattedInitialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
required,
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useRenderedFields } from '../../RenderFields';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import { useLocale } from '../../../utilities/Locale';
|
||||
import Error from '../../Error';
|
||||
import { repeater } from '../../../../../fields/validations';
|
||||
import { repeater } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -48,9 +48,9 @@ const Repeater = (props) => {
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { minRows, maxRows });
|
||||
const validationResult = validate(value, { minRows, maxRows, required });
|
||||
return validationResult;
|
||||
}, [validate, maxRows, minRows]);
|
||||
}, [validate, maxRows, minRows, required]);
|
||||
|
||||
const {
|
||||
showError,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import withCondition from '../../withCondition';
|
||||
import ReactSelect from '../../../elements/ReactSelect';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { select } from '../../../../../fields/validations';
|
||||
import { select } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -78,6 +78,11 @@ const Select = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
@@ -89,7 +94,7 @@ const Select = (props) => {
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -4,7 +4,7 @@ import useFieldType from '../../useFieldType';
|
||||
import withCondition from '../../withCondition';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { text } from '../../../../../fields/validations';
|
||||
import { text } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -28,9 +28,9 @@ const Text = (props) => {
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { minLength, maxLength });
|
||||
const validationResult = validate(value, { minLength, maxLength, required });
|
||||
return validationResult;
|
||||
}, [validate, maxLength, minLength]);
|
||||
}, [validate, maxLength, minLength, required]);
|
||||
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import withCondition from '../../withCondition';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { textarea } from '../../../../../fields/validations';
|
||||
import { textarea } from '../../../../../validation/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -21,10 +21,17 @@ const Textarea = (props) => {
|
||||
label,
|
||||
placeholder,
|
||||
readOnly,
|
||||
minLength,
|
||||
maxLength,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { minLength, maxLength, required });
|
||||
return validationResult;
|
||||
}, [validate, maxLength, minLength, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
@@ -35,7 +42,7 @@ const Textarea = (props) => {
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
|
||||
@@ -86,6 +93,8 @@ Textarea.defaultProps = {
|
||||
placeholder: null,
|
||||
path: '',
|
||||
readOnly: false,
|
||||
minLength: undefined,
|
||||
maxLength: undefined,
|
||||
};
|
||||
|
||||
Textarea.propTypes = {
|
||||
@@ -100,6 +109,8 @@ Textarea.propTypes = {
|
||||
label: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
readOnly: PropTypes.bool,
|
||||
minLength: PropTypes.number,
|
||||
maxLength: PropTypes.number,
|
||||
};
|
||||
|
||||
export default withCondition(Textarea);
|
||||
|
||||
@@ -53,7 +53,7 @@ const useFieldType = (options) => {
|
||||
const sendField = useCallback(async (valueToSend) => {
|
||||
const fieldToDispatch = { path, value: valueToSend };
|
||||
|
||||
fieldToDispatch.valid = (required && typeof validate === 'function') ? await validate(valueToSend || '') : true;
|
||||
fieldToDispatch.valid = typeof validate === 'function' ? await validate(valueToSend || '') : true;
|
||||
|
||||
if (typeof fieldToDispatch.valid === 'string') {
|
||||
fieldToDispatch.errorMessage = fieldToDispatch.valid;
|
||||
@@ -65,7 +65,7 @@ const useFieldType = (options) => {
|
||||
}
|
||||
|
||||
dispatchFields(fieldToDispatch);
|
||||
}, [path, required, dispatchFields, validate, disableFormData]);
|
||||
}, [path, dispatchFields, validate, disableFormData]);
|
||||
|
||||
|
||||
// Method to return from `useFieldType`, used to
|
||||
|
||||
@@ -2,7 +2,7 @@ const mkdirp = require('mkdirp');
|
||||
|
||||
const executePolicy = require('../../auth/executePolicy');
|
||||
const executeFieldHooks = require('../../fields/executeHooks');
|
||||
const { validateCreate } = require('../../fields/validateCreate');
|
||||
const validateCreate = require('../../validation/validateCreate');
|
||||
|
||||
const { MissingFile } = require('../../errors');
|
||||
const resizeAndSave = require('../../uploads/imageResizer');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const executePolicy = require('../../auth/executePolicy');
|
||||
const executeFieldHooks = require('../../fields/executeHooks');
|
||||
const { NotFound, Forbidden } = require('../../errors');
|
||||
const validate = require('../../fields/validateUpdate');
|
||||
const validate = require('../../validation/validateUpdate');
|
||||
|
||||
const resizeAndSave = require('../../uploads/imageResizer');
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { MissingFieldType } = require('../errors');
|
||||
const validations = require('./validations');
|
||||
const validations = require('../validation/validations');
|
||||
|
||||
const sanitizeFields = (fields) => {
|
||||
if (!fields) return [];
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
const { ValidationError } = require('../errors');
|
||||
|
||||
const createValidationPromise = async (data, field) => {
|
||||
const result = await field.validate(data, field);
|
||||
return { result, field };
|
||||
};
|
||||
|
||||
exports.iterateFields = async (data, fields, path = '') => {
|
||||
const validationPromises = [];
|
||||
|
||||
fields.forEach((field) => {
|
||||
const dataToValidate = data || {};
|
||||
|
||||
if ((field.required && !field.localized && !field.condition)) {
|
||||
// If this field does not have a name, it is for
|
||||
// admin panel composition only and should not be
|
||||
// validated against directly
|
||||
if (field.name === undefined && field.fields) {
|
||||
field.fields.forEach((subField) => {
|
||||
validationPromises.push(createValidationPromise(dataToValidate[subField.name], subField));
|
||||
});
|
||||
} else if (field.fields) {
|
||||
if (field.type === 'repeater' || field.type === 'flexible') {
|
||||
const rowCount = Array.isArray(dataToValidate[field.name]) ? dataToValidate[field.name].length : 0;
|
||||
validationPromises.push(createValidationPromise(rowCount, field));
|
||||
|
||||
if (Array.isArray(dataToValidate[field.name])) {
|
||||
dataToValidate[field.name].forEach((rowData, i) => {
|
||||
validationPromises.push(exports.iterateFields(rowData, field.fields, `${path}${field.name}.${i}.`));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
validationPromises.push(exports.iterateFields(dataToValidate[field.name], field.fields, `${path}${field.name}`));
|
||||
}
|
||||
} else {
|
||||
validationPromises.push(createValidationPromise(dataToValidate[field.name], field));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const validationResults = await Promise.all(validationPromises);
|
||||
|
||||
const errors = validationResults.reduce((results, result) => {
|
||||
const { field, result: validationResult } = result;
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return [
|
||||
...results,
|
||||
...result,
|
||||
];
|
||||
}
|
||||
|
||||
if (validationResult === false || typeof validationResult === 'string') {
|
||||
const fieldPath = `${path}${field.name}`;
|
||||
|
||||
return [
|
||||
...results,
|
||||
{
|
||||
field: fieldPath,
|
||||
message: validationResult,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
exports.validateCreate = async (data, fields) => {
|
||||
try {
|
||||
const errors = await exports.iterateFields(data, fields);
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(errors);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
const { ValidationError } = require('../errors');
|
||||
|
||||
const validateUpdate = async (data, fields) => {
|
||||
const validationPromises = [];
|
||||
const validatedFields = [];
|
||||
|
||||
Object.keys(data).forEach((key) => {
|
||||
const dataToValidate = data[key];
|
||||
|
||||
const field = fields.find(matchedField => matchedField.name === key);
|
||||
|
||||
if (field && dataToValidate !== undefined) {
|
||||
validationPromises.push(field.validate(dataToValidate, field));
|
||||
validatedFields.push(field);
|
||||
}
|
||||
});
|
||||
|
||||
const validationResults = await Promise.all(validationPromises);
|
||||
|
||||
const errors = validationResults.reduce((results, result, i) => {
|
||||
const field = validatedFields[i];
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return [
|
||||
...results,
|
||||
...result,
|
||||
];
|
||||
} if (result !== true) {
|
||||
return [
|
||||
...results,
|
||||
{
|
||||
field: field.name,
|
||||
message: result,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(errors);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = validateUpdate;
|
||||
31
src/validation/utilities.js
Normal file
31
src/validation/utilities.js
Normal file
@@ -0,0 +1,31 @@
|
||||
exports.createValidationPromise = async (data, field) => {
|
||||
const result = await field.validate(data, field);
|
||||
return { result, field };
|
||||
};
|
||||
|
||||
exports.getErrorResults = async (resultPromises, path) => {
|
||||
const results = await Promise.all(resultPromises);
|
||||
|
||||
return results.reduce((formattedResults, result) => {
|
||||
const { field, result: validationResult } = result;
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return [
|
||||
...formattedResults,
|
||||
...result,
|
||||
];
|
||||
}
|
||||
|
||||
if (validationResult === false || typeof validationResult === 'string') {
|
||||
return [
|
||||
...formattedResults,
|
||||
{
|
||||
field: `${path}${field.name}`,
|
||||
message: validationResult,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return formattedResults;
|
||||
}, []);
|
||||
};
|
||||
51
src/validation/validateCreate.js
Normal file
51
src/validation/validateCreate.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { ValidationError } = require('../errors');
|
||||
const { createValidationPromise, getErrorResults } = require('./utilities');
|
||||
|
||||
const iterateFields = async (data, fields, path = '') => {
|
||||
const validationPromises = [];
|
||||
|
||||
fields.forEach((field) => {
|
||||
const dataToValidate = data || {};
|
||||
|
||||
if (!field.condition) {
|
||||
// If this field does not have a name, it is for
|
||||
// admin panel composition only and should not be
|
||||
// validated against directly
|
||||
if (field.name === undefined && field.fields) {
|
||||
field.fields.forEach((subField) => {
|
||||
validationPromises.push(createValidationPromise(dataToValidate[subField.name], subField));
|
||||
});
|
||||
} else if (field.fields) {
|
||||
if (field.type === 'repeater' || field.type === 'flexible') {
|
||||
const isArray = Array.isArray(dataToValidate[field.name]);
|
||||
const rowCount = isArray ? dataToValidate[field.name].length : 0;
|
||||
validationPromises.push(createValidationPromise(rowCount, field));
|
||||
|
||||
if (isArray) {
|
||||
dataToValidate[field.name].forEach((rowData, i) => {
|
||||
validationPromises.push(iterateFields(rowData, field.fields, `${path}${field.name}.${i}.`));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
validationPromises.push(iterateFields(dataToValidate[field.name], field.fields, `${path}${field.name}.`));
|
||||
}
|
||||
} else {
|
||||
validationPromises.push(createValidationPromise(dataToValidate[field.name], field));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return getErrorResults(validationPromises, path);
|
||||
};
|
||||
|
||||
|
||||
module.exports = async (data, fields) => {
|
||||
try {
|
||||
const errors = await iterateFields(data, fields);
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(errors);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
44
src/validation/validateUpdate.js
Normal file
44
src/validation/validateUpdate.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const { ValidationError } = require('../errors');
|
||||
const { createValidationPromise, getErrorResults } = require('./utilities');
|
||||
|
||||
const iterateFields = async (data, fields, path = '') => {
|
||||
const validationPromises = [];
|
||||
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
const field = fields.find(matchedField => matchedField.name === key);
|
||||
|
||||
if (field && value !== undefined) {
|
||||
if (field.fields) {
|
||||
if (field.type === 'repeater' || field.type === 'flexible') {
|
||||
const isArray = Array.isArray(value);
|
||||
const rowCount = isArray ? value.length : 0;
|
||||
validationPromises.push(createValidationPromise(rowCount, field));
|
||||
|
||||
if (isArray) {
|
||||
value.forEach((rowData, i) => {
|
||||
validationPromises.push(iterateFields(rowData, field.fields, `${path}${field.name}.${i}.`));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
validationPromises.push(iterateFields(value, field.fields, `${path}${field.name}.`));
|
||||
}
|
||||
} else {
|
||||
validationPromises.push(createValidationPromise(value, field));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return getErrorResults(validationPromises, path);
|
||||
};
|
||||
|
||||
module.exports = async (data, fields) => {
|
||||
try {
|
||||
const errors = await iterateFields(data, fields);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(errors);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
const defaultMessage = 'This field is required.';
|
||||
|
||||
const optionsToValidatorMap = {
|
||||
number: (value, options = {}) => {
|
||||
const parsedValue = parseInt(value, 10);
|
||||
|
||||
if (typeof parsedValue !== 'number' || Number.isNaN(parsedValue)) {
|
||||
if (value && (typeof parsedValue !== 'number' || Number.isNaN(parsedValue))) {
|
||||
return 'Please enter a valid number.';
|
||||
}
|
||||
|
||||
@@ -14,6 +16,10 @@ const optionsToValidatorMap = {
|
||||
return `"${value}" is less than the min allowed value of ${options.min}.`;
|
||||
}
|
||||
|
||||
if (options.required && typeof parsedValue !== 'number') {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
text: (value, options = {}) => {
|
||||
@@ -25,8 +31,10 @@ const optionsToValidatorMap = {
|
||||
return `This value must be longer than the minimum length of ${options.max} characters.`;
|
||||
}
|
||||
|
||||
if (typeof value !== 'string' || (typeof value === 'string' && value.length === 0)) {
|
||||
return 'This field is required.';
|
||||
if (options.required) {
|
||||
if (typeof value !== 'string' || (typeof value === 'string' && value.length === 0)) {
|
||||
return defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -40,13 +48,19 @@ const optionsToValidatorMap = {
|
||||
return `This value must be longer than the minimum length of ${options.max} characters.`;
|
||||
}
|
||||
|
||||
return Boolean(value);
|
||||
},
|
||||
email: (value) => {
|
||||
if (/\S+@\S+\.\S+/.test(value)) {
|
||||
return true;
|
||||
if (options.required && !value) {
|
||||
return defaultMessage;
|
||||
}
|
||||
return 'Please enter a valid email address.';
|
||||
|
||||
return true;
|
||||
},
|
||||
email: (value, options = {}) => {
|
||||
if ((value && !/\S+@\S+\.\S+/.test(value))
|
||||
|| (!value && options.required)) {
|
||||
return 'Please enter a valid email address.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
textarea: (value, options = {}) => {
|
||||
if (options.maxLength && value.length > options.maxLength) {
|
||||
@@ -57,39 +71,52 @@ const optionsToValidatorMap = {
|
||||
return `This value must be longer than the minimum length of ${options.max} characters.`;
|
||||
}
|
||||
|
||||
return Boolean(value);
|
||||
},
|
||||
wysiwyg: (value) => {
|
||||
if (value) return true;
|
||||
if (options.required && !value) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return 'This field is required.';
|
||||
return true;
|
||||
},
|
||||
code: (value) => {
|
||||
if (value) return true;
|
||||
wysiwyg: (value, options = {}) => {
|
||||
if (options.required && !value) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return 'This field is required.';
|
||||
return true;
|
||||
},
|
||||
checkbox: (value) => {
|
||||
if (typeof value === 'boolean') {
|
||||
code: (value, options = {}) => {
|
||||
if (options.required && !value) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
checkbox: (value, options = {}) => {
|
||||
if ((value && typeof value !== 'boolean')
|
||||
|| (options.required && typeof value !== 'boolean')) {
|
||||
return 'This field can only be equal to true or false.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
date: (value, options = {}) => {
|
||||
if (value && value instanceof Date) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'This field can only be equal to true or false.';
|
||||
},
|
||||
date: (value) => {
|
||||
if (value instanceof Date) {
|
||||
return true;
|
||||
if (options.required) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return `"${value}" is not a valid date.`;
|
||||
},
|
||||
upload: (value) => {
|
||||
if (value) return true;
|
||||
return 'This field is required.';
|
||||
upload: (value, options = {}) => {
|
||||
if (value || !options.required) return true;
|
||||
return defaultMessage;
|
||||
},
|
||||
relationship: (value) => {
|
||||
if (value) return true;
|
||||
return 'This field is required.';
|
||||
relationship: (value, options = {}) => {
|
||||
if (value || !options.required) return true;
|
||||
return defaultMessage;
|
||||
},
|
||||
repeater: (value, options = {}) => {
|
||||
if (options.minRows && value < options.minRows) {
|
||||
@@ -100,19 +127,19 @@ const optionsToValidatorMap = {
|
||||
return `This field requires no more than ${options.maxRows} row(s).`;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
if (!value && options.required) {
|
||||
return 'This field requires at least one row.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
select: (value) => {
|
||||
if (value && value.length > 0) return true;
|
||||
return 'This field is required.';
|
||||
select: (value, options = {}) => {
|
||||
if (value || !options.required) return true;
|
||||
return defaultMessage;
|
||||
},
|
||||
radio: (value) => {
|
||||
if (value) return true;
|
||||
return 'This field is required.';
|
||||
radio: (value, options = {}) => {
|
||||
if (value || !options.required) return true;
|
||||
return defaultMessage;
|
||||
},
|
||||
flexible: (value, options) => {
|
||||
if (options.minRows && value < options.minRows) {
|
||||
@@ -123,7 +150,7 @@ const optionsToValidatorMap = {
|
||||
return `This field requires no more than ${options.maxRows} row(s).`;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
if (!value && options.required) {
|
||||
return 'This field requires at least one row.';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user