UNPKG

@r1tsu/payload

Version:

459 lines (458 loc) 16.3 kB
import Ajv from 'ajv'; import { isNumber } from '../utilities/isNumber.js'; import { isValidID } from '../utilities/isValidID.js'; export const text = (value, { hasMany, maxLength: fieldMaxLength, maxRows, minLength, minRows, req: { payload: { config }, t }, required })=>{ let maxLength; if (!required) { if (!value) return true; } if (hasMany === true) { const lengthValidationResult = validateArrayLength(value, { maxRows, minRows, required, t }); if (typeof lengthValidationResult === 'string') return lengthValidationResult; } if (typeof config?.defaultMaxTextLength === 'number') maxLength = config.defaultMaxTextLength; if (typeof fieldMaxLength === 'number') maxLength = fieldMaxLength; const stringsToValidate = Array.isArray(value) ? value : [ value ]; for (const stringValue of stringsToValidate){ const length = stringValue?.length || 0; if (typeof maxLength === 'number' && length > maxLength) { return t('validation:shorterThanMax', { label: t('value'), maxLength, stringValue }); } if (typeof minLength === 'number' && length < minLength) { return t('validation:longerThanMin', { label: t('value'), minLength, stringValue }); } } if (required) { if (!(typeof value === 'string' || Array.isArray(value)) || value?.length === 0) { return t('validation:required'); } } return true; }; export const password = (value, { maxLength: fieldMaxLength, minLength, req: { payload: { config }, t }, required })=>{ let maxLength; if (typeof config?.defaultMaxTextLength === 'number') maxLength = config.defaultMaxTextLength; if (typeof fieldMaxLength === 'number') maxLength = fieldMaxLength; if (value && maxLength && value.length > maxLength) { return t('validation:shorterThanMax', { maxLength }); } if (value && minLength && value.length < minLength) { return t('validation:longerThanMin', { minLength }); } if (required && !value) { return t('validation:required'); } return true; }; export const email = (value, { req: { t }, required })=>{ if (value && !/\S[^\s@]*@\S+\.\S+/.test(value) || !value && required) { return t('validation:emailAddress'); } return true; }; export const textarea = (value, { maxLength: fieldMaxLength, minLength, req: { payload: { config }, t }, required })=>{ let maxLength; if (typeof config?.defaultMaxTextLength === 'number') maxLength = config.defaultMaxTextLength; if (typeof fieldMaxLength === 'number') maxLength = fieldMaxLength; if (value && maxLength && value.length > maxLength) { return t('validation:shorterThanMax', { maxLength }); } if (value && minLength && value.length < minLength) { return t('validation:longerThanMin', { minLength }); } if (required && !value) { return t('validation:required'); } return true; }; export const code = (value, { req: { t }, required })=>{ if (required && value === undefined) { return t('validation:required'); } return true; }; export const json = async (value, { jsonError, jsonSchema, req: { t }, required })=>{ const isNotEmpty = (value)=>{ if (value === undefined || value === null) { return false; } if (Array.isArray(value) && value.length === 0) { return false; } if (typeof value === 'object' && Object.keys(value).length === 0) { return false; } return true; }; const fetchSchema = ({ schema, uri })=>{ if (uri && schema) return schema; // @ts-expect-error return fetch(uri).then((response)=>{ if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }).then((json)=>{ const jsonSchemaSanitizations = { id: undefined, $id: json.id, $schema: 'http://json-schema.org/draft-07/schema#' }; return Object.assign(json, jsonSchemaSanitizations); }); }; if (required && !value) { return t('validation:required'); } if (jsonError !== undefined) { return t('validation:invalidInput'); } if (jsonSchema && isNotEmpty(value)) { try { jsonSchema.schema = await fetchSchema(jsonSchema); const { schema } = jsonSchema; // @ts-expect-error const ajv = new Ajv(); if (!ajv.validate(schema, value)) { return t(ajv.errorsText()); } } catch (error) { return t(error.message); } } return true; }; export const checkbox = (value, { req: { t }, required })=>{ if (value && typeof value !== 'boolean' || required && typeof value !== 'boolean') { return t('validation:trueOrFalse'); } return true; }; export const date = (value, { req: { t }, required })=>{ if (value && !isNaN(Date.parse(value.toString()))) { /* eslint-disable-line */ return true; } if (value) { return t('validation:notValidDate', { value }); } if (required) { return t('validation:required'); } return true; }; export const richText = async (value, options)=>{ const editor = options?.editor; return editor.validate(value, options); }; const validateArrayLength = (value, options)=>{ const { maxRows, minRows, required, t } = options; const arrayLength = Array.isArray(value) ? value.length : value || 0; if (!required && arrayLength === 0) return true; if (minRows && arrayLength < minRows) { return t('validation:requiresAtLeast', { count: minRows, label: t('general:rows') }); } if (maxRows && arrayLength > maxRows) { return t('validation:requiresNoMoreThan', { count: maxRows, label: t('general:rows') }); } if (required && !arrayLength) { return t('validation:requiresAtLeast', { count: 1, label: t('general:row') }); } return true; }; export const number = (value, { hasMany, max, maxRows, min, minRows, req: { t }, required })=>{ if (hasMany === true) { const lengthValidationResult = validateArrayLength(value, { maxRows, minRows, required, t }); if (typeof lengthValidationResult === 'string') return lengthValidationResult; } if (!value && !isNumber(value)) { // if no value is present, validate based on required if (required) return t('validation:required'); if (!required) return true; } const numbersToValidate = Array.isArray(value) ? value : [ value ]; for (const number of numbersToValidate){ if (!isNumber(number)) return t('validation:enterNumber'); const numberValue = parseFloat(number); if (typeof max === 'number' && numberValue > max) { return t('validation:greaterThanMax', { label: t('general:value'), max, value }); } if (typeof min === 'number' && numberValue < min) { return t('validation:lessThanMin', { label: t('general:value'), min, value }); } } return true; }; export const array = (value, { maxRows, minRows, req: { t }, required })=>{ return validateArrayLength(value, { maxRows, minRows, required, t }); }; export const blocks = (value, { maxRows, minRows, req: { t }, required })=>{ return validateArrayLength(value, { maxRows, minRows, required, t }); }; const validateFilterOptions = async (value, { id, data, filterOptions, relationTo, req, req: { payload, t, user }, siblingData })=>{ if (typeof filterOptions !== 'undefined' && value) { const options = {}; const falseCollections = []; const collections = typeof relationTo === 'string' ? [ relationTo ] : relationTo; const values = Array.isArray(value) ? value : [ value ]; for (const collection of collections){ try { let optionFilter = typeof filterOptions === 'function' ? await filterOptions({ id, data, relationTo: collection, siblingData, user }) : filterOptions; if (optionFilter === true) { optionFilter = null; } const valueIDs = []; values.forEach((val)=>{ if (typeof val === 'object' && val?.value) { valueIDs.push(val.value); } if (typeof val === 'string' || typeof val === 'number') { valueIDs.push(val); } }); if (valueIDs.length > 0) { const findWhere = { and: [ { id: { in: valueIDs } } ] }; if (optionFilter && optionFilter !== true) findWhere.and.push(optionFilter); if (optionFilter === false) { falseCollections.push(collection); } const result = await payload.find({ collection, depth: 0, limit: 0, pagination: false, req, where: findWhere }); options[collection] = result.docs.map((doc)=>doc.id); } else { options[collection] = []; } } catch (err) { req.payload.logger.error({ err, msg: `Error validating filter options for collection ${collection}` }); options[collection] = []; } } const invalidRelationships = values.filter((val)=>{ let collection; let requestedID; if (typeof relationTo === 'string') { collection = relationTo; if (typeof val === 'string' || typeof val === 'number') { requestedID = val; } } if (Array.isArray(relationTo) && typeof val === 'object' && val?.relationTo) { collection = val.relationTo; requestedID = val.value; } if (falseCollections.find((slug)=>relationTo === slug)) { return true; } if (!options[collection]) return true; return options[collection].indexOf(requestedID) === -1; }); if (invalidRelationships.length > 0) { return invalidRelationships.reduce((err, invalid, i)=>{ return `${err} ${JSON.stringify(invalid)}${invalidRelationships.length === i + 1 ? ',' : ''} `; }, t('validation:invalidSelections')); } return true; } return true; }; export const upload = (value, options)=>{ if (!value && options.required) { return options?.req?.t('validation:required'); } if (typeof value !== 'undefined' && value !== null) { const idType = options?.req?.payload?.collections[options.relationTo]?.customIDType || options?.req?.payload?.db?.defaultIDType; if (!isValidID(value, idType)) { return options.req?.t('validation:validUploadID'); } } return validateFilterOptions(value, options); }; export const relationship = async (value, options)=>{ const { maxRows, minRows, relationTo, req: { payload, t }, required } = options; if ((!value || Array.isArray(value) && value.length === 0) && required) { return t('validation:required'); } if (Array.isArray(value) && value.length > 0) { if (minRows && value.length < minRows) { return t('validation:lessThanMin', { label: t('general:rows'), min: minRows, value: value.length }); } if (maxRows && value.length > maxRows) { return t('validation:greaterThanMax', { label: t('general:rows'), max: maxRows, value: value.length }); } } if (typeof value !== 'undefined' && value !== null) { const values = Array.isArray(value) ? value : [ value ]; const invalidRelationships = values.filter((val)=>{ let collectionSlug; let requestedID; if (typeof relationTo === 'string') { collectionSlug = relationTo; // custom id if (val) { requestedID = val; } } if (Array.isArray(relationTo) && typeof val === 'object' && val?.relationTo) { collectionSlug = val.relationTo; requestedID = val.value; } if (requestedID === null) return false; const idType = payload.collections[collectionSlug]?.customIDType || payload?.db?.defaultIDType || 'text'; return !isValidID(requestedID, idType); }); if (invalidRelationships.length > 0) { return `This relationship field has the following invalid relationships: ${invalidRelationships.map((err, invalid)=>{ return `${err} ${JSON.stringify(invalid)}`; }).join(', ')}`; } } return validateFilterOptions(value, options); }; export const select = (value, { hasMany, options, req: { t }, required })=>{ if (Array.isArray(value) && value.some((input)=>!options.some((option)=>option === input || typeof option !== 'string' && option?.value === input))) { return t('validation:invalidSelection'); } if (typeof value === 'string' && !options.some((option)=>option === value || typeof option !== 'string' && option.value === value)) { return t('validation:invalidSelection'); } if (required && (typeof value === 'undefined' || value === null || hasMany && Array.isArray(value) && value?.length === 0)) { return t('validation:required'); } return true; }; export const radio = (value, { options, req: { t }, required })=>{ if (value) { const valueMatchesOption = options.some((option)=>option === value || typeof option !== 'string' && option.value === value); return valueMatchesOption || t('validation:invalidSelection'); } return required ? t('validation:required') : true; }; export const point = (value = [ '', '' ], { req: { t }, required })=>{ const lng = parseFloat(String(value[0])); const lat = parseFloat(String(value[1])); if (required && (value[0] && value[1] && typeof lng !== 'number' && typeof lat !== 'number' || Number.isNaN(lng) || Number.isNaN(lat) || Array.isArray(value) && value.length !== 2)) { return t('validation:requiresTwoNumbers'); } if (value[1] && Number.isNaN(lng) || value[0] && Number.isNaN(lat)) { return t('validation:invalidInput'); } return true; }; export default { array, blocks, checkbox, code, date, email, json, number, password, point, radio, relationship, richText, select, text, textarea, upload }; //# sourceMappingURL=validations.js.map