140 lines
4.6 KiB
JavaScript
140 lines
4.6 KiB
JavaScript
const { ValidationError } = require('../errors');
|
|
|
|
module.exports = async (config, entityConfig, operation) => {
|
|
const {
|
|
data: fullData,
|
|
originalDoc: fullOriginalDoc,
|
|
operationName,
|
|
hook,
|
|
req,
|
|
} = operation;
|
|
|
|
// Maintain a top-level list of promises
|
|
// so that all async field access / validations / hooks
|
|
// can run in parallel
|
|
const validationPromises = [];
|
|
const accessPromises = [];
|
|
const relationshipAccessPromises = [];
|
|
const hookPromises = [];
|
|
const errors = [];
|
|
|
|
const createValidationPromise = async (data, field, path) => {
|
|
const shouldValidate = field.validate && !field.condition;
|
|
const dataToValidate = data || field.defaultValue;
|
|
const result = shouldValidate ? await field.validate(dataToValidate, field) : true;
|
|
|
|
if (!result || typeof result === 'string') {
|
|
errors.push({
|
|
message: result,
|
|
field: `${path}${field.name}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
const createRelationshipAccessPromise = async (data, field, access) => {
|
|
const resultingData = data;
|
|
|
|
const result = await access({ req });
|
|
|
|
if (result === false) {
|
|
delete resultingData[field.name];
|
|
}
|
|
};
|
|
|
|
const createAccessPromise = async (data, originalDoc, field) => {
|
|
const resultingData = data;
|
|
|
|
if (field.access && field.access[operationName]) {
|
|
const result = await field.access[operationName]({ req });
|
|
|
|
if (!result && operationName === 'update' && originalDoc[field.name] !== undefined) {
|
|
resultingData[field.name] = originalDoc[field.name];
|
|
} else if (!result) {
|
|
delete resultingData[field.name];
|
|
}
|
|
}
|
|
|
|
if (field.type === 'relationship' && operationName === 'read') {
|
|
const relatedCollections = Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo];
|
|
|
|
relatedCollections.forEach((slug) => {
|
|
const collection = config.collections.find((coll) => coll.slug === slug);
|
|
|
|
if (collection && collection.access && collection.access.read) {
|
|
relationshipAccessPromises.push(createRelationshipAccessPromise(data, field, collection.access.read));
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const createHookPromise = async (data, field) => {
|
|
const resultingData = data;
|
|
|
|
if (field.hooks && field.hooks[hook]) {
|
|
resultingData[field.name] = await field.hooks[hook](data[field.name]);
|
|
}
|
|
};
|
|
|
|
const traverseFields = (fields, data = {}, originalDoc = {}, path) => {
|
|
fields.forEach((field) => {
|
|
const dataCopy = data;
|
|
|
|
if (field.type === 'upload') {
|
|
if (data[field.name] === '') dataCopy[field.name] = null;
|
|
}
|
|
|
|
// TODO: sanitize additional field types as necessary i.e. relationships
|
|
|
|
if (field.type === 'checkbox') {
|
|
if (data[field.name] === 'true') dataCopy[field.name] = true;
|
|
if (data[field.name] === 'false') dataCopy[field.name] = false;
|
|
}
|
|
|
|
accessPromises.push(createAccessPromise(data, originalDoc, field));
|
|
hookPromises.push(createHookPromise(data, field));
|
|
|
|
if (field.fields) {
|
|
if (field.name === undefined) {
|
|
traverseFields(field.fields, data, originalDoc, path);
|
|
} else if (field.type === 'array' || field.type === 'blocks') {
|
|
if (Array.isArray(data[field.name])) {
|
|
data[field.name].forEach((rowData, i) => {
|
|
const originalDocRow = originalDoc && originalDoc[field.name] && originalDoc[field.name][i];
|
|
traverseFields(field.fields, rowData, originalDocRow || undefined, `${path}${field.name}.${i}.`);
|
|
});
|
|
}
|
|
} else {
|
|
traverseFields(field.fields, data[field.name], originalDoc[field.name], `${path}${field.name}.`);
|
|
}
|
|
}
|
|
|
|
if (operationName === 'create' || (operationName === 'update' && data[field.name] !== undefined)) {
|
|
if (field.type === 'array' || field.type === 'blocks') {
|
|
const hasRowsOfData = Array.isArray(data[field.name]);
|
|
const rowCount = hasRowsOfData ? data[field.name].length : 0;
|
|
|
|
validationPromises.push(createValidationPromise(rowCount, field, path));
|
|
} else {
|
|
validationPromises.push(createValidationPromise(data[field.name], field, path));
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// //////////////////////////////////////////
|
|
// Entry point for field validation
|
|
// //////////////////////////////////////////
|
|
|
|
traverseFields(entityConfig.fields, fullData, fullOriginalDoc, '');
|
|
await Promise.all(validationPromises);
|
|
|
|
if (errors.length > 0) {
|
|
throw new ValidationError(errors);
|
|
}
|
|
|
|
await Promise.all(accessPromises);
|
|
await Promise.all(hookPromises);
|
|
|
|
return fullData;
|
|
};
|