fix: strongly types field validation args (#8263)
Continuation of #8243. Strongly types the `value` argument within `field.validate` functions: - Uses existing internal validation types for field `validate` property - Exports additional validation types to cover `hasMany` fields - Includes `null` and `undefined` values
This commit is contained in:
@@ -104,9 +104,33 @@ import type {
|
||||
} from '../../config/types.js'
|
||||
import type { DBIdentifierName } from '../../database/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type {
|
||||
ArrayFieldValidation,
|
||||
BlocksFieldValidation,
|
||||
CheckboxFieldValidation,
|
||||
CodeFieldValidation,
|
||||
CollectionSlug,
|
||||
DateFieldValidation,
|
||||
EmailFieldValidation,
|
||||
JSONFieldValidation,
|
||||
PointFieldValidation,
|
||||
RadioFieldValidation,
|
||||
TextareaFieldValidation,
|
||||
} from '../../index.js'
|
||||
import type { DocumentPreferences } from '../../preferences/types.js'
|
||||
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
|
||||
import type {
|
||||
NumberFieldManyValidation,
|
||||
NumberFieldSingleValidation,
|
||||
RelationshipFieldManyValidation,
|
||||
RelationshipFieldSingleValidation,
|
||||
SelectFieldManyValidation,
|
||||
SelectFieldSingleValidation,
|
||||
TextFieldManyValidation,
|
||||
TextFieldSingleValidation,
|
||||
UploadFieldManyValidation,
|
||||
UploadFieldSingleValidation,
|
||||
} from '../validations.js'
|
||||
|
||||
export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSiblingData = any> = {
|
||||
/** The collection which the field belongs to. If the field belongs to a global, this will be null. */
|
||||
@@ -335,7 +359,7 @@ export type Validate<
|
||||
TSiblingData = any,
|
||||
TFieldConfig extends object = object,
|
||||
> = (
|
||||
value: TValue,
|
||||
value: null | TValue | undefined,
|
||||
options: ValidateOptions<TData, TSiblingData, TFieldConfig, TValue>,
|
||||
) => Promise<string | true> | string | true
|
||||
|
||||
@@ -452,7 +476,7 @@ export type NumberField = {
|
||||
maxRows?: number
|
||||
/** Minimum number of numbers in the numbers array, if `hasMany` is set to true. */
|
||||
minRows?: number
|
||||
validate?: Validate<number[], unknown, unknown, NumberField>
|
||||
validate?: NumberFieldManyValidation
|
||||
}
|
||||
| {
|
||||
/** Makes this field an ordered array of numbers instead of just a single number. */
|
||||
@@ -461,7 +485,7 @@ export type NumberField = {
|
||||
maxRows?: undefined
|
||||
/** Minimum number of numbers in the numbers array, if `hasMany` is set to true. */
|
||||
minRows?: undefined
|
||||
validate?: Validate<number, unknown, unknown, NumberField>
|
||||
validate?: NumberFieldSingleValidation
|
||||
}
|
||||
) &
|
||||
Omit<FieldBase, 'validate'>
|
||||
@@ -502,7 +526,7 @@ export type TextField = {
|
||||
maxRows?: number
|
||||
/** Minimum number of strings in the strings array, if `hasMany` is set to true. */
|
||||
minRows?: number
|
||||
validate?: Validate<string[], unknown, unknown, TextField>
|
||||
validate?: TextFieldManyValidation
|
||||
}
|
||||
| {
|
||||
/** Makes this field an ordered array of strings instead of just a single string. */
|
||||
@@ -511,7 +535,7 @@ export type TextField = {
|
||||
maxRows?: undefined
|
||||
/** Minimum number of strings in the strings array, if `hasMany` is set to true. */
|
||||
minRows?: undefined
|
||||
validate?: Validate<string, unknown, unknown, TextField>
|
||||
validate?: TextFieldSingleValidation
|
||||
}
|
||||
) &
|
||||
Omit<FieldBase, 'validate'>
|
||||
@@ -541,7 +565,7 @@ export type EmailField = {
|
||||
placeholder?: Record<string, string> | string
|
||||
} & Admin
|
||||
type: 'email'
|
||||
validate?: Validate<string, unknown, unknown, EmailField>
|
||||
validate?: EmailFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type EmailFieldClient = {
|
||||
@@ -572,7 +596,7 @@ export type TextareaField = {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
type: 'textarea'
|
||||
validate?: Validate<string, unknown, unknown, TextareaField>
|
||||
validate?: TextareaFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type TextareaFieldClient = {
|
||||
@@ -598,7 +622,7 @@ export type CheckboxField = {
|
||||
} & Admin['components']
|
||||
} & Admin
|
||||
type: 'checkbox'
|
||||
validate?: Validate<boolean, unknown, unknown, CheckboxField>
|
||||
validate?: CheckboxFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type CheckboxFieldClient = {
|
||||
@@ -625,7 +649,7 @@ export type DateField = {
|
||||
placeholder?: Record<string, string> | string
|
||||
} & Admin
|
||||
type: 'date'
|
||||
validate?: Validate<unknown, unknown, unknown, DateField>
|
||||
validate?: DateFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type DateFieldClient = {
|
||||
@@ -861,7 +885,7 @@ type SharedUploadProperties = {
|
||||
*/
|
||||
min?: number
|
||||
minRows?: number
|
||||
validate?: Validate<unknown[], unknown, unknown, SharedUploadProperties>
|
||||
validate?: UploadFieldManyValidation
|
||||
}
|
||||
| {
|
||||
hasMany?: false | undefined
|
||||
@@ -875,7 +899,7 @@ type SharedUploadProperties = {
|
||||
*/
|
||||
min?: undefined
|
||||
minRows?: undefined
|
||||
validate?: Validate<unknown, unknown, unknown, SharedUploadProperties>
|
||||
validate?: UploadFieldSingleValidation
|
||||
}
|
||||
) &
|
||||
Omit<FieldBase, 'validate'>
|
||||
@@ -950,7 +974,7 @@ export type CodeField = {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
type: 'code'
|
||||
validate?: Validate<string, unknown, unknown, CodeField>
|
||||
validate?: CodeFieldValidation
|
||||
} & Omit<FieldBase, 'admin' | 'validate'>
|
||||
|
||||
export type CodeFieldClient = {
|
||||
@@ -983,7 +1007,7 @@ export type JSONField = {
|
||||
uri: string
|
||||
}
|
||||
type: 'json'
|
||||
validate?: Validate<Record<string, unknown>, unknown, unknown, JSONField>
|
||||
validate?: JSONFieldValidation
|
||||
} & Omit<FieldBase, 'admin' | 'validate'>
|
||||
|
||||
export type JSONFieldClient = {
|
||||
@@ -1024,11 +1048,11 @@ export type SelectField = {
|
||||
} & (
|
||||
| {
|
||||
hasMany: true
|
||||
validate?: Validate<string[], unknown, unknown, SelectField>
|
||||
validate?: SelectFieldManyValidation
|
||||
}
|
||||
| {
|
||||
hasMany?: false | undefined
|
||||
validate?: Validate<string, unknown, unknown, SelectField>
|
||||
validate?: SelectFieldSingleValidation
|
||||
}
|
||||
) &
|
||||
Omit<FieldBase, 'validate'>
|
||||
@@ -1068,7 +1092,7 @@ type SharedRelationshipProperties = {
|
||||
*/
|
||||
min?: number
|
||||
minRows?: number
|
||||
validate?: Validate<any[], unknown, unknown, SharedRelationshipProperties>
|
||||
validate?: RelationshipFieldManyValidation
|
||||
}
|
||||
| {
|
||||
hasMany?: false | undefined
|
||||
@@ -1082,7 +1106,7 @@ type SharedRelationshipProperties = {
|
||||
*/
|
||||
min?: undefined
|
||||
minRows?: undefined
|
||||
validate?: Validate<any, unknown, unknown, SharedRelationshipProperties>
|
||||
validate?: RelationshipFieldSingleValidation
|
||||
}
|
||||
) &
|
||||
Omit<FieldBase, 'validate'>
|
||||
@@ -1155,11 +1179,11 @@ export function valueIsValueWithRelation(value: unknown): value is ValueWithRela
|
||||
return value !== null && typeof value === 'object' && 'relationTo' in value && 'value' in value
|
||||
}
|
||||
|
||||
export type RelationshipValue =
|
||||
| (number | string)[]
|
||||
| (number | string)
|
||||
| ValueWithRelation
|
||||
| ValueWithRelation[]
|
||||
export type RelationshipValue = RelationshipValueMany | RelationshipValueSingle
|
||||
|
||||
export type RelationshipValueMany = (number | string)[] | ValueWithRelation[]
|
||||
|
||||
export type RelationshipValueSingle = number | string | ValueWithRelation
|
||||
|
||||
export type RichTextField<
|
||||
TValue extends object = any,
|
||||
@@ -1231,7 +1255,7 @@ export type ArrayField = {
|
||||
maxRows?: number
|
||||
minRows?: number
|
||||
type: 'array'
|
||||
validate?: Validate<unknown[], unknown, unknown, ArrayField>
|
||||
validate?: ArrayFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type ArrayFieldClient = {
|
||||
@@ -1266,7 +1290,7 @@ export type RadioField = {
|
||||
enumName?: DBIdentifierName
|
||||
options: Option[]
|
||||
type: 'radio'
|
||||
validate?: Validate<string, unknown, unknown, RadioField>
|
||||
validate?: RadioFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type RadioFieldClient = {
|
||||
@@ -1352,7 +1376,7 @@ export type BlocksField = {
|
||||
maxRows?: number
|
||||
minRows?: number
|
||||
type: 'blocks'
|
||||
validate?: Validate<string, unknown, unknown, BlocksField>
|
||||
validate?: BlocksFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type BlocksFieldClient = {
|
||||
@@ -1379,7 +1403,7 @@ export type PointField = {
|
||||
step?: number
|
||||
} & Admin
|
||||
type: 'point'
|
||||
validate?: Validate<[number, number], unknown, unknown, PointField>
|
||||
validate?: PointFieldValidation
|
||||
} & Omit<FieldBase, 'validate'>
|
||||
|
||||
export type PointFieldClient = {
|
||||
|
||||
@@ -20,6 +20,8 @@ import type {
|
||||
RadioField,
|
||||
RelationshipField,
|
||||
RelationshipValue,
|
||||
RelationshipValueMany,
|
||||
RelationshipValueSingle,
|
||||
RichTextField,
|
||||
SelectField,
|
||||
TextareaField,
|
||||
@@ -32,6 +34,11 @@ import { isNumber } from '../utilities/isNumber.js'
|
||||
import { isValidID } from '../utilities/isValidID.js'
|
||||
|
||||
export type TextFieldValidation = Validate<string, unknown, unknown, TextField>
|
||||
|
||||
export type TextFieldManyValidation = Validate<string[], unknown, unknown, TextField>
|
||||
|
||||
export type TextFieldSingleValidation = Validate<string, unknown, unknown, TextField>
|
||||
|
||||
export const text: TextFieldValidation = (
|
||||
value,
|
||||
{
|
||||
@@ -93,6 +100,7 @@ export const text: TextFieldValidation = (
|
||||
}
|
||||
|
||||
export type PasswordFieldValidation = Validate<string, unknown, unknown, TextField>
|
||||
|
||||
export const password: PasswordFieldValidation = (
|
||||
value,
|
||||
{
|
||||
@@ -135,6 +143,7 @@ export type ConfirmPasswordFieldValidation = Validate<
|
||||
{ password: string },
|
||||
TextField
|
||||
>
|
||||
|
||||
export const confirmPassword: ConfirmPasswordFieldValidation = (
|
||||
value,
|
||||
{ req: { t }, required, siblingData },
|
||||
@@ -151,6 +160,7 @@ export const confirmPassword: ConfirmPasswordFieldValidation = (
|
||||
}
|
||||
|
||||
export type EmailFieldValidation = Validate<string, unknown, { username?: string }, EmailField>
|
||||
|
||||
export const email: EmailFieldValidation = (
|
||||
value,
|
||||
{
|
||||
@@ -185,6 +195,7 @@ export const email: EmailFieldValidation = (
|
||||
}
|
||||
|
||||
export type UsernameFieldValidation = Validate<string, unknown, { email?: string }, TextField>
|
||||
|
||||
export const username: UsernameFieldValidation = (
|
||||
value,
|
||||
{
|
||||
@@ -229,6 +240,7 @@ export const username: UsernameFieldValidation = (
|
||||
}
|
||||
|
||||
export type TextareaFieldValidation = Validate<string, unknown, unknown, TextareaField>
|
||||
|
||||
export const textarea: TextareaFieldValidation = (
|
||||
value,
|
||||
{
|
||||
@@ -265,6 +277,7 @@ export const textarea: TextareaFieldValidation = (
|
||||
}
|
||||
|
||||
export type CodeFieldValidation = Validate<string, unknown, unknown, CodeField>
|
||||
|
||||
export const code: CodeFieldValidation = (value, { req: { t }, required }) => {
|
||||
if (required && value === undefined) {
|
||||
return t('validation:required')
|
||||
@@ -279,6 +292,7 @@ export type JSONFieldValidation = Validate<
|
||||
unknown,
|
||||
{ jsonError?: string } & JSONField
|
||||
>
|
||||
|
||||
export const json: JSONFieldValidation = async (
|
||||
value,
|
||||
{ jsonError, jsonSchema, req: { t }, required },
|
||||
@@ -348,6 +362,7 @@ export const json: JSONFieldValidation = async (
|
||||
}
|
||||
|
||||
export type CheckboxFieldValidation = Validate<boolean, unknown, unknown, CheckboxField>
|
||||
|
||||
export const checkbox: CheckboxFieldValidation = (value, { req: { t }, required }) => {
|
||||
if ((value && typeof value !== 'boolean') || (required && typeof value !== 'boolean')) {
|
||||
return t('validation:trueOrFalse')
|
||||
@@ -357,6 +372,7 @@ export const checkbox: CheckboxFieldValidation = (value, { req: { t }, required
|
||||
}
|
||||
|
||||
export type DateFieldValidation = Validate<Date, unknown, unknown, DateField>
|
||||
|
||||
export const date: DateFieldValidation = (value, { req: { t }, required }) => {
|
||||
if (value && !isNaN(Date.parse(value.toString()))) {
|
||||
return true
|
||||
@@ -374,6 +390,7 @@ export const date: DateFieldValidation = (value, { req: { t }, required }) => {
|
||||
}
|
||||
|
||||
export type RichTextFieldValidation = Validate<object, unknown, unknown, RichTextField>
|
||||
|
||||
export const richText: RichTextFieldValidation = async (value, options) => {
|
||||
if (!options?.editor) {
|
||||
throw new Error('richText field has no editor property.')
|
||||
@@ -420,6 +437,11 @@ const validateArrayLength = (
|
||||
}
|
||||
|
||||
export type NumberFieldValidation = Validate<number | number[], unknown, unknown, NumberField>
|
||||
|
||||
export type NumberFieldManyValidation = Validate<number[], unknown, unknown, NumberField>
|
||||
|
||||
export type NumberFieldSingleValidation = Validate<number, unknown, unknown, NumberField>
|
||||
|
||||
export const number: NumberFieldValidation = (
|
||||
value,
|
||||
{ hasMany, max, maxRows, min, minRows, req: { t }, required },
|
||||
@@ -611,6 +633,10 @@ const validateFilterOptions: Validate<
|
||||
|
||||
export type UploadFieldValidation = Validate<unknown, unknown, unknown, UploadField>
|
||||
|
||||
export type UploadFieldManyValidation = Validate<unknown[], unknown, unknown, UploadField>
|
||||
|
||||
export type UploadFieldSingleValidation = Validate<unknown, unknown, unknown, UploadField>
|
||||
|
||||
export const upload: UploadFieldValidation = async (value, options) => {
|
||||
const {
|
||||
maxRows,
|
||||
@@ -694,6 +720,21 @@ export type RelationshipFieldValidation = Validate<
|
||||
unknown,
|
||||
RelationshipField
|
||||
>
|
||||
|
||||
export type RelationshipFieldManyValidation = Validate<
|
||||
RelationshipValueMany,
|
||||
unknown,
|
||||
unknown,
|
||||
RelationshipField
|
||||
>
|
||||
|
||||
export type RelationshipFieldSingleValidation = Validate<
|
||||
RelationshipValueSingle,
|
||||
unknown,
|
||||
unknown,
|
||||
RelationshipField
|
||||
>
|
||||
|
||||
export const relationship: RelationshipFieldValidation = async (value, options) => {
|
||||
const {
|
||||
maxRows,
|
||||
@@ -772,6 +813,11 @@ export const relationship: RelationshipFieldValidation = async (value, options)
|
||||
}
|
||||
|
||||
export type SelectFieldValidation = Validate<string | string[], unknown, unknown, SelectField>
|
||||
|
||||
export type SelectFieldManyValidation = Validate<string[], unknown, unknown, SelectField>
|
||||
|
||||
export type SelectFieldSingleValidation = Validate<string, unknown, unknown, SelectField>
|
||||
|
||||
export const select: SelectFieldValidation = (
|
||||
value,
|
||||
{ hasMany, options, req: { t }, required },
|
||||
@@ -810,6 +856,7 @@ export const select: SelectFieldValidation = (
|
||||
}
|
||||
|
||||
export type RadioFieldValidation = Validate<unknown, unknown, unknown, RadioField>
|
||||
|
||||
export const radio: RadioFieldValidation = (value, { options, req: { t }, required }) => {
|
||||
if (value) {
|
||||
const valueMatchesOption = options.some(
|
||||
@@ -827,6 +874,7 @@ export type PointFieldValidation = Validate<
|
||||
unknown,
|
||||
PointField
|
||||
>
|
||||
|
||||
export const point: PointFieldValidation = (value = ['', ''], { req: { t }, required }) => {
|
||||
const lng = parseFloat(String(value[0]))
|
||||
const lat = parseFloat(String(value[1]))
|
||||
|
||||
@@ -937,15 +937,25 @@ export type {
|
||||
DateFieldValidation,
|
||||
EmailFieldValidation,
|
||||
JSONFieldValidation,
|
||||
NumberFieldManyValidation,
|
||||
NumberFieldSingleValidation,
|
||||
NumberFieldValidation,
|
||||
PasswordFieldValidation,
|
||||
PointFieldValidation,
|
||||
RadioFieldValidation,
|
||||
RelationshipFieldManyValidation,
|
||||
RelationshipFieldSingleValidation,
|
||||
RelationshipFieldValidation,
|
||||
RichTextFieldValidation,
|
||||
SelectFieldManyValidation,
|
||||
SelectFieldSingleValidation,
|
||||
SelectFieldValidation,
|
||||
TextareaFieldValidation,
|
||||
TextFieldManyValidation,
|
||||
TextFieldSingleValidation,
|
||||
TextFieldValidation,
|
||||
UploadFieldManyValidation,
|
||||
UploadFieldSingleValidation,
|
||||
UploadFieldValidation,
|
||||
UsernameFieldValidation,
|
||||
} from './fields/validations.js'
|
||||
|
||||
@@ -60,7 +60,7 @@ const NumberFields: CollectionConfig = {
|
||||
name: 'validatesHasMany',
|
||||
type: 'number',
|
||||
hasMany: true,
|
||||
validate: (value: number[]) => {
|
||||
validate: (value) => {
|
||||
if (value && !Array.isArray(value)) {
|
||||
return 'value should be an array'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user