'use strict'; var strapiAdmin = require('@strapi/admin/strapi-admin'); var pipe = require('lodash/fp/pipe'); var yup = require('yup'); var attributes = require('../constants/attributes.js'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var yup__namespace = /*#__PURE__*/_interopNamespaceDefault(yup); const arrayValidator = (attribute, options)=>({ message: strapiAdmin.translatedErrors.required, test (value) { if (options.status === 'draft') { return true; } if (!attribute.required) { return true; } if (!value) { return false; } if (Array.isArray(value) && value.length === 0) { return false; } return true; } }); /** * TODO: should we create a Map to store these based on the hash of the schema? */ const createYupSchema = (attributes$1 = {}, components = {}, options = { status: null })=>{ const createModelSchema = (attributes$1)=>yup__namespace.object().shape(Object.entries(attributes$1).reduce((acc, [name, attribute])=>{ if (attributes.DOCUMENT_META_FIELDS.includes(name)) { return acc; } /** * These validations won't apply to every attribute * and that's okay, in that case we just return the * schema as it was passed. */ const validations = [ addNullableValidation, addRequiredValidation, addMinLengthValidation, addMaxLengthValidation, addMinValidation, addMaxValidation, addRegexValidation ].map((fn)=>fn(attribute, options)); const transformSchema = pipe(...validations); switch(attribute.type){ case 'component': { const { attributes } = components[attribute.component]; if (attribute.repeatable) { return { ...acc, [name]: transformSchema(yup__namespace.array().of(createModelSchema(attributes).nullable(false))).test(arrayValidator(attribute, options)) }; } else { return { ...acc, [name]: transformSchema(createModelSchema(attributes).nullable()) }; } } case 'dynamiczone': return { ...acc, [name]: transformSchema(yup__namespace.array().of(yup__namespace.lazy((data)=>{ const attributes = components?.[data?.__component]?.attributes; const validation = yup__namespace.object().shape({ __component: yup__namespace.string().required().oneOf(Object.keys(components)) }).nullable(false); if (!attributes) { return validation; } return validation.concat(createModelSchema(attributes)); }))).test(arrayValidator(attribute, options)) }; case 'relation': return { ...acc, [name]: transformSchema(yup__namespace.lazy((value)=>{ if (!value) { return yup__namespace.mixed().nullable(true); } else if (Array.isArray(value)) { // If a relation value is an array, we expect // an array of objects with {id} properties, representing the related entities. return yup__namespace.array().of(yup__namespace.object().shape({ id: yup__namespace.number().required() })); } else if (typeof value === 'object') { // A realtion value can also be an object. Some API // repsonses return the number of entities in the relation // as { count: x } return yup__namespace.object(); } else { return yup__namespace.mixed().test('type-error', 'Relation values must be either null, an array of objects with {id} or an object.', ()=>false); } })) }; default: return { ...acc, [name]: transformSchema(createAttributeSchema(attribute)) }; } }, {}))/** * TODO: investigate why an undefined object fails a check of `nullable`. */ .default(null); return createModelSchema(attributes$1); }; const createAttributeSchema = (attribute)=>{ switch(attribute.type){ case 'biginteger': return yup__namespace.string().matches(/^-?\d*$/); case 'boolean': return yup__namespace.boolean(); case 'blocks': return yup__namespace.mixed().test('isBlocks', strapiAdmin.translatedErrors.json, (value)=>{ if (!value || Array.isArray(value)) { return true; } else { return false; } }); case 'decimal': case 'float': case 'integer': return yup__namespace.number(); case 'email': return yup__namespace.string().email(strapiAdmin.translatedErrors.email); case 'enumeration': return yup__namespace.string().oneOf([ ...attribute.enum, null ]); case 'json': return yup__namespace.mixed().test('isJSON', strapiAdmin.translatedErrors.json, (value)=>{ /** * We don't want to validate the JSON field if it's empty. */ if (!value || typeof value === 'string' && value.length === 0) { return true; } // If the value was created via content API and wasn't changed, then it's still an object if (typeof value === 'object') { try { JSON.stringify(value); return true; } catch (err) { return false; } } try { JSON.parse(value); return true; } catch (err) { return false; } }); case 'password': case 'richtext': case 'string': case 'text': return yup__namespace.string(); case 'uid': return yup__namespace.string().matches(/^[A-Za-z0-9-_.~]*$/); default: /** * This allows any value. */ return yup__namespace.mixed(); } }; // Helper function to return schema.nullable() if it exists, otherwise return schema const nullableSchema = (schema)=>{ return schema?.nullable ? schema.nullable() : // e.g. when the schema has been built using yup.lazy (e.g. for relations). // In these cases we should just return the schema as it is. schema; }; const addNullableValidation = ()=>(schema)=>{ return nullableSchema(schema); }; const addRequiredValidation = (attribute, options)=>(schema)=>{ if (options.status === 'draft' || !attribute.required) { return schema; } if (attribute.required && 'required' in schema) { return schema.required(strapiAdmin.translatedErrors.required); } return schema; }; const addMinLengthValidation = (attribute, options)=>(schema)=>{ // Skip minLength validation for draft if (options.status === 'draft') { return schema; } if ('minLength' in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && 'min' in schema) { return schema.min(attribute.minLength, { ...strapiAdmin.translatedErrors.minLength, values: { min: attribute.minLength } }); } return schema; }; const addMaxLengthValidation = (attribute)=>(schema)=>{ if ('maxLength' in attribute && attribute.maxLength && Number.isInteger(attribute.maxLength) && 'max' in schema) { return schema.max(attribute.maxLength, { ...strapiAdmin.translatedErrors.maxLength, values: { max: attribute.maxLength } }); } return schema; }; const addMinValidation = (attribute, options)=>(schema)=>{ // do not validate min for draft if (options.status === 'draft') { return schema; } if ('min' in attribute && 'min' in schema) { const min = toInteger(attribute.min); if (min) { return schema.min(min, { ...strapiAdmin.translatedErrors.min, values: { min } }); } } return schema; }; const addMaxValidation = (attribute)=>(schema)=>{ if ('max' in attribute) { const max = toInteger(attribute.max); if ('max' in schema && max) { return schema.max(max, { ...strapiAdmin.translatedErrors.max, values: { max } }); } } return schema; }; const toInteger = (val)=>{ if (typeof val === 'number' || val === undefined) { return val; } else { const num = Number(val); return isNaN(num) ? undefined : num; } }; const addRegexValidation = (attribute)=>(schema)=>{ if ('regex' in attribute && attribute.regex && 'matches' in schema) { return schema.matches(new RegExp(attribute.regex), { message: { id: strapiAdmin.translatedErrors.regex.id, defaultMessage: 'The value does not match the defined pattern.' }, excludeEmptyString: !attribute.required }); } return schema; }; exports.createYupSchema = createYupSchema; //# sourceMappingURL=validation.js.map