mirror of
https://github.com/strapi/strapi.git
synced 2025-11-25 22:51:33 +00:00
Merge pull request #11683 from strapi/v4/unique-validator
[v4] add `unique` validator
This commit is contained in:
commit
3e9e3f13cb
@ -46,7 +46,7 @@ const advancedForm = {
|
|||||||
id: getTrad('form.attribute.item.settings.name'),
|
id: getTrad('form.attribute.item.settings.name'),
|
||||||
defaultMessage: 'Settings',
|
defaultMessage: 'Settings',
|
||||||
},
|
},
|
||||||
items: [options.required, options.unique, options.private],
|
items: [options.required, options.private],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -209,7 +209,7 @@ const advancedForm = {
|
|||||||
id: getTrad('form.attribute.item.settings.name'),
|
id: getTrad('form.attribute.item.settings.name'),
|
||||||
defaultMessage: 'Settings',
|
defaultMessage: 'Settings',
|
||||||
},
|
},
|
||||||
items: [options.required, options.unique, options.private],
|
items: [options.required, options.private],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -222,7 +222,7 @@ const advancedForm = {
|
|||||||
id: getTrad('form.attribute.item.settings.name'),
|
id: getTrad('form.attribute.item.settings.name'),
|
||||||
defaultMessage: 'Settings',
|
defaultMessage: 'Settings',
|
||||||
},
|
},
|
||||||
items: [options.required, options.unique, options.private],
|
items: [options.required, options.private],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -297,13 +297,7 @@ const advancedForm = {
|
|||||||
id: getTrad('form.attribute.item.settings.name'),
|
id: getTrad('form.attribute.item.settings.name'),
|
||||||
defaultMessage: 'Settings',
|
defaultMessage: 'Settings',
|
||||||
},
|
},
|
||||||
items: [
|
items: [options.required, options.maxLength, options.minLength, options.private],
|
||||||
options.required,
|
|
||||||
options.unique,
|
|
||||||
options.maxLength,
|
|
||||||
options.minLength,
|
|
||||||
options.private,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -330,13 +324,7 @@ const advancedForm = {
|
|||||||
id: getTrad('form.attribute.item.settings.name'),
|
id: getTrad('form.attribute.item.settings.name'),
|
||||||
defaultMessage: 'Settings',
|
defaultMessage: 'Settings',
|
||||||
},
|
},
|
||||||
items: [
|
items: [options.required, options.maxLength, options.minLength, options.private],
|
||||||
options.required,
|
|
||||||
options.unique,
|
|
||||||
options.maxLength,
|
|
||||||
options.minLength,
|
|
||||||
options.private,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -51,7 +51,6 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
|
|||||||
return {
|
return {
|
||||||
multiple: yup.boolean(),
|
multiple: yup.boolean(),
|
||||||
required: validators.required,
|
required: validators.required,
|
||||||
unique: validators.unique,
|
|
||||||
allowedTypes: yup
|
allowedTypes: yup
|
||||||
.array()
|
.array()
|
||||||
.of(yup.string().oneOf(['images', 'videos', 'files']))
|
.of(yup.string().oneOf(['images', 'videos', 'files']))
|
||||||
@ -129,7 +128,6 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
|
|||||||
return {
|
return {
|
||||||
default: yup.mixed().test(isValidDefaultJSON),
|
default: yup.mixed().test(isValidDefaultJSON),
|
||||||
required: validators.required,
|
required: validators.required,
|
||||||
unique: validators.unique,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'enumeration': {
|
case 'enumeration': {
|
||||||
@ -148,7 +146,6 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
|
|||||||
default: yup.string().when('enum', enumVal => yup.string().oneOf(enumVal)),
|
default: yup.string().when('enum', enumVal => yup.string().oneOf(enumVal)),
|
||||||
enumName: yup.string().test(isValidName),
|
enumName: yup.string().test(isValidName),
|
||||||
required: validators.required,
|
required: validators.required,
|
||||||
unique: validators.unique,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'password': {
|
case 'password': {
|
||||||
@ -225,7 +222,6 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
|
|||||||
return {
|
return {
|
||||||
default: yup.boolean(),
|
default: yup.boolean(),
|
||||||
required: validators.required,
|
required: validators.required,
|
||||||
unique: validators.unique,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -202,9 +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(
|
||||||
isDraft,
|
model,
|
||||||
});
|
data,
|
||||||
|
{
|
||||||
|
isDraft,
|
||||||
|
},
|
||||||
|
entityToUpdate
|
||||||
|
);
|
||||||
|
|
||||||
const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
|
const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -12,8 +12,11 @@ const { yup, validateYupSchema } = strapiUtils;
|
|||||||
const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
|
const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
|
||||||
const { ValidationError } = strapiUtils.errors;
|
const { ValidationError } = strapiUtils.errors;
|
||||||
|
|
||||||
const addMinMax = (attr, validator, data) => {
|
const addMinMax = (validator, { attr, updatedAttribute }) => {
|
||||||
if (Number.isInteger(attr.min) && (attr.required || (Array.isArray(data) && data.length > 0))) {
|
if (
|
||||||
|
Number.isInteger(attr.min) &&
|
||||||
|
(attr.required || (Array.isArray(updatedAttribute.value) && updatedAttribute.value.length > 0))
|
||||||
|
) {
|
||||||
validator = validator.min(attr.min);
|
validator = validator.min(attr.min);
|
||||||
}
|
}
|
||||||
if (Number.isInteger(attr.max)) {
|
if (Number.isInteger(attr.max)) {
|
||||||
@ -22,7 +25,7 @@ const addMinMax = (attr, validator, data) => {
|
|||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addRequiredValidation = createOrUpdate => (required, validator) => {
|
const addRequiredValidation = createOrUpdate => (validator, { attr: { required } }) => {
|
||||||
if (required) {
|
if (required) {
|
||||||
if (createOrUpdate === 'creation') {
|
if (createOrUpdate === 'creation') {
|
||||||
validator = validator.notNil();
|
validator = validator.notNil();
|
||||||
@ -35,7 +38,7 @@ const addRequiredValidation = createOrUpdate => (required, validator) => {
|
|||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addDefault = createOrUpdate => (attr, validator) => {
|
const addDefault = createOrUpdate => (validator, { attr }) => {
|
||||||
if (createOrUpdate === 'creation') {
|
if (createOrUpdate === 'creation') {
|
||||||
if (
|
if (
|
||||||
((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
|
((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
|
||||||
@ -54,7 +57,7 @@ const addDefault = createOrUpdate => (attr, validator) => {
|
|||||||
|
|
||||||
const preventCast = validator => validator.transform((val, originalVal) => originalVal);
|
const preventCast = validator => validator.transform((val, originalVal) => originalVal);
|
||||||
|
|
||||||
const createComponentValidator = createOrUpdate => (attr, data, { isDraft }) => {
|
const createComponentValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
|
||||||
let validator;
|
let validator;
|
||||||
|
|
||||||
const model = strapi.getModel(attr.component);
|
const model = strapi.getModel(attr.component);
|
||||||
@ -66,19 +69,23 @@ const createComponentValidator = createOrUpdate => (attr, data, { isDraft }) =>
|
|||||||
validator = yup
|
validator = yup
|
||||||
.array()
|
.array()
|
||||||
.of(
|
.of(
|
||||||
yup.lazy(item => createModelValidator(createOrUpdate)(model, item, { isDraft }).notNull())
|
yup.lazy(item =>
|
||||||
|
createModelValidator(createOrUpdate)({ model, data: item }, { isDraft }).notNull()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
validator = addRequiredValidation(createOrUpdate)(true, validator);
|
validator = addRequiredValidation(createOrUpdate)(validator, { attr: { required: true } });
|
||||||
validator = addMinMax(attr, validator, data);
|
validator = addMinMax(validator, { attr, updatedAttribute });
|
||||||
} else {
|
} else {
|
||||||
validator = createModelValidator(createOrUpdate)(model, data, { isDraft });
|
validator = createModelValidator(createOrUpdate)({ model, updatedAttribute }, { isDraft });
|
||||||
validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
|
validator = addRequiredValidation(createOrUpdate)(validator, {
|
||||||
|
attr: { required: !isDraft && attr.required },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDzValidator = createOrUpdate => (attr, data, { isDraft }) => {
|
const createDzValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
|
||||||
let validator;
|
let validator;
|
||||||
|
|
||||||
validator = yup.array().of(
|
validator = yup.array().of(
|
||||||
@ -95,76 +102,85 @@ const createDzValidator = createOrUpdate => (attr, data, { isDraft }) => {
|
|||||||
.notNull();
|
.notNull();
|
||||||
|
|
||||||
return model
|
return model
|
||||||
? schema.concat(createModelValidator(createOrUpdate)(model, item, { isDraft }))
|
? schema.concat(createModelValidator(createOrUpdate)({ model, data: item }, { isDraft }))
|
||||||
: schema;
|
: schema;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
validator = addRequiredValidation(createOrUpdate)(true, validator);
|
validator = addRequiredValidation(createOrUpdate)(validator, { attr: { required: true } });
|
||||||
validator = addMinMax(attr, validator, data);
|
validator = addMinMax(validator, { attr, updatedAttribute });
|
||||||
|
|
||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createRelationValidator = createOrUpdate => (attr, data, { isDraft }) => {
|
const createRelationValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
|
||||||
let validator;
|
let validator;
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(updatedAttribute.value)) {
|
||||||
validator = yup.array().of(yup.mixed());
|
validator = yup.array().of(yup.mixed());
|
||||||
} else {
|
} else {
|
||||||
validator = yup.mixed();
|
validator = yup.mixed();
|
||||||
}
|
}
|
||||||
validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
|
|
||||||
|
validator = addRequiredValidation(createOrUpdate)(validator, {
|
||||||
|
attr: { required: !isDraft && attr.required },
|
||||||
|
});
|
||||||
|
|
||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createScalarAttributeValidator = createOrUpdate => (attr, { isDraft }) => {
|
const createScalarAttributeValidator = createOrUpdate => (metas, options) => {
|
||||||
let validator;
|
let validator;
|
||||||
|
|
||||||
if (has(attr.type, validators)) {
|
if (has(metas.attr.type, validators)) {
|
||||||
validator = validators[attr.type](attr, { isDraft });
|
validator = validators[metas.attr.type](metas, options);
|
||||||
} else {
|
} else {
|
||||||
// No validators specified - fall back to mixed
|
// No validators specified - fall back to mixed
|
||||||
validator = yup.mixed();
|
validator = yup.mixed();
|
||||||
}
|
}
|
||||||
|
|
||||||
validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
|
validator = addRequiredValidation(createOrUpdate)(validator, {
|
||||||
|
attr: { required: !options.isDraft && metas.attr.required },
|
||||||
|
});
|
||||||
|
|
||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createAttributeValidator = createOrUpdate => (attr, data, { isDraft }) => {
|
const createAttributeValidator = createOrUpdate => (metas, options) => {
|
||||||
let validator;
|
let validator;
|
||||||
|
|
||||||
if (isMediaAttribute(attr)) {
|
if (isMediaAttribute(metas.attr)) {
|
||||||
validator = yup.mixed();
|
validator = yup.mixed();
|
||||||
} else if (isScalarAttribute(attr)) {
|
} else if (isScalarAttribute(metas.attr)) {
|
||||||
validator = createScalarAttributeValidator(createOrUpdate)(attr, { isDraft });
|
validator = createScalarAttributeValidator(createOrUpdate)(metas, options);
|
||||||
} else {
|
} else {
|
||||||
if (attr.type === 'component') {
|
if (metas.attr.type === 'component') {
|
||||||
validator = createComponentValidator(createOrUpdate)(attr, data, { isDraft });
|
validator = createComponentValidator(createOrUpdate)(metas, options);
|
||||||
} else if (attr.type === 'dynamiczone') {
|
} else if (metas.attr.type === 'dynamiczone') {
|
||||||
validator = createDzValidator(createOrUpdate)(attr, data, { isDraft });
|
validator = createDzValidator(createOrUpdate)(metas, options);
|
||||||
} else {
|
} else {
|
||||||
validator = createRelationValidator(createOrUpdate)(attr, data, { isDraft });
|
validator = createRelationValidator(createOrUpdate)(metas, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
validator = preventCast(validator);
|
validator = preventCast(validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
validator = addDefault(createOrUpdate)(attr, validator);
|
validator = addDefault(createOrUpdate)(validator, metas);
|
||||||
|
|
||||||
return validator;
|
return validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createModelValidator = createOrUpdate => (model, data, { isDraft }) => {
|
const createModelValidator = createOrUpdate => ({ model, data, entity }, options) => {
|
||||||
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],
|
{
|
||||||
prop(attributeName, data),
|
attr: model.attributes[attributeName],
|
||||||
{ isDraft }
|
updatedAttribute: { name: attributeName, value: prop(attributeName, data) },
|
||||||
|
model,
|
||||||
|
entity,
|
||||||
|
},
|
||||||
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
return assoc(attributeName, validator)(validators);
|
return assoc(attributeName, validator)(validators);
|
||||||
@ -173,7 +189,12 @@ const createModelValidator = createOrUpdate => (model, data, { isDraft }) => {
|
|||||||
return yup.object().shape(schema);
|
return yup.object().shape(schema);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createValidateEntity = createOrUpdate => async (model, data, { isDraft = false } = {}) => {
|
const createValidateEntity = createOrUpdate => async (
|
||||||
|
model,
|
||||||
|
data,
|
||||||
|
{ isDraft = false } = {},
|
||||||
|
entity = null
|
||||||
|
) => {
|
||||||
if (!isObject(data)) {
|
if (!isObject(data)) {
|
||||||
const { displayName } = model.info;
|
const { displayName } = model.info;
|
||||||
|
|
||||||
@ -182,7 +203,14 @@ const createValidateEntity = createOrUpdate => async (model, data, { isDraft = f
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validator = createModelValidator(createOrUpdate)(model, data, { isDraft }).required();
|
const validator = createModelValidator(createOrUpdate)(
|
||||||
|
{
|
||||||
|
model,
|
||||||
|
data,
|
||||||
|
entity,
|
||||||
|
},
|
||||||
|
{ isDraft }
|
||||||
|
).required();
|
||||||
return validateYupSchema(validator, { strict: false, abortEarly: false })(data);
|
return validateYupSchema(validator, { strict: false, abortEarly: false })(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,72 +4,155 @@ const _ = require('lodash');
|
|||||||
|
|
||||||
const { yup } = require('@strapi/utils');
|
const { yup } = require('@strapi/utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('yup').StringSchema} StringSchema
|
||||||
|
* @type {import('yup').NumberSchema} NumberSchema
|
||||||
|
* @type {import('yup').AnySchema} AnySchema
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to compose validators
|
* Utility function to compose validators
|
||||||
*/
|
*/
|
||||||
const composeValidators = (...fns) => (attr, { isDraft }) => {
|
const composeValidators = (...fns) => (...args) => {
|
||||||
return fns.reduce((validator, fn) => {
|
let validator = yup.mixed();
|
||||||
return fn(attr, validator, { isDraft });
|
|
||||||
}, yup.mixed());
|
// if we receive a schema then use it as base schema for nested composition
|
||||||
|
if (yup.isSchema(args[0])) {
|
||||||
|
validator = args[0];
|
||||||
|
args = args.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fns.reduce((validator, fn) => fn(validator, ...args), validator);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Validator utils */
|
/* Validator utils */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds minLength validator
|
* Adds minLength validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {StringSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas
|
||||||
|
* @param {{ minLength: Number }} metas.attr model attribute
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {boolean} options.isDraft
|
||||||
|
*
|
||||||
|
* @returns {StringSchema}
|
||||||
*/
|
*/
|
||||||
const addMinLengthValidator = ({ minLength }, validator, { isDraft }) =>
|
const addMinLengthValidator = (validator, { attr }, { isDraft }) =>
|
||||||
_.isInteger(minLength) && !isDraft ? validator.min(minLength) : validator;
|
_.isInteger(attr.minLength) && !isDraft ? validator.min(attr.minLength) : validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds maxLength validator
|
* Adds maxLength validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {StringSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas
|
||||||
|
* @param {{ maxLength: Number }} metas.attr model attribute
|
||||||
|
*
|
||||||
|
* @returns {StringSchema}
|
||||||
*/
|
*/
|
||||||
const addMaxLengthValidator = ({ maxLength }, validator) =>
|
const addMaxLengthValidator = (validator, { attr }) =>
|
||||||
_.isInteger(maxLength) ? validator.max(maxLength) : validator;
|
_.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds min integer validator
|
* Adds min integer validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {NumberSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas
|
||||||
|
* @param {{ min: Number }} metas.attr model attribute
|
||||||
|
*
|
||||||
|
* @returns {NumberSchema}
|
||||||
*/
|
*/
|
||||||
const addMinIntegerValidator = ({ min }, validator) =>
|
const addMinIntegerValidator = (validator, { attr }) =>
|
||||||
_.isNumber(min) ? validator.min(_.toInteger(min)) : validator;
|
_.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds max integer validator
|
* Adds max integer validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {NumberSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas
|
||||||
|
* @param {{ max: Number }} metas.attr model attribute
|
||||||
|
*
|
||||||
|
* @returns {NumberSchema}
|
||||||
*/
|
*/
|
||||||
const addMaxIntegerValidator = ({ max }, validator) =>
|
const addMaxIntegerValidator = (validator, { attr }) =>
|
||||||
_.isNumber(max) ? validator.max(_.toInteger(max)) : validator;
|
_.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds min float/decimal validator
|
* Adds min float/decimal validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {NumberSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas
|
||||||
|
* @param {{ min: Number }} metas.attr model attribute
|
||||||
|
*
|
||||||
|
* @returns {NumberSchema}
|
||||||
*/
|
*/
|
||||||
const addMinFloatValidator = ({ min }, validator) =>
|
const addMinFloatValidator = (validator, { attr }) =>
|
||||||
_.isNumber(min) ? validator.min(min) : validator;
|
_.isNumber(attr.min) ? validator.min(attr.min) : validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds max float/decimal validator
|
* Adds max float/decimal validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {NumberSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas model attribute
|
||||||
|
* @param {{ max: Number }} metas.attr
|
||||||
|
*
|
||||||
|
* @returns {NumberSchema}
|
||||||
*/
|
*/
|
||||||
const addMaxFloatValidator = ({ max }, validator) =>
|
const addMaxFloatValidator = (validator, { attr }) =>
|
||||||
_.isNumber(max) ? validator.max(max) : validator;
|
_.isNumber(attr.max) ? validator.max(attr.max) : validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds regex validator
|
* Adds regex validator
|
||||||
* @param {Object} attribute model attribute
|
* @param {StringSchema} validator yup validator
|
||||||
* @param {Object} validator yup validator
|
* @param {Object} metas model attribute
|
||||||
|
* @param {{ regex: RegExp }} metas.attr
|
||||||
|
*
|
||||||
|
* @returns {StringSchema}
|
||||||
*/
|
*/
|
||||||
const addStringRegexValidator = ({ regex }, validator) =>
|
const addStringRegexValidator = (validator, { attr }) =>
|
||||||
_.isUndefined(regex) ? validator : validator.matches(new RegExp(regex));
|
_.isUndefined(attr.regex) ? validator : validator.matches(new RegExp(attr.regex));
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {AnySchema} validator
|
||||||
|
* @param {Object} metas
|
||||||
|
* @param {{ unique: Boolean, type: String }} metas.attr
|
||||||
|
* @param {{ uid: String }} metas.model
|
||||||
|
* @param {{ name: String, value: any }} metas.updatedAttribute
|
||||||
|
* @param {Object} metas.entity
|
||||||
|
*
|
||||||
|
* @returns {AnySchema}
|
||||||
|
*/
|
||||||
|
const addUniqueValidator = (validator, { attr, model, updatedAttribute, entity }) => {
|
||||||
|
if (!attr.unique && attr.type !== 'uid') {
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validator.test('unique', 'This attribute must be unique', async value => {
|
||||||
|
/**
|
||||||
|
* If the attribute value is `null` we want to skip the unique validation.
|
||||||
|
* Otherwise it'll only accept a single `null` entry in the database.
|
||||||
|
*/
|
||||||
|
if (updatedAttribute.value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 && updatedAttribute.value === entity[updatedAttribute.name]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let whereParams = entity
|
||||||
|
? { $and: [{ [updatedAttribute.name]: value }, { $not: { id: entity.id } }] }
|
||||||
|
: { [updatedAttribute.name]: value };
|
||||||
|
|
||||||
|
const record = await strapi.db.query(model.uid).findOne({
|
||||||
|
select: ['id'],
|
||||||
|
where: whereParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return !record;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/* Type validators */
|
/* Type validators */
|
||||||
|
|
||||||
@ -77,29 +160,32 @@ const stringValidator = composeValidators(
|
|||||||
() => yup.string().transform((val, originalVal) => originalVal),
|
() => yup.string().transform((val, originalVal) => originalVal),
|
||||||
addMinLengthValidator,
|
addMinLengthValidator,
|
||||||
addMaxLengthValidator,
|
addMaxLengthValidator,
|
||||||
addStringRegexValidator
|
addStringRegexValidator,
|
||||||
|
addUniqueValidator
|
||||||
);
|
);
|
||||||
|
|
||||||
const emailValidator = composeValidators(stringValidator, (attr, validator) => validator.email());
|
const emailValidator = composeValidators(stringValidator, validator => validator.email());
|
||||||
|
|
||||||
const uidValidator = composeValidators(stringValidator, (attr, validator) =>
|
const uidValidator = composeValidators(stringValidator, validator =>
|
||||||
validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
|
validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
|
||||||
);
|
);
|
||||||
|
|
||||||
const enumerationValidator = attr => {
|
const enumerationValidator = ({ attr }) => {
|
||||||
return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
|
return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
|
||||||
};
|
};
|
||||||
|
|
||||||
const integerValidator = composeValidators(
|
const integerValidator = composeValidators(
|
||||||
() => yup.number().integer(),
|
() => yup.number().integer(),
|
||||||
addMinIntegerValidator,
|
addMinIntegerValidator,
|
||||||
addMaxIntegerValidator
|
addMaxIntegerValidator,
|
||||||
|
addUniqueValidator
|
||||||
);
|
);
|
||||||
|
|
||||||
const floatValidator = composeValidators(
|
const floatValidator = composeValidators(
|
||||||
() => yup.number(),
|
() => yup.number(),
|
||||||
addMinFloatValidator,
|
addMinFloatValidator,
|
||||||
addMaxFloatValidator
|
addMaxFloatValidator,
|
||||||
|
addUniqueValidator
|
||||||
);
|
);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -113,11 +199,11 @@ module.exports = {
|
|||||||
uid: uidValidator,
|
uid: uidValidator,
|
||||||
json: () => yup.mixed(),
|
json: () => yup.mixed(),
|
||||||
integer: integerValidator,
|
integer: integerValidator,
|
||||||
biginteger: () => yup.mixed(),
|
biginteger: composeValidators(addUniqueValidator),
|
||||||
float: floatValidator,
|
float: floatValidator,
|
||||||
decimal: floatValidator,
|
decimal: floatValidator,
|
||||||
date: () => yup.mixed(),
|
date: composeValidators(addUniqueValidator),
|
||||||
time: () => yup.mixed(),
|
time: composeValidators(addUniqueValidator),
|
||||||
datetime: () => yup.mixed(),
|
datetime: composeValidators(addUniqueValidator),
|
||||||
timestamp: () => yup.mixed(),
|
timestamp: composeValidators(addUniqueValidator),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -159,7 +159,7 @@ describe('Migration - draft and publish', () => {
|
|||||||
expect(body.results[0].publishedAt).toBeUndefined();
|
expect(body.results[0].publishedAt).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('Unique constraint is kept after disabling the feature', async () => {
|
test('Unique constraint is kept after disabling the feature', async () => {
|
||||||
const dogToCreate = { code: 'sameCode' };
|
const dogToCreate = { code: 'sameCode' };
|
||||||
|
|
||||||
let res = await rq({
|
let res = await rq({
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const restart = async () => {
|
|||||||
rq = await createAuthRequest({ strapi });
|
rq = await createAuthRequest({ strapi });
|
||||||
};
|
};
|
||||||
|
|
||||||
describe.skip('Migration - unique attribute', () => {
|
describe('Migration - unique attribute', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await builder
|
await builder
|
||||||
.addContentType(dogModel)
|
.addContentType(dogModel)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user