@sanity/validation 2.33.4-shopify.8 → 2.34.1-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/util/requestIdleCallback.d.ts +3 -0
- package/dist/dts/util/requestIdleCallback.d.ts.map +1 -0
- package/dist/dts/validateDocument.d.ts +3 -1
- package/dist/dts/validateDocument.d.ts.map +1 -1
- package/lib/Rule.js +23 -100
- package/lib/ValidationError.js +0 -9
- package/lib/getClient.js +0 -3
- package/lib/index.js +9 -7
- package/lib/inferFromSchema.js +0 -4
- package/lib/inferFromSchemaType.js +8 -20
- package/lib/util/convertToValidationMarker.js +8 -17
- package/lib/util/deepEquals.js +1 -18
- package/lib/util/escapeRegex.js +1 -4
- package/lib/util/normalizeValidationRules.js +11 -29
- package/lib/util/pathToString.js +0 -6
- package/lib/util/requestIdleCallback.js +30 -0
- package/lib/util/typeString.js +2 -5
- package/lib/validateDocument.js +140 -168
- package/lib/validators/arrayValidator.js +0 -27
- package/lib/validators/booleanValidator.js +0 -8
- package/lib/validators/dateValidator.js +8 -28
- package/lib/validators/genericValidator.js +2 -27
- package/lib/validators/numberValidator.js +0 -15
- package/lib/validators/objectValidator.js +1 -26
- package/lib/validators/slugValidator.js +5 -36
- package/lib/validators/stringValidator.js +3 -31
- package/package.json +6 -5
- package/src/index.ts +2 -0
- package/src/util/requestIdleCallback.ts +29 -0
- package/src/validateDocument.test.ts +0 -77
- package/src/validateDocument.ts +97 -60
|
@@ -687,81 +687,4 @@ describe('validateItem', () => {
|
|
|
687
687
|
},
|
|
688
688
|
])
|
|
689
689
|
})
|
|
690
|
-
|
|
691
|
-
it('runs all nested validation checks concurrently', async () => {
|
|
692
|
-
type Resolver = (value: true) => void
|
|
693
|
-
const resolvers = new Set<Resolver>()
|
|
694
|
-
const customValidationFn = () => new Promise<true>((resolve) => resolvers.add(resolve))
|
|
695
|
-
|
|
696
|
-
const schema: Schema = createSchema({
|
|
697
|
-
types: [
|
|
698
|
-
{
|
|
699
|
-
name: 'root',
|
|
700
|
-
type: 'object',
|
|
701
|
-
fields: [
|
|
702
|
-
{
|
|
703
|
-
name: 'level1Object',
|
|
704
|
-
type: 'object',
|
|
705
|
-
validation: (rule: Rule) => [
|
|
706
|
-
rule.custom(customValidationFn),
|
|
707
|
-
rule.fields({
|
|
708
|
-
level2String: (r) => r.custom(customValidationFn),
|
|
709
|
-
}),
|
|
710
|
-
],
|
|
711
|
-
fields: [
|
|
712
|
-
{
|
|
713
|
-
name: 'level2String',
|
|
714
|
-
type: 'string',
|
|
715
|
-
validation: (rule: Rule) => rule.custom(customValidationFn),
|
|
716
|
-
},
|
|
717
|
-
],
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
name: 'level1Array',
|
|
721
|
-
type: 'array',
|
|
722
|
-
validation: (rule: Rule) => rule.custom(customValidationFn),
|
|
723
|
-
of: [
|
|
724
|
-
{
|
|
725
|
-
type: 'object',
|
|
726
|
-
fields: [
|
|
727
|
-
{
|
|
728
|
-
name: 'level2Number',
|
|
729
|
-
type: 'number',
|
|
730
|
-
validation: (rule: Rule) => rule.custom(customValidationFn),
|
|
731
|
-
},
|
|
732
|
-
],
|
|
733
|
-
},
|
|
734
|
-
],
|
|
735
|
-
},
|
|
736
|
-
],
|
|
737
|
-
validation: (rule: Rule) => rule.custom(customValidationFn),
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
const value = {
|
|
743
|
-
level1Object: {level3String: 'a string'},
|
|
744
|
-
level1Array: [{level2Number: 5}],
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const resultPromise = validateItem({
|
|
748
|
-
value,
|
|
749
|
-
document: undefined,
|
|
750
|
-
parent: undefined,
|
|
751
|
-
path: undefined,
|
|
752
|
-
type: schema.get('root'),
|
|
753
|
-
getDocumentExists: undefined,
|
|
754
|
-
})
|
|
755
|
-
|
|
756
|
-
// after `validateItem(...)` has been initiated, all of the custom
|
|
757
|
-
// validation calls should have fired so the resolver size should equal the
|
|
758
|
-
// amount of total fields
|
|
759
|
-
expect(resolvers.size).toBe(6)
|
|
760
|
-
|
|
761
|
-
for (const resolver of resolvers) {
|
|
762
|
-
resolver(true)
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
await resultPromise
|
|
766
|
-
})
|
|
767
690
|
})
|
package/src/validateDocument.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isBlock,
|
|
3
|
+
isBlockSchemaType,
|
|
4
|
+
isKeyedObject,
|
|
5
|
+
isSpanSchemaType,
|
|
6
|
+
isTypedObject,
|
|
2
7
|
SanityDocument,
|
|
3
8
|
Schema,
|
|
4
9
|
SchemaType,
|
|
5
10
|
ValidationContext,
|
|
6
11
|
ValidationMarker,
|
|
7
|
-
isKeyedObject,
|
|
8
|
-
isTypedObject,
|
|
9
|
-
isBlock,
|
|
10
|
-
isBlockSchemaType,
|
|
11
|
-
isSpanSchemaType,
|
|
12
12
|
} from '@sanity/types'
|
|
13
|
-
import {
|
|
13
|
+
import {concat, defer, merge, Observable, of} from 'rxjs'
|
|
14
|
+
import {catchError, map, mergeAll, mergeMap, toArray} from 'rxjs/operators'
|
|
15
|
+
import {flatten, uniqBy} from 'lodash'
|
|
14
16
|
import typeString from './util/typeString'
|
|
17
|
+
import {requestIdleCallback, cancelIdleCallback} from './util/requestIdleCallback'
|
|
15
18
|
import ValidationErrorClass from './ValidationError'
|
|
16
19
|
import normalizeValidationRules from './util/normalizeValidationRules'
|
|
17
20
|
|
|
@@ -45,38 +48,47 @@ export function resolveTypeForArrayItem(
|
|
|
45
48
|
candidates.find((candidate) => candidate.name === 'object' && primitive === 'object')
|
|
46
49
|
)
|
|
47
50
|
}
|
|
51
|
+
const EMPTY_MARKERS: ValidationMarker[] = []
|
|
48
52
|
|
|
49
|
-
export default
|
|
53
|
+
export default function validateDocument(
|
|
50
54
|
doc: SanityDocument,
|
|
51
55
|
schema: Schema,
|
|
52
56
|
context?: Pick<ValidationContext, 'getDocumentExists'>
|
|
53
57
|
): Promise<ValidationMarker[]> {
|
|
58
|
+
return validateDocumentObservable(doc, schema, context).toPromise()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function validateDocumentObservable(
|
|
62
|
+
doc: SanityDocument,
|
|
63
|
+
schema: Schema,
|
|
64
|
+
context?: Pick<ValidationContext, 'getDocumentExists'>
|
|
65
|
+
): Observable<ValidationMarker[]> {
|
|
54
66
|
const documentType = schema.get(doc._type)
|
|
55
67
|
if (!documentType) {
|
|
56
68
|
console.warn('Schema type for object type "%s" not found, skipping validation', doc._type)
|
|
57
|
-
return
|
|
69
|
+
return of(EMPTY_MARKERS)
|
|
58
70
|
}
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
return validateItemObservable({
|
|
73
|
+
parent: undefined,
|
|
74
|
+
value: doc,
|
|
75
|
+
path: [],
|
|
76
|
+
document: doc,
|
|
77
|
+
type: documentType,
|
|
78
|
+
getDocumentExists: context?.getDocumentExists,
|
|
79
|
+
}).pipe(
|
|
80
|
+
catchError((err) => {
|
|
81
|
+
console.error(err)
|
|
82
|
+
return of([
|
|
83
|
+
{
|
|
84
|
+
type: 'validation' as const,
|
|
85
|
+
level: 'error' as const,
|
|
86
|
+
path: [],
|
|
87
|
+
item: new ValidationErrorClass(err?.message),
|
|
88
|
+
},
|
|
89
|
+
])
|
|
68
90
|
})
|
|
69
|
-
|
|
70
|
-
console.error(err)
|
|
71
|
-
return [
|
|
72
|
-
{
|
|
73
|
-
type: 'validation',
|
|
74
|
-
level: 'error',
|
|
75
|
-
path: [],
|
|
76
|
-
item: new ValidationErrorClass(err?.message),
|
|
77
|
-
},
|
|
78
|
-
]
|
|
79
|
-
}
|
|
91
|
+
)
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
/**
|
|
@@ -94,27 +106,32 @@ type ValidateItemOptions = {
|
|
|
94
106
|
value: unknown
|
|
95
107
|
} & ExplicitUndefined<ValidationContext>
|
|
96
108
|
|
|
97
|
-
export
|
|
109
|
+
export function validateItem(opts: ValidateItemOptions): Promise<ValidationMarker[]> {
|
|
110
|
+
return validateItemObservable(opts).toPromise()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function validateItemObservable({
|
|
98
114
|
value,
|
|
99
115
|
type,
|
|
100
116
|
path = [],
|
|
101
117
|
parent,
|
|
102
118
|
...restOfContext
|
|
103
|
-
}: ValidateItemOptions):
|
|
119
|
+
}: ValidateItemOptions): Observable<ValidationMarker[]> {
|
|
104
120
|
const rules = normalizeValidationRules(type)
|
|
105
|
-
|
|
106
121
|
// run validation for the current value
|
|
107
122
|
const selfChecks = rules.map((rule) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
defer(() =>
|
|
124
|
+
rule.validate(value, {
|
|
125
|
+
...restOfContext,
|
|
126
|
+
parent,
|
|
127
|
+
path,
|
|
128
|
+
type,
|
|
129
|
+
})
|
|
130
|
+
)
|
|
114
131
|
)
|
|
115
132
|
|
|
116
133
|
// run validation for nested values (conditionally)
|
|
117
|
-
let
|
|
134
|
+
let fieldChecks: Array<Observable<ValidationMarker[]>> = []
|
|
118
135
|
|
|
119
136
|
const selfIsRequired = rules.some((rule) => rule.isRequired())
|
|
120
137
|
const shouldRunNestedObjectValidation =
|
|
@@ -132,7 +149,7 @@ export async function validateItem({
|
|
|
132
149
|
}, {})
|
|
133
150
|
|
|
134
151
|
// Validation for rules set at the object level with `Rule.fields({/* ... */})`
|
|
135
|
-
|
|
152
|
+
fieldChecks = fieldChecks.concat(
|
|
136
153
|
rules
|
|
137
154
|
.map((rule) => rule._fieldRules)
|
|
138
155
|
.filter(isNonNullable)
|
|
@@ -141,20 +158,22 @@ export async function validateItem({
|
|
|
141
158
|
const fieldType = fieldTypes[name]
|
|
142
159
|
return normalizeValidationRules({...fieldType, validation}).map((subRule) => {
|
|
143
160
|
const nestedValue = isRecord(value) ? value[name] : undefined
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
return defer(() =>
|
|
162
|
+
subRule.validate(nestedValue, {
|
|
163
|
+
...restOfContext,
|
|
164
|
+
parent: value,
|
|
165
|
+
path: path.concat(name),
|
|
166
|
+
type: fieldType,
|
|
167
|
+
})
|
|
168
|
+
)
|
|
150
169
|
})
|
|
151
170
|
})
|
|
152
171
|
)
|
|
153
172
|
|
|
154
173
|
// Validation from each field's schema `validation: Rule => {/* ... */}` function
|
|
155
|
-
|
|
174
|
+
fieldChecks = fieldChecks.concat(
|
|
156
175
|
type.fields.map((field) =>
|
|
157
|
-
|
|
176
|
+
validateItemObservable({
|
|
158
177
|
...restOfContext,
|
|
159
178
|
parent: value,
|
|
160
179
|
value: isRecord(value) ? value[field.name] : undefined,
|
|
@@ -172,9 +191,9 @@ export async function validateItem({
|
|
|
172
191
|
const shouldRunNestedValidationForArrays = type?.jsonType === 'array' && Array.isArray(value)
|
|
173
192
|
|
|
174
193
|
if (shouldRunNestedValidationForArrays) {
|
|
175
|
-
|
|
194
|
+
fieldChecks = fieldChecks.concat(
|
|
176
195
|
value.map((item) =>
|
|
177
|
-
|
|
196
|
+
validateItemObservable({
|
|
178
197
|
...restOfContext,
|
|
179
198
|
parent: value,
|
|
180
199
|
value: item,
|
|
@@ -195,16 +214,16 @@ export async function validateItem({
|
|
|
195
214
|
const spanType = spanChildrenField.type.of.find(isSpanSchemaType)
|
|
196
215
|
|
|
197
216
|
const annotations = (spanType?.annotations || []).reduce<Map<string, SchemaType>>(
|
|
198
|
-
(
|
|
199
|
-
|
|
200
|
-
return
|
|
217
|
+
(acc, annotationType) => {
|
|
218
|
+
acc.set(annotationType.name, annotationType)
|
|
219
|
+
return acc
|
|
201
220
|
},
|
|
202
221
|
new Map()
|
|
203
222
|
)
|
|
204
223
|
|
|
205
|
-
|
|
224
|
+
fieldChecks = fieldChecks.concat(
|
|
206
225
|
value.markDefs.map((markDef) =>
|
|
207
|
-
|
|
226
|
+
validateItemObservable({
|
|
208
227
|
...restOfContext,
|
|
209
228
|
parent: value,
|
|
210
229
|
value: markDef,
|
|
@@ -215,13 +234,31 @@ export async function validateItem({
|
|
|
215
234
|
)
|
|
216
235
|
}
|
|
217
236
|
|
|
218
|
-
|
|
237
|
+
return defer(() => merge([...selfChecks, ...fieldChecks])).pipe(
|
|
238
|
+
mergeMap((validateNode) => concat(idle(), validateNode), 40),
|
|
239
|
+
mergeAll(),
|
|
240
|
+
toArray(),
|
|
241
|
+
map(flatten),
|
|
242
|
+
map((results) => {
|
|
243
|
+
// run `uniqBy` if `_fieldRules` are present because they can
|
|
244
|
+
// cause repeat markers
|
|
245
|
+
if (rules.some((rule) => rule._fieldRules)) {
|
|
246
|
+
return uniqBy(results, (rule) => JSON.stringify(rule))
|
|
247
|
+
}
|
|
248
|
+
return results
|
|
249
|
+
})
|
|
250
|
+
)
|
|
251
|
+
}
|
|
219
252
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
function idle(timeout?: number): Observable<never> {
|
|
254
|
+
return new Observable<never>((observer) => {
|
|
255
|
+
const handle = requestIdleCallback(
|
|
256
|
+
() => {
|
|
257
|
+
observer.complete()
|
|
258
|
+
},
|
|
259
|
+
timeout ? {timeout} : undefined
|
|
260
|
+
)
|
|
225
261
|
|
|
226
|
-
|
|
262
|
+
return () => cancelIdleCallback(handle)
|
|
263
|
+
})
|
|
227
264
|
}
|