skip unique validation if the attribute is unchanged

This commit is contained in:
Dieter Stinglhamber 2021-11-26 09:30:28 +01:00
parent d3de1ca7be
commit 2b625210d7
3 changed files with 45 additions and 25 deletions

View File

@ -202,10 +202,14 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
const isDraft = contentTypesUtils.isDraft(entityToUpdate, model); const isDraft = contentTypesUtils.isDraft(entityToUpdate, model);
const validData = await entityValidator.validateEntityUpdate(model, data, { const validData = await entityValidator.validateEntityUpdate(
model,
data,
{
isDraft, isDraft,
entityId, },
}); entityToUpdate
);
const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams)); const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));

View File

@ -120,12 +120,13 @@ const createRelationValidator = createOrUpdate => (attr, data, { isDraft }) => {
const createScalarAttributeValidator = createOrUpdate => ( const createScalarAttributeValidator = createOrUpdate => (
attr, attr,
{ isDraft, uid, attributeName, entityId } data,
{ isDraft, model, attributeName, entity }
) => { ) => {
let validator; let validator;
if (has(attr.type, validators)) { if (has(attr.type, validators)) {
validator = validators[attr.type](attr, { isDraft, uid, attributeName, entityId }); validator = validators[attr.type](attr, { isDraft, model, attributeName, entity, data });
} else { } else {
// No validators specified - fall back to mixed // No validators specified - fall back to mixed
validator = yup.mixed(); validator = yup.mixed();
@ -139,18 +140,18 @@ const createScalarAttributeValidator = createOrUpdate => (
const createAttributeValidator = createOrUpdate => ( const createAttributeValidator = createOrUpdate => (
attr, attr,
data, data,
{ isDraft, uid, attributeName, entityId } { isDraft, model, attributeName, entity }
) => { ) => {
let validator; let validator;
if (isMediaAttribute(attr)) { if (isMediaAttribute(attr)) {
validator = yup.mixed(); validator = yup.mixed();
} else if (isScalarAttribute(attr)) { } else if (isScalarAttribute(attr)) {
validator = createScalarAttributeValidator(createOrUpdate)(attr, { validator = createScalarAttributeValidator(createOrUpdate)(attr, data, {
isDraft, isDraft,
uid, model,
attributeName, attributeName,
entityId, entity,
}); });
} else { } else {
if (attr.type === 'component') { if (attr.type === 'component') {
@ -169,14 +170,14 @@ const createAttributeValidator = createOrUpdate => (
return validator; return validator;
}; };
const createModelValidator = createOrUpdate => (model, data, { isDraft, entityId }) => { const createModelValidator = createOrUpdate => (model, data, { isDraft }, entity) => {
const writableAttributes = model ? getWritableAttributes(model) : []; const writableAttributes = model ? getWritableAttributes(model) : [];
const schema = writableAttributes.reduce((validators, attributeName) => { const schema = writableAttributes.reduce((validators, attributeName) => {
const validator = createAttributeValidator(createOrUpdate)( const validator = createAttributeValidator(createOrUpdate)(
model.attributes[attributeName], model.attributes[attributeName],
prop(attributeName, data), prop(attributeName, data),
{ isDraft, uid: model.uid, attributeName, entityId } { isDraft, model, attributeName, entity }
); );
return assoc(attributeName, validator)(validators); return assoc(attributeName, validator)(validators);
@ -188,7 +189,8 @@ const createModelValidator = createOrUpdate => (model, data, { isDraft, entityId
const createValidateEntity = createOrUpdate => async ( const createValidateEntity = createOrUpdate => async (
model, model,
data, data,
{ isDraft = false, entityId } = {} { isDraft = false } = {},
entity = null
) => { ) => {
if (!isObject(data)) { if (!isObject(data)) {
const { displayName } = model.info; const { displayName } = model.info;
@ -198,10 +200,14 @@ const createValidateEntity = createOrUpdate => async (
); );
} }
const validator = createModelValidator(createOrUpdate)(model, data, { const validator = createModelValidator(createOrUpdate)(
model,
data,
{
isDraft, isDraft,
entityId, },
}).required(); entity
).required();
return validateYupSchema(validator, { strict: false, abortEarly: false })(data); return validateYupSchema(validator, { strict: false, abortEarly: false })(data);
}; };

View File

@ -7,9 +7,9 @@ const { yup } = require('@strapi/utils');
/** /**
* Utility function to compose validators * Utility function to compose validators
*/ */
const composeValidators = (...fns) => (attr, { isDraft, uid, attributeName, entityId }) => { const composeValidators = (...fns) => (attr, { isDraft, model, attributeName, entity, data }) => {
return fns.reduce((validator, fn) => { return fns.reduce((validator, fn) => {
return fn(attr, validator, { isDraft, uid, attributeName, entityId }); return fn(attr, validator, { isDraft, model, attributeName, entity, data });
}, yup.mixed()); }, yup.mixed());
}; };
@ -71,14 +71,24 @@ const addMaxFloatValidator = ({ max }, validator) =>
const addStringRegexValidator = ({ regex }, validator) => const addStringRegexValidator = ({ regex }, validator) =>
_.isUndefined(regex) ? validator : validator.matches(new RegExp(regex)); _.isUndefined(regex) ? validator : validator.matches(new RegExp(regex));
const addUniqueValidator = (attr, validator, { uid, attributeName, entityId }) => { const addUniqueValidator = (attr, validator, { model, attributeName, entity, data }) => {
if (attr.unique) { /**
* If the attribute is unchanged we skip the unique verification. This will
* prevent the validator to be triggered in case the user activated the
* unique constraint after already creating multiple entries with
* the same attribute value for that field.
*/
if (entity && data === entity[attributeName]) {
return validator;
}
if (attr.unique || attr.type === 'uid') {
return validator.test('unique', 'This attribute must be unique', async value => { return validator.test('unique', 'This attribute must be unique', async value => {
let whereParams = entityId let whereParams = entity
? { $and: [{ [attributeName]: value }, { $not: { id: entityId } }] } ? { $and: [{ [attributeName]: value }, { $not: { id: entity.id } }] }
: { [attributeName]: value }; : { [attributeName]: value };
const record = await strapi.db.query(uid).findOne({ const record = await strapi.db.query(model.uid).findOne({
select: ['id'], select: ['id'],
where: whereParams, where: whereParams,
}); });
@ -131,7 +141,7 @@ module.exports = {
password: stringValidator, password: stringValidator,
email: emailValidator, email: emailValidator,
enumeration: enumerationValidator, enumeration: enumerationValidator,
boolean: () => () => yup.mixed(), boolean: () => yup.boolean(),
uid: uidValidator, uid: uidValidator,
json: () => yup.mixed(), json: () => yup.mixed(),
integer: integerValidator, integer: integerValidator,