-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathvalidator.ts
More file actions
executable file
·122 lines (105 loc) · 4.64 KB
/
Copy pathvalidator.ts
File metadata and controls
executable file
·122 lines (105 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { Editable, Reference } from '../types'
export type ValidationError = string | null | undefined
export type ValidationObject = { name: string; error: ValidationError; field?: string }
export type Validator = {
name: string
required?: (() => ValidationError) | boolean
minLength?: number
maxLength?: number
asNumber?: ((num: number) => ValidationError) | boolean
asString?: ((str: string) => ValidationError) | boolean
miscCheck?: (obj: object) => ValidationError
miscArray?: (data: object[]) => ValidationError
condition?: (data: object) => boolean // Validator is run if condition returns true, example in ./reference
useEditData?: boolean
}
export type Validators<T> = { [field in keyof T]: Validator } & { [key: string]: Validator }
const validationObject = (name: string, error: ValidationError, field: string): ValidationObject => {
const object: ValidationObject = { name, error }
Object.defineProperty(object, 'field', {
value: field,
enumerable: false,
})
return object
}
const validate: (validator: Validator, value: unknown) => ValidationError = (validator: Validator, value: unknown) => {
const { required, minLength, maxLength, asNumber, asString, miscCheck, miscArray } = validator
if ((value === null || value === undefined || value === '') && required) {
if (typeof required === 'boolean') return 'This field is required'
if (typeof required === 'function') return required()
}
if (asNumber) {
if (value === null || value === undefined || value === '') return null
if (typeof value === 'number') {
if (typeof asNumber === 'function') return asNumber(value)
return null
}
if (typeof value === 'string') {
const trimmed = value.trim()
if (trimmed === '') return null
const asCoercedNumber = Number(trimmed)
if (Number.isNaN(asCoercedNumber)) return `${validator.name} must be a valid number`
if (typeof asNumber === 'function') return asNumber(asCoercedNumber)
return null
}
return `${validator.name} must be a valid number`
}
if (asString) {
if (typeof value !== 'string') return 'Value must be of type string'
if (minLength && value.length < minLength) return `Value must be at least ${minLength} characters long`
if (maxLength && value.length > maxLength) return `Value must be at most ${maxLength} characters long`
if (typeof asString === 'function') return asString(value)
}
if (miscCheck && value && typeof value == 'object') {
return miscCheck(value)
} else if (miscCheck) {
return 'You are using a check intended for objects on values that are not objects'
}
if (miscArray && Array.isArray(value)) {
//Typescript
const valueAsArray = value as object[]
for (let i = 0; i < valueAsArray.length; i++) {
if (typeof valueAsArray[i] !== 'object' || valueAsArray[i] === null || Array.isArray(valueAsArray[i])) {
return `Element at index ${i} must be an object`
}
}
return miscArray(valueAsArray)
} else if (miscArray) {
return 'You are using a check intended for arrays on values that are not arrays'
}
return null
}
export const validator = <T>(
validators: Validators<Partial<T>>,
editData: Partial<T>,
fieldName: keyof T
): ValidationObject => {
const fieldValidator = validators[fieldName]
if (!fieldValidator || (fieldValidator.condition && !fieldValidator.condition(editData))) {
return validationObject(fieldName as string, null, fieldName as string)
}
const validationError = validate(fieldValidator, fieldValidator.useEditData ? editData : editData[fieldName])
return validationObject(fieldValidator.name, validationError, fieldName as string)
}
export const validateFields = <T>(
validators: Validators<Partial<T>>,
editData: Partial<T>,
fieldNames: Array<keyof T> = Object.keys(validators) as Array<keyof T>
): ValidationObject[] =>
fieldNames
.map(fieldName => validator<T>(validators, editData, fieldName))
.filter((validationObject): validationObject is ValidationObject => Boolean(validationObject.error))
export const referenceValidator: (references: Editable<Reference>[]) => ValidationError = (
references: Editable<Reference>[]
) => {
if (references.length === 0) {
return 'There must be at least one reference'
}
// new references have rowState = 'new', old ones don't have a rowState and references that will be deleted have rowstate='removed'
// so checking the update has at least one reference that's not queued for removal
const nonRemovedReferences = references.some(ref => !ref.rowState || ref.rowState !== 'removed')
if (!nonRemovedReferences) {
return 'There must be at least one reference'
}
return null
}