UNPKG

@sanity/validation

Version:

Validation and warning infrastructure for Sanity projects

118 lines (95 loc) 3.23 kB
import {ValidationMarker, Validators} from '@sanity/types' import typeString from '../util/typeString' import deepEquals from '../util/deepEquals' import pathToString from '../util/pathToString' import ValidationErrorClass from '../ValidationError' const SLOW_VALIDATOR_TIMEOUT = 5000 const formatValidationErrors = (options: { message: string | undefined results: ValidationMarker[] operation: 'AND' | 'OR' }) => { let message if (options.message) { message = options.message } else if (options.results.length === 1) { message = options.results[0]?.item.message } else { message = `[${options.results .map((err) => err.item.message) .join(` - ${options.operation} - `)}]` } return new ValidationErrorClass(message, { children: options.results.length > 1 ? options.results : undefined, operation: options.operation, }) } const genericValidators: Validators = { type: (expected, value, message) => { const actualType = typeString(value) if (actualType !== expected && actualType !== 'undefined') { return message || `Expected type "${expected}", got "${actualType}"` } return true }, presence: (expected, value, message) => { if (value === undefined && expected === 'required') { return message || 'Value is required' } return true }, all: async (children, value, message, context) => { const resolved = await Promise.all(children.map((child) => child.validate(value, context))) const results = resolved.flat() if (!results.length) return true return formatValidationErrors({ message, results, operation: 'AND', }) }, either: async (children, value, message, context) => { const resolved = await Promise.all(children.map((child) => child.validate(value, context))) const results = resolved.flat() // Read: There is at least one rule that matched if (results.length < children.length) return true return formatValidationErrors({ message, results, operation: 'OR', }) }, valid: (allowedValues, actual, message) => { const valueType = typeof actual if (valueType === 'undefined') { return true } const value = (valueType === 'number' || valueType === 'string') && `${actual}` const strValue = value && value.length > 30 ? `${value.slice(0, 30)}…` : value const defaultMessage = value ? `Value "${strValue}" did not match any allowed values` : 'Value did not match any allowed values' return allowedValues.some((expected) => deepEquals(expected, actual)) ? true : message || defaultMessage }, custom: async (fn, value, message, context) => { const slowTimer = setTimeout(() => { // eslint-disable-next-line no-console console.warn( `Custom validator at ${pathToString( context.path )} has taken more than ${SLOW_VALIDATOR_TIMEOUT}ms to respond` ) }, SLOW_VALIDATOR_TIMEOUT) let result try { result = await fn(value, context) } finally { clearTimeout(slowTimer) } if (typeof result === 'string') return message || result return result }, } export default genericValidators