UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

129 lines (101 loc) 4.14 kB
import {type Validators} from '@sanity/types' import {genericValidators} from './genericValidator' const DUMMY_ORIGIN = 'http://sanity' const isRelativeUrl = (url: string) => /^\.*\//.test(url) const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ export const stringValidators: Validators = { ...genericValidators, min: (minLength, value, message, {i18n}) => { if (!value || value.length >= minLength) { return true } return message || i18n.t('validation:string.minimum-length', {minLength}) }, max: (maxLength, value, message, {i18n}) => { if (!value || value.length <= maxLength) { return true } return message || i18n.t('validation:string.maximum-length', {maxLength}) }, length: (wantedLength, value, message, {i18n}) => { const strValue = value || '' if (strValue.length === wantedLength) { return true } return message || i18n.t('validation:string.exact-length', {wantedLength}) }, uri: (constraints, value, message, {i18n}) => { const strValue = value || '' if (!strValue) { return true // `presence()` should catch empty values } const {options} = constraints const {allowCredentials, relativeOnly} = options const allowRelative = options.allowRelative || relativeOnly let url try { // WARNING: Safari checks for a given `base` param by looking at the length of arguments passed // to new URL(str, base), and will fail if invoked with new URL(strValue, undefined) url = allowRelative ? new URL(strValue, DUMMY_ORIGIN) : new URL(strValue) } catch (err) { return message || i18n.t('validation:string.url.invalid') } if (relativeOnly && url.origin !== DUMMY_ORIGIN) { return message || i18n.t('validation:string.url.not-relative') } if (!allowRelative && url.origin === DUMMY_ORIGIN && isRelativeUrl(strValue)) { return message || i18n.t('validation:string.url.not-absolute') } if (!allowCredentials && (url.username || url.password)) { return message || i18n.t('validation:string.url.includes-credentials') } const urlScheme = url.protocol.replace(/:$/, '') const matchesAllowedScheme = options.scheme.some((scheme) => scheme.test(urlScheme)) if (!matchesAllowedScheme) { return message || i18n.t('validation:string.url.disallowed-scheme', {scheme: urlScheme}) } return true }, stringCasing: (casing, value, message, {i18n}) => { const strValue = value || '' if (casing === 'uppercase' && strValue !== strValue.toLocaleUpperCase()) { return message || i18n.t('validation:string.uppercase') } if (casing === 'lowercase' && strValue !== strValue.toLocaleLowerCase()) { return message || i18n.t('validation:string.lowercase') } return true }, presence: (flag, value, message, {i18n}) => { if (flag === 'required' && !value) { return message || i18n.t('validation:generic.required', {context: 'string'}) } return true }, regex: (options, value, message, {i18n}) => { const {pattern, name, invert} = options const regName = name || `${pattern.toString()}` const strValue = value || '' // Regexes with global or sticky flags are stateful (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex). // This resets the state stored from the previous check pattern.lastIndex = 0 const matches = pattern.test(strValue) if ((!invert && !matches) || (invert && matches)) { if (message) { return message } return invert ? i18n.t('validation:string.regex-match', {name: regName}) : i18n.t('validation:string.regex-does-not-match', {name: regName}) } return true }, email: (_unused, value, message, {i18n}) => { const strValue = `${value || ''}`.trim() if (!strValue || emailRegex.test(strValue)) { return true } return message || i18n.t('validation:string.email') }, }