@sanity/validation 3.14.4 → 6.12.0-next.112
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/LICENSE +1 -1
- package/README.md +52 -0
- package/lib/_internal.d.ts +96 -0
- package/lib/_internal.js +39 -0
- package/lib/_internal.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -1306
- package/lib/validateDocument-6pLTIHIn.d.ts +277 -0
- package/lib/validateDocument-Cq33kUmN.js +1127 -0
- package/lib/validateDocument-Cq33kUmN.js.map +1 -0
- package/package.json +53 -45
- package/lib/dts/src/index.d.ts +0 -50
- package/lib/index.cjs.mjs +0 -9
- package/lib/index.esm.js +0 -1286
- package/lib/index.esm.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/src/Rule.ts +0 -424
- package/src/ValidationError.ts +0 -32
- package/src/index.ts +0 -9
- package/src/inferFromSchema.ts +0 -19
- package/src/inferFromSchemaType.ts +0 -50
- package/src/util/convertToValidationMarker.ts +0 -84
- package/src/util/deepEquals.ts +0 -77
- package/src/util/escapeRegex.ts +0 -5
- package/src/util/normalizeValidationRules.test.ts +0 -170
- package/src/util/normalizeValidationRules.ts +0 -118
- package/src/util/pathToString.ts +0 -21
- package/src/util/requestIdleCallback.ts +0 -31
- package/src/util/typeString.test.ts +0 -27
- package/src/util/typeString.ts +0 -23
- package/src/validateDocument.test.ts +0 -703
- package/src/validateDocument.ts +0 -240
- package/src/validators/arrayValidator.ts +0 -100
- package/src/validators/booleanValidator.ts +0 -16
- package/src/validators/dateValidator.ts +0 -113
- package/src/validators/genericValidator.ts +0 -117
- package/src/validators/numberValidator.ts +0 -66
- package/src/validators/objectValidator.ts +0 -64
- package/src/validators/slugValidator.ts +0 -117
- package/src/validators/stringValidator.ts +0 -120
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
import { isArrayOfBlocksSchemaType, isKeyedObject, isReference, isSlug, isTypedObject } from "@sanity/types";
|
|
2
|
+
import { createClientConcurrencyLimiter } from "@sanity/util/client";
|
|
3
|
+
import { ConcurrencyLimiter } from "@sanity/util/concurrency-limiter";
|
|
4
|
+
import flatten from "lodash-es/flatten.js";
|
|
5
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
6
|
+
import { Observable, Subject, bufferTime, concat, defer, filter, finalize, firstValueFrom, from, lastValueFrom, map, merge, mergeMap, of, share, switchMap } from "rxjs";
|
|
7
|
+
import { catchError, map as map$1, mergeAll, mergeMap as mergeMap$1, switchMap as switchMap$1, toArray } from "rxjs/operators";
|
|
8
|
+
import { createInstance } from "i18next";
|
|
9
|
+
import { Rule as Rule$1 } from "@sanity/schema";
|
|
10
|
+
import get from "lodash-es/get.js";
|
|
11
|
+
import isPlainObject from "lodash-es/isPlainObject.js";
|
|
12
|
+
import * as legacyDateFormat from "@sanity/util/legacyDateFormat";
|
|
13
|
+
import { getPublishedId } from "@sanity/id-utils";
|
|
14
|
+
import memoize from "lodash-es/memoize.js";
|
|
15
|
+
/** Machine-readable codes emitted by built-in document validation. @beta */
|
|
16
|
+
const validationMarkerCodes = {
|
|
17
|
+
arrayDuplicateItem: "array.duplicate-item",
|
|
18
|
+
arrayExactLength: "array.exact-length",
|
|
19
|
+
arrayMaximumLength: "array.maximum-length",
|
|
20
|
+
arrayMinimumLength: "array.minimum-length",
|
|
21
|
+
assetRequired: "asset.required",
|
|
22
|
+
custom: "custom",
|
|
23
|
+
dateInvalidFormat: "date.invalid-format",
|
|
24
|
+
dateMaximum: "date.maximum",
|
|
25
|
+
dateMinimum: "date.minimum",
|
|
26
|
+
documentUnknownType: "document.unknown-type",
|
|
27
|
+
mediaCustom: "media.custom",
|
|
28
|
+
mediaInvalidReference: "media.invalid-reference",
|
|
29
|
+
mediaNotFound: "media.not-found",
|
|
30
|
+
numberGreaterThan: "number.greater-than",
|
|
31
|
+
numberInteger: "number.integer",
|
|
32
|
+
numberLessThan: "number.less-than",
|
|
33
|
+
numberMaximum: "number.maximum",
|
|
34
|
+
numberMinimum: "number.minimum",
|
|
35
|
+
numberPrecision: "number.precision",
|
|
36
|
+
objectUnknownField: "object.unknown-field",
|
|
37
|
+
referenceInvalid: "reference.invalid",
|
|
38
|
+
referenceNotPublished: "reference.not-published",
|
|
39
|
+
ruleAllFailed: "rule.all-failed",
|
|
40
|
+
ruleEitherFailed: "rule.either-failed",
|
|
41
|
+
slugInvalidType: "slug.invalid-type",
|
|
42
|
+
slugMissingCurrent: "slug.missing-current",
|
|
43
|
+
slugNotUnique: "slug.not-unique",
|
|
44
|
+
stringEmail: "string.email",
|
|
45
|
+
stringExactLength: "string.exact-length",
|
|
46
|
+
stringLowercase: "string.lowercase",
|
|
47
|
+
stringMaximumLength: "string.maximum-length",
|
|
48
|
+
stringMinimumLength: "string.minimum-length",
|
|
49
|
+
stringRegexMatch: "string.regex-match",
|
|
50
|
+
stringRegexMismatch: "string.regex-mismatch",
|
|
51
|
+
stringUppercase: "string.uppercase",
|
|
52
|
+
stringUrlCredentialsNotAllowed: "string.url.credentials-not-allowed",
|
|
53
|
+
stringUrlInvalid: "string.url.invalid",
|
|
54
|
+
stringUrlNotAbsolute: "string.url.not-absolute",
|
|
55
|
+
stringUrlNotRelative: "string.url.not-relative",
|
|
56
|
+
stringUrlSchemeNotAllowed: "string.url.scheme-not-allowed",
|
|
57
|
+
validationException: "validation.exception",
|
|
58
|
+
validationFailed: "validation.failed",
|
|
59
|
+
valueNotAllowed: "value.not-allowed",
|
|
60
|
+
valueRequired: "value.required",
|
|
61
|
+
valueTypeMismatch: "value.type-mismatch"
|
|
62
|
+
}, validationLocaleStrings = {
|
|
63
|
+
"array.exact-length": "Must have exactly {{wantedLength}} items",
|
|
64
|
+
"array.exact-length_blocks": "Must have exactly {{wantedLength}} blocks",
|
|
65
|
+
"array.item-duplicate": "Can't be a duplicate",
|
|
66
|
+
"array.maximum-length": "Must have at most {{maxLength}} items",
|
|
67
|
+
"array.maximum-length_blocks": "Must have at most {{maxLength}} blocks",
|
|
68
|
+
"array.minimum-length": "Must have at least {{minLength}} items",
|
|
69
|
+
"array.minimum-length_blocks": "Must have at least {{minLength}} blocks",
|
|
70
|
+
"date.invalid-format": "Must be a valid ISO-8601 formatted date string",
|
|
71
|
+
"date.maximum": "Must be at or before {{maxDate}}",
|
|
72
|
+
"date.minimum": "Must be at or after {{minDate}}",
|
|
73
|
+
"generic.incorrect-type": "Expected type \"{{expectedType}}\", got \"{{actualType}}\"",
|
|
74
|
+
"generic.not-allowed": "Value did not match any allowed values",
|
|
75
|
+
"generic.not-allowed_hint": "Value \"{{hint}}\" did not match any allowed values",
|
|
76
|
+
"generic.required": "Required",
|
|
77
|
+
"number.greater-than": "Must be greater than {{threshold}}",
|
|
78
|
+
"number.less-than": "Must be less than {{threshold}}",
|
|
79
|
+
"number.maximum": "Must be lower than or equal to {{maxNumber}}",
|
|
80
|
+
"number.maximum-precision": "Max precision is {{limit}}",
|
|
81
|
+
"number.minimum": "Must be greater than or equal to {{minNumber}}",
|
|
82
|
+
"number.non-integer": "Must be an integer",
|
|
83
|
+
"object.asset-required": "Asset is required",
|
|
84
|
+
"object.asset-required_file": "File is required",
|
|
85
|
+
"object.asset-required_image": "Image is required",
|
|
86
|
+
"object.media-not-found": "The asset could not be found in the Media Library",
|
|
87
|
+
"object.not-media-library-asset": "Must be a reference to a Media Library asset",
|
|
88
|
+
"object.not-reference": "Must be a reference to a document",
|
|
89
|
+
"object.reference-not-published": "Referenced document must be published",
|
|
90
|
+
"slug.missing-current": "Slug must have a value",
|
|
91
|
+
"slug.not-object": "Slug must be an object",
|
|
92
|
+
"slug.not-unique": "Slug is already in use",
|
|
93
|
+
"string.email": "Must be a valid email address",
|
|
94
|
+
"string.exact-length": "Must be exactly {{wantedLength}} characters long",
|
|
95
|
+
"string.lowercase": "Must be all lowercase characters",
|
|
96
|
+
"string.maximum-length": "Must be at most {{maxLength}} characters long",
|
|
97
|
+
"string.minimum-length": "Must be at least {{minLength}} characters long",
|
|
98
|
+
"string.regex-does-not-match": "Does not match \"{{name}}\"-pattern",
|
|
99
|
+
"string.regex-match": "Should not match \"{{name}}\"-pattern",
|
|
100
|
+
"string.uppercase": "Must be all uppercase characters",
|
|
101
|
+
"string.url.disallowed-scheme": "Does not match allowed protocols/schemes",
|
|
102
|
+
"string.url.includes-credentials": "Username/password not allowed",
|
|
103
|
+
"string.url.invalid": "Not a valid URL",
|
|
104
|
+
"string.url.not-absolute": "Relative URLs are not allowed",
|
|
105
|
+
"string.url.not-relative": "Only relative URLs are allowed"
|
|
106
|
+
};
|
|
107
|
+
let fallbackLocaleSource;
|
|
108
|
+
function getFallbackLocaleSource() {
|
|
109
|
+
if (fallbackLocaleSource) return fallbackLocaleSource;
|
|
110
|
+
let i18n = createInstance({
|
|
111
|
+
defaultNS: "validation",
|
|
112
|
+
fallbackLng: "en-US",
|
|
113
|
+
initAsync: !1,
|
|
114
|
+
interpolation: { escapeValue: !1 },
|
|
115
|
+
lng: "en-US",
|
|
116
|
+
ns: ["validation"],
|
|
117
|
+
resources: { "en-US": { validation: validationLocaleStrings } },
|
|
118
|
+
supportedLngs: ["en-US"]
|
|
119
|
+
});
|
|
120
|
+
return i18n.init(), fallbackLocaleSource = {
|
|
121
|
+
currentLocale: { id: "en-US" },
|
|
122
|
+
loadNamespaces: (namespaces) => i18n.loadNamespaces(namespaces),
|
|
123
|
+
t: i18n.t
|
|
124
|
+
}, fallbackLocaleSource;
|
|
125
|
+
}
|
|
126
|
+
function resolveConditionalProperty(property, context) {
|
|
127
|
+
let { currentUser, document, parent, value, path } = context;
|
|
128
|
+
return typeof property == "boolean" || property === void 0 ? !!property : property({
|
|
129
|
+
document,
|
|
130
|
+
parent,
|
|
131
|
+
value,
|
|
132
|
+
currentUser,
|
|
133
|
+
path
|
|
134
|
+
}) === !0;
|
|
135
|
+
}
|
|
136
|
+
function createBatchedGetDocumentExists(client) {
|
|
137
|
+
let id$ = new Subject(), limiter = new ConcurrencyLimiter(1), existence$ = id$.pipe(bufferTime(250, null, 100), map((ids) => Array.from(new Set(ids))), mergeMap((ids) => from(limiter.ready()).pipe(switchMap(() => client.observable.request({
|
|
138
|
+
url: client.getDataUrl("doc", ids.join(",")),
|
|
139
|
+
query: { excludeContent: "true" },
|
|
140
|
+
tag: "documents-availability"
|
|
141
|
+
}).pipe(map((availability) => ({
|
|
142
|
+
availability,
|
|
143
|
+
ids
|
|
144
|
+
})))), finalize(limiter.release))), mergeMap(({ availability, ids }) => ids.map((id) => {
|
|
145
|
+
let omittedIds = availability.omitted.reduce((acc, next) => (acc[next.id] = next.reason, acc), {});
|
|
146
|
+
return omittedIds[id] && omittedIds[id] === "existence" ? {
|
|
147
|
+
id,
|
|
148
|
+
exists: !1
|
|
149
|
+
} : {
|
|
150
|
+
id,
|
|
151
|
+
exists: !0
|
|
152
|
+
};
|
|
153
|
+
})), share());
|
|
154
|
+
return async function getDocumentExists(options) {
|
|
155
|
+
let result = firstValueFrom(existence$.pipe(filter(({ id }) => id === options.id)));
|
|
156
|
+
id$.next(options.id);
|
|
157
|
+
let { exists } = await result;
|
|
158
|
+
return exists;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function pathToString(path = []) {
|
|
162
|
+
return path.reduce((target, segment, i) => {
|
|
163
|
+
let segmentType = typeof segment;
|
|
164
|
+
if (segmentType === "number") return `${target}[${segment}]`;
|
|
165
|
+
if (segmentType === "string") return `${target}${i === 0 ? "" : "."}${segment}`;
|
|
166
|
+
if (isKeyedObject(segment)) return `${target}[_key=="${segment._key}"]`;
|
|
167
|
+
throw Error(`Unsupported path segment "${segment}"`);
|
|
168
|
+
}, "");
|
|
169
|
+
}
|
|
170
|
+
function convertToValidationMarker(validatorResult, level, context, fallback = { code: validationMarkerCodes.validationFailed }) {
|
|
171
|
+
if (!context) throw Error("missing context");
|
|
172
|
+
if (validatorResult === !0) return [];
|
|
173
|
+
if (Array.isArray(validatorResult)) return validatorResult.flatMap((child) => convertToValidationMarker(child, level, context, fallback));
|
|
174
|
+
if (typeof validatorResult == "string") return convertToValidationMarker({ message: validatorResult }, level, context, fallback);
|
|
175
|
+
if (typeof validatorResult.message != "string") throw Error(`${pathToString(context.path)}: Validator must return 'true' if valid or an error message as a string on errors`);
|
|
176
|
+
let { message, __internal_metadata } = validatorResult, code = validatorResult.code || fallback.code, details = validatorResult.details || fallback.details, normalizedPaths = [];
|
|
177
|
+
validatorResult.path && normalizedPaths.push(validatorResult.path);
|
|
178
|
+
for (let path of validatorResult.paths || []) normalizedPaths.push(path);
|
|
179
|
+
return normalizedPaths.length ? normalizedPaths.map((path) => ({
|
|
180
|
+
code,
|
|
181
|
+
...details && { details },
|
|
182
|
+
path: (context.path || []).concat(path),
|
|
183
|
+
level: level || "error",
|
|
184
|
+
item: { message },
|
|
185
|
+
message,
|
|
186
|
+
__internal_metadata
|
|
187
|
+
})) : [{
|
|
188
|
+
code,
|
|
189
|
+
...details && { details },
|
|
190
|
+
level: level || "error",
|
|
191
|
+
item: { message },
|
|
192
|
+
message,
|
|
193
|
+
path: context.path || [],
|
|
194
|
+
__internal_metadata
|
|
195
|
+
}];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Extracts the correct localized validation message based on given locale source
|
|
199
|
+
*
|
|
200
|
+
* @param message - Localized messages to extract string from
|
|
201
|
+
* @param i18n - Locale source, holding the current locale
|
|
202
|
+
* @returns The localized string, or a fallback "Unknown error" if not found
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
function localizeMessage(message, i18n) {
|
|
206
|
+
let { currentLocale } = i18n, locale = currentLocale.id;
|
|
207
|
+
if (message[locale]) return message[locale];
|
|
208
|
+
if (locale.includes("-")) {
|
|
209
|
+
let language = locale.split("-", 1)[0];
|
|
210
|
+
if (message[language]) return message[language];
|
|
211
|
+
}
|
|
212
|
+
return message["en-US"] || message["en-GB"] || message.en || "Unknown validation error (not localized)";
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Check if passed message/result is a localized message object
|
|
216
|
+
*
|
|
217
|
+
* @param message - Message to check
|
|
218
|
+
* @returns True if message is a localized message object, false otherwise
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
function isLocalizedMessages(message) {
|
|
222
|
+
return message !== !0 && message !== void 0 && typeof message != "string" && isPlainObject(message) && !("message" in message);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Modified version of fast-deep-equal (https://github.com/epoberezkin/fast-deep-equal)
|
|
226
|
+
* MIT-licensed, copyright (c) 2017 Evgeny Poberezkin
|
|
227
|
+
**/
|
|
228
|
+
function deepEqualsIgnoreKey(a, b) {
|
|
229
|
+
if (a === b) return !0;
|
|
230
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
231
|
+
if (a.length != b.length) return !1;
|
|
232
|
+
for (let i = 0; i < a.length; i++) if (!deepEqualsIgnoreKey(a[i], b[i])) return !1;
|
|
233
|
+
return !0;
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(a) != Array.isArray(b)) return !1;
|
|
236
|
+
if (a && b && typeof a == "object" && typeof b == "object") {
|
|
237
|
+
let keys = Object.keys(a);
|
|
238
|
+
if (keys.length !== Object.keys(b).length) return !1;
|
|
239
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
240
|
+
if (a instanceof Date != b instanceof Date) return !1;
|
|
241
|
+
if (a instanceof RegExp && b instanceof RegExp) return a.toString() == b.toString();
|
|
242
|
+
if (a instanceof RegExp != b instanceof RegExp) return !1;
|
|
243
|
+
for (let i = 0; i < keys.length; i++) if (keys[i] !== "_key" && !Object.prototype.hasOwnProperty.call(b, keys[i])) return !1;
|
|
244
|
+
for (let i = 0; i < keys.length; i++) {
|
|
245
|
+
let key = keys[i];
|
|
246
|
+
if (key !== "_key" && !deepEqualsIgnoreKey(a[key], b[key])) return !1;
|
|
247
|
+
}
|
|
248
|
+
return !0;
|
|
249
|
+
}
|
|
250
|
+
return !1;
|
|
251
|
+
}
|
|
252
|
+
const _toString = {}.toString, builtIns = [
|
|
253
|
+
Object,
|
|
254
|
+
Function,
|
|
255
|
+
Array,
|
|
256
|
+
String,
|
|
257
|
+
Boolean,
|
|
258
|
+
Number,
|
|
259
|
+
Date,
|
|
260
|
+
RegExp,
|
|
261
|
+
Error
|
|
262
|
+
];
|
|
263
|
+
function isBuiltIn(_constructor) {
|
|
264
|
+
for (let i = 0; i < builtIns.length; i++) if (builtIns[i] === _constructor) return !0;
|
|
265
|
+
return !1;
|
|
266
|
+
}
|
|
267
|
+
function typeString(obj) {
|
|
268
|
+
let stringType = _toString.call(obj).slice(8, -1);
|
|
269
|
+
if (obj == null) return stringType.toLowerCase();
|
|
270
|
+
let constructorType = obj.constructor;
|
|
271
|
+
return constructorType && !isBuiltIn(constructorType) ? constructorType.name : stringType;
|
|
272
|
+
}
|
|
273
|
+
const SLOW_VALIDATOR_TIMEOUT = 5e3, formatValidationErrors = (options) => {
|
|
274
|
+
let message = options.message || (options.results.length === 1 ? options.results[0]?.message : options.i18n.t("{{messages, list}}", {
|
|
275
|
+
messages: options.results.map((err) => err.message || err.item?.message),
|
|
276
|
+
formatParams: { messages: {
|
|
277
|
+
style: "long",
|
|
278
|
+
type: options.operation
|
|
279
|
+
} }
|
|
280
|
+
}));
|
|
281
|
+
return {
|
|
282
|
+
code: options.operation === "conjunction" ? validationMarkerCodes.ruleAllFailed : validationMarkerCodes.ruleEitherFailed,
|
|
283
|
+
details: { causes: options.results.map(({ code, details, item, message: causeMessage, path }) => ({
|
|
284
|
+
code: code || validationMarkerCodes.validationFailed,
|
|
285
|
+
details,
|
|
286
|
+
message: causeMessage || item?.message,
|
|
287
|
+
path
|
|
288
|
+
})) },
|
|
289
|
+
message: message || "Validation failed"
|
|
290
|
+
};
|
|
291
|
+
}, genericValidators = {
|
|
292
|
+
type: (expectedType, value, message, { i18n }) => {
|
|
293
|
+
let actualType = typeString(value);
|
|
294
|
+
return actualType !== expectedType && actualType !== "undefined" ? {
|
|
295
|
+
code: validationMarkerCodes.valueTypeMismatch,
|
|
296
|
+
details: {
|
|
297
|
+
actualType,
|
|
298
|
+
expectedType
|
|
299
|
+
},
|
|
300
|
+
message: message || i18n.t("validation:generic.incorrect-type", {
|
|
301
|
+
actualType,
|
|
302
|
+
expectedType
|
|
303
|
+
})
|
|
304
|
+
} : !0;
|
|
305
|
+
},
|
|
306
|
+
presence: (expected, value, message, { i18n }) => value === void 0 && expected === "required" ? {
|
|
307
|
+
code: validationMarkerCodes.valueRequired,
|
|
308
|
+
message: message || i18n.t("validation:generic.required")
|
|
309
|
+
} : !0,
|
|
310
|
+
all: async (children, value, message, context) => {
|
|
311
|
+
let results = (await Promise.all(children.map((child) => child.validate(value, context)))).flat();
|
|
312
|
+
return results.length === 0 || formatValidationErrors({
|
|
313
|
+
message,
|
|
314
|
+
results,
|
|
315
|
+
operation: "conjunction",
|
|
316
|
+
i18n: context.i18n
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
either: async (children, value, message, context) => {
|
|
320
|
+
let resolved = await Promise.all(children.map((child) => child.validate(value, context))), results = resolved.flat();
|
|
321
|
+
return resolved.find((result) => !result.length) ? !0 : formatValidationErrors({
|
|
322
|
+
message,
|
|
323
|
+
results,
|
|
324
|
+
operation: "disjunction",
|
|
325
|
+
i18n: context.i18n
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
valid: (allowedValues, actual, message, { i18n }) => {
|
|
329
|
+
let valueType = typeof actual;
|
|
330
|
+
if (valueType === "undefined") return !0;
|
|
331
|
+
let value = (valueType === "number" || valueType === "string") && `${actual}`, strValue = value && value.length > 30 ? `${value.slice(0, 30)}…` : value;
|
|
332
|
+
return allowedValues.some((expected) => deepEqualsIgnoreKey(expected, actual)) ? !0 : {
|
|
333
|
+
code: validationMarkerCodes.valueNotAllowed,
|
|
334
|
+
details: { allowedValuesCount: allowedValues.length },
|
|
335
|
+
message: message || i18n.t("validation:generic.not-allowed", value ? {
|
|
336
|
+
context: "hint",
|
|
337
|
+
replace: { hint: strValue }
|
|
338
|
+
} : {})
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
custom: async (fn, value, message, context) => {
|
|
342
|
+
let slowTimer = setTimeout(() => {
|
|
343
|
+
context.environment === "studio" && console.warn(`Custom validator at ${pathToString(context.path)} has taken more than ${SLOW_VALIDATOR_TIMEOUT}ms to respond`);
|
|
344
|
+
}, SLOW_VALIDATOR_TIMEOUT), result;
|
|
345
|
+
try {
|
|
346
|
+
result = await fn(value, context);
|
|
347
|
+
} finally {
|
|
348
|
+
clearTimeout(slowTimer);
|
|
349
|
+
}
|
|
350
|
+
return isLocalizedMessages(result) ? localizeMessage(result, context.i18n) : typeof result == "string" && message || result;
|
|
351
|
+
}
|
|
352
|
+
}, arrayValidators = {
|
|
353
|
+
...genericValidators,
|
|
354
|
+
min: (minLength, value, message, { i18n, type }) => {
|
|
355
|
+
if (!value || value.length >= minLength) return !0;
|
|
356
|
+
let context = isArrayOfBlocksSchemaType(type) ? "blocks" : void 0;
|
|
357
|
+
return {
|
|
358
|
+
code: validationMarkerCodes.arrayMinimumLength,
|
|
359
|
+
details: {
|
|
360
|
+
actualLength: value.length,
|
|
361
|
+
minimumLength: minLength
|
|
362
|
+
},
|
|
363
|
+
message: message || i18n.t("validation:array.minimum-length", {
|
|
364
|
+
minLength,
|
|
365
|
+
context
|
|
366
|
+
})
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
max: (maxLength, value, message, { i18n, type }) => {
|
|
370
|
+
if (!value || value.length <= maxLength) return !0;
|
|
371
|
+
let context = isArrayOfBlocksSchemaType(type) ? "blocks" : void 0;
|
|
372
|
+
return {
|
|
373
|
+
code: validationMarkerCodes.arrayMaximumLength,
|
|
374
|
+
details: {
|
|
375
|
+
actualLength: value.length,
|
|
376
|
+
maximumLength: maxLength
|
|
377
|
+
},
|
|
378
|
+
message: message || i18n.t("validation:array.maximum-length", {
|
|
379
|
+
maxLength,
|
|
380
|
+
context
|
|
381
|
+
})
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
length: (wantedLength, value, message, { i18n, type }) => {
|
|
385
|
+
if (!value || value.length === wantedLength) return !0;
|
|
386
|
+
let context = isArrayOfBlocksSchemaType(type) ? "blocks" : void 0;
|
|
387
|
+
return {
|
|
388
|
+
code: validationMarkerCodes.arrayExactLength,
|
|
389
|
+
details: {
|
|
390
|
+
actualLength: value.length,
|
|
391
|
+
expectedLength: wantedLength
|
|
392
|
+
},
|
|
393
|
+
message: message || i18n.t("validation:array.exact-length", {
|
|
394
|
+
wantedLength,
|
|
395
|
+
context
|
|
396
|
+
})
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
presence: (flag, value, message, { i18n }) => flag === "required" && !value ? {
|
|
400
|
+
code: validationMarkerCodes.valueRequired,
|
|
401
|
+
message: message || i18n.t("validation:generic.required", { context: "array" })
|
|
402
|
+
} : !0,
|
|
403
|
+
valid: (allowedValues, values, message, { i18n }) => {
|
|
404
|
+
if (values === void 0) return !0;
|
|
405
|
+
let paths = [];
|
|
406
|
+
for (let i = 0; i < values.length; i++) {
|
|
407
|
+
let value = values[i];
|
|
408
|
+
if (allowedValues.some((expected) => deepEqualsIgnoreKey(expected, value))) continue;
|
|
409
|
+
let pathSegment = value && value._key ? { _key: value._key } : i;
|
|
410
|
+
paths.push([pathSegment]);
|
|
411
|
+
}
|
|
412
|
+
let sharedMessage = message || i18n.t("validation:generic.not-allowed");
|
|
413
|
+
return paths.map((path) => ({
|
|
414
|
+
code: validationMarkerCodes.valueNotAllowed,
|
|
415
|
+
details: { allowedValuesCount: allowedValues.length },
|
|
416
|
+
message: sharedMessage,
|
|
417
|
+
path
|
|
418
|
+
}));
|
|
419
|
+
},
|
|
420
|
+
unique: (_unused, value, message, { i18n }) => {
|
|
421
|
+
let dupeIndices = [];
|
|
422
|
+
if (!value) return !0;
|
|
423
|
+
for (let x = 0; x < value.length; x++) for (let y = x + 1; y < value.length; y++) {
|
|
424
|
+
let itemA = value[x], itemB = value[y];
|
|
425
|
+
deepEqualsIgnoreKey(itemA, itemB) && (dupeIndices.indexOf(x) === -1 && dupeIndices.push(x), dupeIndices.indexOf(y) === -1 && dupeIndices.push(y));
|
|
426
|
+
}
|
|
427
|
+
let paths = dupeIndices.map((idx) => {
|
|
428
|
+
let item = value[idx];
|
|
429
|
+
return [item && item._key ? { _key: item._key } : idx];
|
|
430
|
+
}), sharedMessage = message || i18n.t("validation:array.item-duplicate");
|
|
431
|
+
return paths.map((path) => ({
|
|
432
|
+
code: validationMarkerCodes.arrayDuplicateItem,
|
|
433
|
+
message: sharedMessage,
|
|
434
|
+
path
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
}, booleanValidators = {
|
|
438
|
+
...genericValidators,
|
|
439
|
+
presence: (flag, value, message, { i18n }) => flag === "required" && typeof value != "boolean" ? {
|
|
440
|
+
code: validationMarkerCodes.valueRequired,
|
|
441
|
+
message: message || i18n.t("validation:generic.required", { context: "boolean" })
|
|
442
|
+
} : !0
|
|
443
|
+
};
|
|
444
|
+
function isRecord$1(obj) {
|
|
445
|
+
return typeof obj == "object" && !!obj && !Array.isArray(obj);
|
|
446
|
+
}
|
|
447
|
+
const isoDate = /^(?:[-+]\d{2})?(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/, getFormattedDate = (type = "", value, options) => {
|
|
448
|
+
let dateFormat = options?.dateFormat || legacyDateFormat.DEFAULT_DATE_FORMAT, timeFormat = options?.timeFormat || legacyDateFormat.DEFAULT_TIME_FORMAT;
|
|
449
|
+
return legacyDateFormat.format(value, type === "date" ? dateFormat : `${dateFormat} ${timeFormat}`, { useUTC: type === "date" });
|
|
450
|
+
};
|
|
451
|
+
function parseDate(date, throwOnError = !1) {
|
|
452
|
+
if (!date) return null;
|
|
453
|
+
if (date === "now") return /* @__PURE__ */ new Date();
|
|
454
|
+
let parsed = new Date(date), isInvalid = isNaN(parsed.getTime());
|
|
455
|
+
if (isInvalid && throwOnError) throw Error(`Unable to parse "${date}" to a date`);
|
|
456
|
+
return isInvalid ? null : parsed;
|
|
457
|
+
}
|
|
458
|
+
const dateValidators = {
|
|
459
|
+
...genericValidators,
|
|
460
|
+
type: (_unused, value, message, { i18n }) => value === void 0 || isoDate.test(`${value}`) ? !0 : {
|
|
461
|
+
code: validationMarkerCodes.dateInvalidFormat,
|
|
462
|
+
details: { actualValue: value },
|
|
463
|
+
message: message || i18n.t("validation:date.invalid-format")
|
|
464
|
+
},
|
|
465
|
+
min: (minDate, value, message, { type, i18n }) => {
|
|
466
|
+
let dateVal = parseDate(value), minDateVal = parseDate(minDate, !0);
|
|
467
|
+
if (!dateVal || !value || dateVal >= minDateVal) return !0;
|
|
468
|
+
if (!type) throw Error("`type` was not provided in validation context.");
|
|
469
|
+
let dateTimeOptions = isRecord$1(type.options) ? type.options : {};
|
|
470
|
+
return {
|
|
471
|
+
code: validationMarkerCodes.dateMinimum,
|
|
472
|
+
details: {
|
|
473
|
+
actualValue: value,
|
|
474
|
+
minimum: minDate
|
|
475
|
+
},
|
|
476
|
+
message: message || i18n.t("validation:date.minimum", {
|
|
477
|
+
minDate: getFormattedDate(type.name, minDateVal, dateTimeOptions),
|
|
478
|
+
providedMinDate: minDate
|
|
479
|
+
})
|
|
480
|
+
};
|
|
481
|
+
},
|
|
482
|
+
max: (maxDate, value, message, { type, i18n }) => {
|
|
483
|
+
let dateVal = parseDate(value), maxDateVal = parseDate(maxDate, !0);
|
|
484
|
+
if (!dateVal || !value || dateVal <= maxDateVal) return !0;
|
|
485
|
+
if (!type) throw Error("`type` was not provided in validation context.");
|
|
486
|
+
let dateTimeOptions = isRecord$1(type.options) ? type.options : {};
|
|
487
|
+
return {
|
|
488
|
+
code: validationMarkerCodes.dateMaximum,
|
|
489
|
+
details: {
|
|
490
|
+
actualValue: value,
|
|
491
|
+
maximum: maxDate
|
|
492
|
+
},
|
|
493
|
+
message: message || i18n.t("validation:date.maximum", {
|
|
494
|
+
maxDate: getFormattedDate(type.name, maxDateVal, dateTimeOptions),
|
|
495
|
+
providedMaxDate: maxDate
|
|
496
|
+
})
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}, precisionRx = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/, numberValidators = {
|
|
500
|
+
...genericValidators,
|
|
501
|
+
integer: (_unused, value, message, { i18n }) => Number.isInteger(value) ? !0 : {
|
|
502
|
+
code: validationMarkerCodes.numberInteger,
|
|
503
|
+
details: { actualValue: value },
|
|
504
|
+
message: message || i18n.t("validation:number.non-integer")
|
|
505
|
+
},
|
|
506
|
+
precision: (limit, value, message, { i18n }) => {
|
|
507
|
+
if (value === void 0) return !0;
|
|
508
|
+
let places = value.toString().match(precisionRx), decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0);
|
|
509
|
+
return decimals > limit ? {
|
|
510
|
+
code: validationMarkerCodes.numberPrecision,
|
|
511
|
+
details: {
|
|
512
|
+
actualPrecision: decimals,
|
|
513
|
+
maximumPrecision: limit
|
|
514
|
+
},
|
|
515
|
+
message: message || i18n.t("validation:number.maximum-precision", { limit })
|
|
516
|
+
} : !0;
|
|
517
|
+
},
|
|
518
|
+
min: (minNumber, value, message, { i18n }) => value >= minNumber || {
|
|
519
|
+
code: validationMarkerCodes.numberMinimum,
|
|
520
|
+
details: {
|
|
521
|
+
actualValue: value,
|
|
522
|
+
minimum: minNumber
|
|
523
|
+
},
|
|
524
|
+
message: message || i18n.t("validation:number.minimum", { minNumber })
|
|
525
|
+
},
|
|
526
|
+
max: (maxNumber, value, message, { i18n }) => value <= maxNumber || {
|
|
527
|
+
code: validationMarkerCodes.numberMaximum,
|
|
528
|
+
details: {
|
|
529
|
+
actualValue: value,
|
|
530
|
+
maximum: maxNumber
|
|
531
|
+
},
|
|
532
|
+
message: message || i18n.t("validation:number.maximum", { maxNumber })
|
|
533
|
+
},
|
|
534
|
+
greaterThan: (threshold, value, message, { i18n }) => value > threshold || {
|
|
535
|
+
code: validationMarkerCodes.numberGreaterThan,
|
|
536
|
+
details: {
|
|
537
|
+
actualValue: value,
|
|
538
|
+
threshold
|
|
539
|
+
},
|
|
540
|
+
message: message || i18n.t("validation:number.greater-than", { threshold })
|
|
541
|
+
},
|
|
542
|
+
lessThan: (threshold, value, message, { i18n }) => value < threshold || {
|
|
543
|
+
code: validationMarkerCodes.numberLessThan,
|
|
544
|
+
details: {
|
|
545
|
+
actualValue: value,
|
|
546
|
+
threshold
|
|
547
|
+
},
|
|
548
|
+
message: message || i18n.t("validation:number.less-than", { threshold })
|
|
549
|
+
}
|
|
550
|
+
}, metaKeys = [
|
|
551
|
+
"_key",
|
|
552
|
+
"_type",
|
|
553
|
+
"_weak"
|
|
554
|
+
], objectValidators = {
|
|
555
|
+
...genericValidators,
|
|
556
|
+
presence: (expected, value, message, { i18n }) => {
|
|
557
|
+
if (expected !== "required") return !0;
|
|
558
|
+
let keys = value && Object.keys(value).filter((key) => !metaKeys.includes(key));
|
|
559
|
+
return value === void 0 || keys && keys.length === 0 ? {
|
|
560
|
+
code: validationMarkerCodes.valueRequired,
|
|
561
|
+
message: message || i18n.t("validation:generic.required", { context: "object" })
|
|
562
|
+
} : !0;
|
|
563
|
+
},
|
|
564
|
+
reference: async (_unused, value, message, context) => {
|
|
565
|
+
if (!value) return !0;
|
|
566
|
+
let { type, document, getDocumentExists, i18n } = context;
|
|
567
|
+
if (!isReference(value)) return {
|
|
568
|
+
code: validationMarkerCodes.referenceInvalid,
|
|
569
|
+
details: { actualType: typeString(value) },
|
|
570
|
+
message: message || i18n.t("validation:object.not-reference")
|
|
571
|
+
};
|
|
572
|
+
if (!type) throw Error("`type` was not provided in validation context");
|
|
573
|
+
if ("weak" in type && type.weak) return !0;
|
|
574
|
+
if (!getDocumentExists) throw Error("`getDocumentExists` was not provided in validation context");
|
|
575
|
+
let documentId = document?._id;
|
|
576
|
+
return documentId && value._ref == getPublishedId(documentId) || await getDocumentExists({ id: value._ref }) ? !0 : {
|
|
577
|
+
code: validationMarkerCodes.referenceNotPublished,
|
|
578
|
+
details: { referenceId: value._ref },
|
|
579
|
+
message: i18n.t("validation:object.reference-not-published", { documentId: value._ref })
|
|
580
|
+
};
|
|
581
|
+
},
|
|
582
|
+
assetRequired: (flag, value, message, { i18n }) => !value || !value.asset || !value.asset._ref ? {
|
|
583
|
+
__internal_metadata: { name: "assetRequired" },
|
|
584
|
+
code: validationMarkerCodes.assetRequired,
|
|
585
|
+
details: { assetType: flag.assetType },
|
|
586
|
+
message: message || i18n.t("validation:object.asset-required", { context: flag.assetType || "" })
|
|
587
|
+
} : !0,
|
|
588
|
+
media: async (fn, value, message, context) => {
|
|
589
|
+
let slowTimer = setTimeout(() => {
|
|
590
|
+
context.environment === "studio" && console.warn(`Media validator at ${pathToString(context.path)} has taken more than ${SLOW_VALIDATOR_TIMEOUT}ms to respond`);
|
|
591
|
+
}, SLOW_VALIDATOR_TIMEOUT);
|
|
592
|
+
if (!value) return !0;
|
|
593
|
+
if (!value || !value.media || !value.media._ref) return {
|
|
594
|
+
code: validationMarkerCodes.mediaInvalidReference,
|
|
595
|
+
message: context.i18n.t("validation:object.not-media-library-asset")
|
|
596
|
+
};
|
|
597
|
+
let result = !0;
|
|
598
|
+
try {
|
|
599
|
+
let [type, libraryId, documentId] = value.media._ref.split(":", 3), resourceConfig = { resource: {
|
|
600
|
+
type,
|
|
601
|
+
id: libraryId
|
|
602
|
+
} }, asset = await context.getClient({ apiVersion: "2025-02-19" }).withConfig(resourceConfig).fetch("*[_id == $id] { ..., 'currentVersion': @.currentVersion-> { ... } }[0]", { id: documentId });
|
|
603
|
+
if (!asset) return console.warn(`${context.i18n.t("validation:object.media-not-found")}\nAsset ID: ${value.media._ref}`), {
|
|
604
|
+
code: validationMarkerCodes.mediaNotFound,
|
|
605
|
+
details: { referenceId: value.media._ref },
|
|
606
|
+
message: context.i18n.t("validation:object.media-not-found")
|
|
607
|
+
};
|
|
608
|
+
result = await fn({
|
|
609
|
+
media: { asset },
|
|
610
|
+
value
|
|
611
|
+
}, context);
|
|
612
|
+
} catch (err) {
|
|
613
|
+
throw Error(`Media validator at ${pathToString(context.path)} failed with an error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
614
|
+
} finally {
|
|
615
|
+
clearTimeout(slowTimer);
|
|
616
|
+
}
|
|
617
|
+
let validationErrorMetadata = { __internal_metadata: { name: "media" } };
|
|
618
|
+
return result === !0 || Array.isArray(result) ? result : [result].map((res) => typeof res == "string" ? {
|
|
619
|
+
...validationErrorMetadata,
|
|
620
|
+
message: message || res
|
|
621
|
+
} : isLocalizedMessages(res) ? {
|
|
622
|
+
...validationErrorMetadata,
|
|
623
|
+
message: message || localizeMessage(res, context.i18n)
|
|
624
|
+
} : {
|
|
625
|
+
...validationErrorMetadata,
|
|
626
|
+
...res,
|
|
627
|
+
message: message || res.message
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}, DUMMY_ORIGIN = "http://sanity", isRelativeUrl = (url) => /^\.*\//.test(url), 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,}))$/, typeValidators = {
|
|
631
|
+
Boolean: booleanValidators,
|
|
632
|
+
Number: numberValidators,
|
|
633
|
+
String: {
|
|
634
|
+
...genericValidators,
|
|
635
|
+
min: (minLength, value, message, { i18n }) => !value || value.length >= minLength || {
|
|
636
|
+
code: validationMarkerCodes.stringMinimumLength,
|
|
637
|
+
details: {
|
|
638
|
+
actualLength: value.length,
|
|
639
|
+
minimumLength: minLength
|
|
640
|
+
},
|
|
641
|
+
message: message || i18n.t("validation:string.minimum-length", { minLength })
|
|
642
|
+
},
|
|
643
|
+
max: (maxLength, value, message, { i18n }) => !value || value.length <= maxLength || {
|
|
644
|
+
code: validationMarkerCodes.stringMaximumLength,
|
|
645
|
+
details: {
|
|
646
|
+
actualLength: value.length,
|
|
647
|
+
maximumLength: maxLength
|
|
648
|
+
},
|
|
649
|
+
message: message || i18n.t("validation:string.maximum-length", { maxLength })
|
|
650
|
+
},
|
|
651
|
+
length: (wantedLength, value, message, { i18n }) => {
|
|
652
|
+
let strValue = value || "";
|
|
653
|
+
return strValue.length === wantedLength || {
|
|
654
|
+
code: validationMarkerCodes.stringExactLength,
|
|
655
|
+
details: {
|
|
656
|
+
actualLength: strValue.length,
|
|
657
|
+
expectedLength: wantedLength
|
|
658
|
+
},
|
|
659
|
+
message: message || i18n.t("validation:string.exact-length", { wantedLength })
|
|
660
|
+
};
|
|
661
|
+
},
|
|
662
|
+
uri: (constraints, value, message, { i18n }) => {
|
|
663
|
+
let strValue = value || "";
|
|
664
|
+
if (!strValue) return !0;
|
|
665
|
+
let { options } = constraints, { allowCredentials, relativeOnly } = options, allowRelative = options.allowRelative || relativeOnly, url;
|
|
666
|
+
try {
|
|
667
|
+
url = allowRelative ? new URL(strValue, DUMMY_ORIGIN) : new URL(strValue);
|
|
668
|
+
} catch {
|
|
669
|
+
return {
|
|
670
|
+
code: validationMarkerCodes.stringUrlInvalid,
|
|
671
|
+
message: message || i18n.t("validation:string.url.invalid")
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
if (relativeOnly && url.origin !== DUMMY_ORIGIN) return {
|
|
675
|
+
code: validationMarkerCodes.stringUrlNotRelative,
|
|
676
|
+
message: message || i18n.t("validation:string.url.not-relative")
|
|
677
|
+
};
|
|
678
|
+
if (!allowRelative && url.origin === DUMMY_ORIGIN && isRelativeUrl(strValue)) return {
|
|
679
|
+
code: validationMarkerCodes.stringUrlNotAbsolute,
|
|
680
|
+
message: message || i18n.t("validation:string.url.not-absolute")
|
|
681
|
+
};
|
|
682
|
+
if (!allowCredentials && (url.username || url.password)) return {
|
|
683
|
+
code: validationMarkerCodes.stringUrlCredentialsNotAllowed,
|
|
684
|
+
message: message || i18n.t("validation:string.url.includes-credentials")
|
|
685
|
+
};
|
|
686
|
+
let urlScheme = url.protocol.replace(/:$/, "");
|
|
687
|
+
return isRelativeUrl(strValue) || options.scheme.some((scheme) => scheme.test(urlScheme)) ? !0 : {
|
|
688
|
+
code: validationMarkerCodes.stringUrlSchemeNotAllowed,
|
|
689
|
+
details: { scheme: urlScheme },
|
|
690
|
+
message: message || i18n.t("validation:string.url.disallowed-scheme", { scheme: urlScheme })
|
|
691
|
+
};
|
|
692
|
+
},
|
|
693
|
+
stringCasing: (casing, value, message, { i18n }) => {
|
|
694
|
+
let strValue = value || "";
|
|
695
|
+
return casing === "uppercase" && strValue !== strValue.toLocaleUpperCase() ? {
|
|
696
|
+
code: validationMarkerCodes.stringUppercase,
|
|
697
|
+
message: message || i18n.t("validation:string.uppercase")
|
|
698
|
+
} : casing === "lowercase" && strValue !== strValue.toLocaleLowerCase() ? {
|
|
699
|
+
code: validationMarkerCodes.stringLowercase,
|
|
700
|
+
message: message || i18n.t("validation:string.lowercase")
|
|
701
|
+
} : !0;
|
|
702
|
+
},
|
|
703
|
+
presence: (flag, value, message, { i18n }) => flag === "required" && !value ? {
|
|
704
|
+
code: validationMarkerCodes.valueRequired,
|
|
705
|
+
message: message || i18n.t("validation:generic.required", { context: "string" })
|
|
706
|
+
} : !0,
|
|
707
|
+
regex: (options, value, message, { i18n }) => {
|
|
708
|
+
let { pattern, name, invert } = options, regName = name || `${pattern.toString()}`, strValue = value || "";
|
|
709
|
+
pattern.lastIndex = 0;
|
|
710
|
+
let matches = pattern.test(strValue);
|
|
711
|
+
return !invert && !matches || invert && matches ? {
|
|
712
|
+
code: invert ? validationMarkerCodes.stringRegexMatch : validationMarkerCodes.stringRegexMismatch,
|
|
713
|
+
details: {
|
|
714
|
+
invert,
|
|
715
|
+
name: regName,
|
|
716
|
+
pattern: pattern.source
|
|
717
|
+
},
|
|
718
|
+
message: message || (invert ? i18n.t("validation:string.regex-match", { name: regName }) : i18n.t("validation:string.regex-does-not-match", { name: regName }))
|
|
719
|
+
} : !0;
|
|
720
|
+
},
|
|
721
|
+
email: (_unused, value, message, { i18n }) => {
|
|
722
|
+
let strValue = `${value || ""}`.trim();
|
|
723
|
+
return !strValue || emailRegex.test(strValue) ? !0 : {
|
|
724
|
+
code: validationMarkerCodes.stringEmail,
|
|
725
|
+
message: message || i18n.t("validation:string.email")
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
Array: arrayValidators,
|
|
730
|
+
Object: objectValidators,
|
|
731
|
+
Date: dateValidators
|
|
732
|
+
}, isFieldRef = (constraint) => typeof constraint != "object" || !constraint ? !1 : constraint.type === Rule$2.FIELD_REF, EMPTY_ARRAY = [], fallbackCodeForRule = (flag) => flag === "custom" ? validationMarkerCodes.custom : flag === "media" ? validationMarkerCodes.mediaCustom : flag === "all" ? validationMarkerCodes.ruleAllFailed : flag === "either" ? validationMarkerCodes.ruleEitherFailed : validationMarkerCodes.validationFailed, Rule$2 = class Rule extends Rule$1 {
|
|
733
|
+
static array = (def) => new Rule(def).type("Array");
|
|
734
|
+
static object = (def) => new Rule(def).type("Object");
|
|
735
|
+
static string = (def) => new Rule(def).type("String");
|
|
736
|
+
static number = (def) => new Rule(def).type("Number");
|
|
737
|
+
static boolean = (def) => new Rule(def).type("Boolean");
|
|
738
|
+
static dateTime = (def) => new Rule(def).type("Date");
|
|
739
|
+
clone() {
|
|
740
|
+
let rule = new Rule();
|
|
741
|
+
return rule._type = this._type, rule._message = this._message, rule._required = this._required, rule._rules = [...this._rules], rule._level = this._level, rule._fieldRules = this._fieldRules, rule._typeDef = this._typeDef, rule;
|
|
742
|
+
}
|
|
743
|
+
async validate(value, { __internal = {}, ...context }) {
|
|
744
|
+
let { customValidationConcurrencyLimiter } = __internal, valueIsEmpty = value == null;
|
|
745
|
+
if (valueIsEmpty && this._required === "optional") return EMPTY_ARRAY;
|
|
746
|
+
let rules = this._required === void 0 && valueIsEmpty ? this._rules.filter((curr) => curr.flag === "custom") : this._rules, validators = this._type && typeValidators[this._type] || genericValidators;
|
|
747
|
+
return (await Promise.all(rules.map(async (curr) => {
|
|
748
|
+
if (curr.flag === void 0) throw Error("Invalid rule, did not contain \"flag\"-property");
|
|
749
|
+
let validator = validators[curr.flag];
|
|
750
|
+
if (!validator) {
|
|
751
|
+
let forType = this._type ? `type "${this._type}"` : "rule without declared type";
|
|
752
|
+
throw Error(`Validator for flag "${curr.flag}" not found for ${forType}`);
|
|
753
|
+
}
|
|
754
|
+
let specConstraint = "constraint" in curr ? curr.constraint : null;
|
|
755
|
+
if (isFieldRef(specConstraint) && (specConstraint = get(context.parent, specConstraint.path)), curr.flag === "custom" && customValidationConcurrencyLimiter && !specConstraint?.bypassConcurrencyLimit) {
|
|
756
|
+
let customValidator = specConstraint;
|
|
757
|
+
specConstraint = async (...args) => {
|
|
758
|
+
await customValidationConcurrencyLimiter.ready();
|
|
759
|
+
try {
|
|
760
|
+
return await customValidator(...args);
|
|
761
|
+
} finally {
|
|
762
|
+
customValidationConcurrencyLimiter.release();
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
let message = isLocalizedMessages(this._message) ? localizeMessage(this._message, context.i18n) : this._message;
|
|
767
|
+
try {
|
|
768
|
+
return convertToValidationMarker(await validator(specConstraint, value, message, context), this._level, context, { code: fallbackCodeForRule(curr.flag) });
|
|
769
|
+
} catch (err) {
|
|
770
|
+
let errorMessage = `${pathToString(context.path)}: Exception occurred while validating value: ${err.message}`;
|
|
771
|
+
return convertToValidationMarker({
|
|
772
|
+
code: validationMarkerCodes.validationException,
|
|
773
|
+
message: errorMessage
|
|
774
|
+
}, "error", context);
|
|
775
|
+
}
|
|
776
|
+
}))).flat();
|
|
777
|
+
}
|
|
778
|
+
}, memoizedWarnOnArraySlug = memoize(warnOnArraySlug);
|
|
779
|
+
function serializePath(path) {
|
|
780
|
+
return path.reduce((target, part, i) => {
|
|
781
|
+
let isIndex = typeof part == "number", isKey = isKeyedObject(part);
|
|
782
|
+
return `${target}${isIndex || isKey ? "[]" : `${i === 0 ? "" : "."}${part}`}`;
|
|
783
|
+
}, "");
|
|
784
|
+
}
|
|
785
|
+
const defaultIsUnique = (slug, context) => {
|
|
786
|
+
let { getClient, document, path, type } = context, schemaOptions = type?.options;
|
|
787
|
+
if (!document) throw Error("`document` was not provided in validation context.");
|
|
788
|
+
if (!path) throw Error("`path` was not provided in validation context.");
|
|
789
|
+
let disableArrayWarning = schemaOptions?.disableArrayWarning || !1, docType = document._type, atPath = serializePath(path.concat("current"));
|
|
790
|
+
!disableArrayWarning && atPath.includes("[]") && context.environment === "studio" && memoizedWarnOnArraySlug(serializePath(path));
|
|
791
|
+
let constraints = [
|
|
792
|
+
"_type == $docType",
|
|
793
|
+
"!sanity::versionOf($published)",
|
|
794
|
+
`${atPath} == $slug`
|
|
795
|
+
].join(" && ");
|
|
796
|
+
return getClient({ apiVersion: "2025-02-19" }).withConfig({ perspective: "raw" }).fetch(`!defined(*[${constraints}][0]._id)`, {
|
|
797
|
+
docType,
|
|
798
|
+
published: getPublishedId(document._id),
|
|
799
|
+
slug
|
|
800
|
+
}, { tag: "validation.slug-is-unique" });
|
|
801
|
+
};
|
|
802
|
+
function warnOnArraySlug(serializedPath) {
|
|
803
|
+
console.warn([
|
|
804
|
+
`Slug field at path ${serializedPath} is within an array and cannot be automatically checked for uniqueness`,
|
|
805
|
+
"If you need to check for uniqueness, provide your own \"isUnique\" method",
|
|
806
|
+
"To disable this message, set `disableArrayWarning: true` on the slug `options` field"
|
|
807
|
+
].join("\n"));
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Validates slugs values by querying for uniqueness from the client.
|
|
811
|
+
*
|
|
812
|
+
* This is a custom rule implementation (e.g. `Rule.custom(slugValidator)`)
|
|
813
|
+
* that's populated in `inferFromSchemaType` when the type name is `slug`
|
|
814
|
+
*/
|
|
815
|
+
const slugValidator = async (value, context) => {
|
|
816
|
+
if (!value) return !0;
|
|
817
|
+
let { i18n } = context;
|
|
818
|
+
if (typeof value != "object" || Array.isArray(value)) return {
|
|
819
|
+
code: validationMarkerCodes.slugInvalidType,
|
|
820
|
+
details: { actualType: typeString(value) },
|
|
821
|
+
message: i18n.t("validation:slug.not-object")
|
|
822
|
+
};
|
|
823
|
+
if (!isSlug(value) || value.current.trim().length === 0) return {
|
|
824
|
+
code: validationMarkerCodes.slugMissingCurrent,
|
|
825
|
+
message: i18n.t("validation:slug.missing-current")
|
|
826
|
+
};
|
|
827
|
+
let isUnique = context?.type?.options?.isUnique || defaultIsUnique, slugContext = {
|
|
828
|
+
...context,
|
|
829
|
+
parent: context.parent,
|
|
830
|
+
type: context.type,
|
|
831
|
+
defaultIsUnique
|
|
832
|
+
};
|
|
833
|
+
return await isUnique(value.current, slugContext) ? !0 : {
|
|
834
|
+
code: validationMarkerCodes.slugNotUnique,
|
|
835
|
+
details: { slug: value.current },
|
|
836
|
+
message: i18n.t("validation:slug.not-unique", { slug: value.current })
|
|
837
|
+
};
|
|
838
|
+
}, ruleConstraintTypes = {
|
|
839
|
+
array: !0,
|
|
840
|
+
boolean: !0,
|
|
841
|
+
date: !0,
|
|
842
|
+
number: !0,
|
|
843
|
+
object: !0,
|
|
844
|
+
string: !0
|
|
845
|
+
}, isRuleConstraint = (typeString) => typeString in ruleConstraintTypes;
|
|
846
|
+
function getTypeChain(type, visited = /* @__PURE__ */ new Set()) {
|
|
847
|
+
return !type || visited.has(type) ? [] : (visited.add(type), [...type.type ? getTypeChain(type.type, visited) : [], type]);
|
|
848
|
+
}
|
|
849
|
+
function baseRuleReducer(inputRule, type) {
|
|
850
|
+
let baseRule = inputRule;
|
|
851
|
+
isRuleConstraint(type.jsonType) && (baseRule = baseRule.type(type.jsonType));
|
|
852
|
+
let typeOptionsList = type?.options && typeof type.options == "object" && "list" in type.options && type.options.list;
|
|
853
|
+
return Array.isArray(typeOptionsList) && (baseRule = baseRule.valid(typeOptionsList.map((option) => extractValueFromListOption(option, type)))), type.name === "datetime" || type.name === "date" ? baseRule.type("Date") : type.name === "url" ? baseRule.uri() : type.name === "slug" ? baseRule.custom(slugValidator, { bypassConcurrencyLimit: !0 }) : type.name === "reference" ? baseRule.reference() : type.name === "email" ? baseRule.email() : baseRule;
|
|
854
|
+
}
|
|
855
|
+
function hasValueField(typeDef) {
|
|
856
|
+
return typeDef ? !("fields" in typeDef) && typeDef.type ? hasValueField(typeDef.type) : !("fields" in typeDef) || !Array.isArray(typeDef.fields) ? !1 : typeDef.fields.some((field) => field.name === "value") : !1;
|
|
857
|
+
}
|
|
858
|
+
function extractValueFromListOption(option, typeDef) {
|
|
859
|
+
return typeDef.jsonType === "object" && hasValueField(typeDef) || option.value === void 0 ? option : option.value;
|
|
860
|
+
}
|
|
861
|
+
const isUriSpec = (spec) => spec.flag === "uri";
|
|
862
|
+
function omitLeakedDefaultUri(rules, typeDef) {
|
|
863
|
+
if (!getTypeChain(typeDef).some((type) => type.name === "url")) return rules;
|
|
864
|
+
let defaultUri = new Rule$2(typeDef).uri()._rules.find(isUriSpec)?.constraint, isDefaultUri = (spec) => isUriSpec(spec) && isEqual(spec.constraint, defaultUri), isCustomUri = (spec) => isUriSpec(spec) && !isEqual(spec.constraint, defaultUri);
|
|
865
|
+
return rules.some((rule) => rule._rules.some(isCustomUri)) ? rules.map((rule) => {
|
|
866
|
+
if (!rule._rules.some(isDefaultUri)) return rule;
|
|
867
|
+
let cleaned = rule.clone();
|
|
868
|
+
return cleaned._rules = rule._rules.filter((spec) => !isDefaultUri(spec)), cleaned;
|
|
869
|
+
}) : rules;
|
|
870
|
+
}
|
|
871
|
+
function normalizeValidationRules(typeDef, context) {
|
|
872
|
+
if (!typeDef) return [];
|
|
873
|
+
let validation = typeDef.validation;
|
|
874
|
+
if (Array.isArray(validation)) return omitLeakedDefaultUri(validation.flatMap((i) => normalizeValidationRules({
|
|
875
|
+
...typeDef,
|
|
876
|
+
validation: i
|
|
877
|
+
}, context)), typeDef);
|
|
878
|
+
let baseRule = Object.values(getTypeChain(typeDef).reduce((acc, type) => (acc[type.name] = type, acc), {})).reduce(baseRuleReducer, new Rule$2(typeDef));
|
|
879
|
+
return validation && typeof validation == "object" ? [validation] : validation && typeof validation == "function" ? normalizeValidationRules({
|
|
880
|
+
...typeDef,
|
|
881
|
+
validation: validation(baseRule, context)
|
|
882
|
+
}, context) : [baseRule];
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Simple requestIdleCallback polyfill
|
|
886
|
+
* Can be removed when all browsers support requestIdleCallback: https://caniuse.com/requestidlecallback
|
|
887
|
+
* @param callback -
|
|
888
|
+
* @param options -
|
|
889
|
+
*/
|
|
890
|
+
const requestIdleCallbackShim = function requestIdleCallbackShim(callback, _options) {
|
|
891
|
+
let start = Date.now();
|
|
892
|
+
return globalThis.setTimeout(() => {
|
|
893
|
+
callback({
|
|
894
|
+
didTimeout: !1,
|
|
895
|
+
timeRemaining() {
|
|
896
|
+
return Math.max(0, Date.now() - start);
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
}, 0);
|
|
900
|
+
}, cancelIdleCallbackShim = function cancelIdleCallbackShim(handle) {
|
|
901
|
+
return globalThis.clearTimeout(handle);
|
|
902
|
+
}, win = typeof window > "u" ? void 0 : window, requestIdleCallback = win?.requestIdleCallback || requestIdleCallbackShim, cancelIdleCallback = win?.cancelIdleCallback || cancelIdleCallbackShim, unknownFieldsValidator = (type) => (value) => {
|
|
903
|
+
if (typeof value != "object" || !value) return !0;
|
|
904
|
+
let fieldNames = new Set(type.fields?.map((field) => field.name));
|
|
905
|
+
return Object.keys(value).filter((key) => !key.startsWith("_")).filter((key) => !fieldNames.has(key)).map((unknownField) => ({
|
|
906
|
+
code: validationMarkerCodes.objectUnknownField,
|
|
907
|
+
details: {
|
|
908
|
+
fieldName: unknownField,
|
|
909
|
+
typeName: type.name
|
|
910
|
+
},
|
|
911
|
+
message: `Field '${unknownField}' does not exist on type '${type.name}'`,
|
|
912
|
+
path: [unknownField]
|
|
913
|
+
}));
|
|
914
|
+
}, DEFAULT_VALIDATION_CLIENT_OPTIONS = { apiVersion: "2025-02-19" }, isRecord = (maybeRecord) => typeof maybeRecord == "object" && !!maybeRecord && !Array.isArray(maybeRecord);
|
|
915
|
+
/**
|
|
916
|
+
* Recursively extracts all `_fieldRules` from a rule and its nested constraints.
|
|
917
|
+
* This handles cases where `Rule.fields()` is used inside `Rule.all()` or `Rule.either()`.
|
|
918
|
+
*/
|
|
919
|
+
function extractFieldRulesFromRule(rule) {
|
|
920
|
+
let results = [];
|
|
921
|
+
rule._fieldRules && results.push(rule._fieldRules);
|
|
922
|
+
for (let ruleSpec of rule._rules) if (ruleSpec.flag === "all" || ruleSpec.flag === "either") {
|
|
923
|
+
let childRules = ruleSpec.constraint;
|
|
924
|
+
if (Array.isArray(childRules)) for (let childRule of childRules) results.push(...extractFieldRulesFromRule(childRule));
|
|
925
|
+
}
|
|
926
|
+
return results;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* @internal
|
|
930
|
+
*/
|
|
931
|
+
function resolveTypeForArrayItem(item, candidates) {
|
|
932
|
+
if (candidates.length === 1) return candidates[0];
|
|
933
|
+
let itemType = isTypedObject(item) && item._type, primitive = item == null || !itemType && typeString(item).toLowerCase();
|
|
934
|
+
return primitive && primitive !== "object" ? candidates.find((candidate) => candidate.jsonType === primitive) : candidates.find((candidate) => candidate.type?.name === itemType) || candidates.find((candidate) => candidate.name === itemType) || candidates.find((candidate) => candidate.name === "object" && primitive === "object");
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Validates a document against the schema in a resolved Studio workspace or source.
|
|
938
|
+
*
|
|
939
|
+
* @beta
|
|
940
|
+
* @deprecated Prefer {@link validateDocument} with `{document, schema, client}` for new code.
|
|
941
|
+
*/
|
|
942
|
+
function validateDocumentWithWorkspace({ document, workspace, getClient = workspace.getClient, getDocumentExists, environment = "studio", maxCustomValidationConcurrency, maxFetchConcurrency, currentUser }) {
|
|
943
|
+
return validateDocumentInternal({
|
|
944
|
+
currentUser,
|
|
945
|
+
document,
|
|
946
|
+
environment,
|
|
947
|
+
getClient,
|
|
948
|
+
getDocumentExists,
|
|
949
|
+
i18n: workspace.i18n,
|
|
950
|
+
maxCustomValidationConcurrency,
|
|
951
|
+
maxFetchConcurrency,
|
|
952
|
+
schema: workspace.schema
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
function validateDocument(options) {
|
|
956
|
+
if ("workspace" in options) return validateDocumentWithWorkspace(options);
|
|
957
|
+
let { client, document, schema, ...internalOptions } = options;
|
|
958
|
+
return validateDocumentInternal({
|
|
959
|
+
...internalOptions,
|
|
960
|
+
document,
|
|
961
|
+
environment: "cli",
|
|
962
|
+
getClient: ({ apiVersion }) => client.withConfig({ apiVersion }),
|
|
963
|
+
i18n: getFallbackLocaleSource(),
|
|
964
|
+
schema
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
/** @internal */
|
|
968
|
+
function validateDocumentInternal({ document, schema, getClient, getDocumentExists, i18n = getFallbackLocaleSource(), environment, maxCustomValidationConcurrency, maxFetchConcurrency, currentUser }) {
|
|
969
|
+
let limitConcurrency = createClientConcurrencyLimiter(maxFetchConcurrency ?? 25), getConcurrencyLimitedClient = (clientOptions) => limitConcurrency(getClient(clientOptions));
|
|
970
|
+
return lastValueFrom(validateDocumentObservable({
|
|
971
|
+
document,
|
|
972
|
+
getClient: getConcurrencyLimitedClient,
|
|
973
|
+
i18n,
|
|
974
|
+
schema,
|
|
975
|
+
getDocumentExists: getDocumentExists || createBatchedGetDocumentExists(getClient(DEFAULT_VALIDATION_CLIENT_OPTIONS)),
|
|
976
|
+
environment,
|
|
977
|
+
maxCustomValidationConcurrency,
|
|
978
|
+
currentUser
|
|
979
|
+
}));
|
|
980
|
+
}
|
|
981
|
+
const customValidationConcurrencyLimiters = /* @__PURE__ */ new WeakMap();
|
|
982
|
+
/**
|
|
983
|
+
* Validates a document against the given schema, returning an Observable
|
|
984
|
+
* @internal
|
|
985
|
+
*/
|
|
986
|
+
function validateDocumentObservable({ document, getClient, i18n = getFallbackLocaleSource(), schema, getDocumentExists, environment, maxCustomValidationConcurrency, currentUser }) {
|
|
987
|
+
if (typeof document?._type != "string") throw Error("Tried to validate a value without a '_type'");
|
|
988
|
+
let documentType = schema.get(document._type);
|
|
989
|
+
if (!documentType) return environment === "studio" ? (console.warn("Schema type for object type \"%s\" not found, skipping validation", document._type), of([])) : of([{
|
|
990
|
+
code: validationMarkerCodes.documentUnknownType,
|
|
991
|
+
details: { documentType: document._type },
|
|
992
|
+
level: "warning",
|
|
993
|
+
message: `Could not find schema type for type '${document._type}', skipping validation`,
|
|
994
|
+
path: []
|
|
995
|
+
}]);
|
|
996
|
+
let customValidationConcurrencyLimiter = customValidationConcurrencyLimiters.get(schema);
|
|
997
|
+
customValidationConcurrencyLimiter || (customValidationConcurrencyLimiter = new ConcurrencyLimiter(maxCustomValidationConcurrency ?? 5), customValidationConcurrencyLimiters.set(schema, customValidationConcurrencyLimiter));
|
|
998
|
+
let validationOptions = {
|
|
999
|
+
getClient,
|
|
1000
|
+
schema,
|
|
1001
|
+
parent: void 0,
|
|
1002
|
+
value: document,
|
|
1003
|
+
path: [],
|
|
1004
|
+
document,
|
|
1005
|
+
type: documentType,
|
|
1006
|
+
i18n,
|
|
1007
|
+
getDocumentExists,
|
|
1008
|
+
environment,
|
|
1009
|
+
customValidationConcurrencyLimiter,
|
|
1010
|
+
currentUser
|
|
1011
|
+
};
|
|
1012
|
+
return from(i18n.loadNamespaces(["validation"])).pipe(switchMap$1(() => validateItemObservable(validationOptions)), map$1((markers) => markers.map(toDocumentValidationMarker)), catchError((err) => {
|
|
1013
|
+
console.error(err);
|
|
1014
|
+
let message = err?.message || "Unknown error", errorMarker = {
|
|
1015
|
+
code: validationMarkerCodes.validationException,
|
|
1016
|
+
level: "error",
|
|
1017
|
+
message,
|
|
1018
|
+
item: { message },
|
|
1019
|
+
path: []
|
|
1020
|
+
};
|
|
1021
|
+
return of([errorMarker]);
|
|
1022
|
+
}));
|
|
1023
|
+
}
|
|
1024
|
+
function validateItem(opts) {
|
|
1025
|
+
return lastValueFrom(validateItemObservable(opts));
|
|
1026
|
+
}
|
|
1027
|
+
function validateItemObservable({ value, type, path = [], parent, customValidationConcurrencyLimiter, environment, ...restOfContext }) {
|
|
1028
|
+
let ancestorHidden = restOfContext.hidden === !0, resolveHiddenForType = (schemaType, schemaValue, schemaParent, schemaPath, ancestorHiddenValue) => schemaType ? ancestorHiddenValue || resolveConditionalProperty(schemaType.hidden, {
|
|
1029
|
+
...restOfContext,
|
|
1030
|
+
parent: schemaParent,
|
|
1031
|
+
value: schemaValue,
|
|
1032
|
+
path: schemaPath || [],
|
|
1033
|
+
currentUser: restOfContext.currentUser ?? null
|
|
1034
|
+
}) : ancestorHiddenValue, hidden = resolveHiddenForType(type, value, parent, path, ancestorHidden), addUnknownFieldsValidator = (rule) => type?.jsonType === "object" && getTypeChain(type).find((t) => [
|
|
1035
|
+
"object",
|
|
1036
|
+
"document",
|
|
1037
|
+
"file",
|
|
1038
|
+
"image"
|
|
1039
|
+
].includes(t.name)) && environment !== "studio" ? rule.custom(unknownFieldsValidator(type), { bypassConcurrencyLimit: !0 }).warning() : rule, rules = normalizeValidationRules(type, {
|
|
1040
|
+
...restOfContext,
|
|
1041
|
+
hidden,
|
|
1042
|
+
environment,
|
|
1043
|
+
parent,
|
|
1044
|
+
path,
|
|
1045
|
+
type
|
|
1046
|
+
}), selfChecks = rules.map(addUnknownFieldsValidator).map((rule) => defer(() => rule.validate(value, {
|
|
1047
|
+
...restOfContext,
|
|
1048
|
+
environment,
|
|
1049
|
+
hidden,
|
|
1050
|
+
parent,
|
|
1051
|
+
path,
|
|
1052
|
+
type,
|
|
1053
|
+
__internal: { customValidationConcurrencyLimiter }
|
|
1054
|
+
}))), nestedChecks = [], selfIsRequired = rules.some((rule) => rule.isRequired());
|
|
1055
|
+
if (type?.jsonType === "object" && (value || value == null && selfIsRequired)) {
|
|
1056
|
+
let fieldTypes = type.fields.reduce((acc, field) => (acc[field.name] = field.type, acc), {});
|
|
1057
|
+
nestedChecks = nestedChecks.concat(rules.flatMap((rule) => extractFieldRulesFromRule(rule)).flatMap((fieldResults) => Object.entries(fieldResults)).flatMap(([name, validation]) => {
|
|
1058
|
+
let fieldType = fieldTypes[name];
|
|
1059
|
+
return normalizeValidationRules({
|
|
1060
|
+
...fieldType,
|
|
1061
|
+
validation
|
|
1062
|
+
}).map(addUnknownFieldsValidator).map((subRule) => {
|
|
1063
|
+
let nestedValue = isRecord(value) ? value[name] : void 0, nestedHidden = resolveHiddenForType(fieldType, nestedValue, value, path.concat(name), hidden);
|
|
1064
|
+
return defer(() => subRule.validate(nestedValue, {
|
|
1065
|
+
...restOfContext,
|
|
1066
|
+
parent: value,
|
|
1067
|
+
path: path.concat(name),
|
|
1068
|
+
type: fieldType,
|
|
1069
|
+
environment,
|
|
1070
|
+
hidden: nestedHidden,
|
|
1071
|
+
__internal: { customValidationConcurrencyLimiter }
|
|
1072
|
+
}));
|
|
1073
|
+
});
|
|
1074
|
+
})), nestedChecks = nestedChecks.concat(type.fields.map((field) => validateItemObservable({
|
|
1075
|
+
...restOfContext,
|
|
1076
|
+
hidden,
|
|
1077
|
+
parent: value,
|
|
1078
|
+
value: isRecord(value) ? value[field.name] : void 0,
|
|
1079
|
+
path: path.concat(field.name),
|
|
1080
|
+
type: field.type,
|
|
1081
|
+
environment,
|
|
1082
|
+
customValidationConcurrencyLimiter
|
|
1083
|
+
})));
|
|
1084
|
+
}
|
|
1085
|
+
return type?.jsonType === "array" && Array.isArray(value) && (nestedChecks = nestedChecks.concat(value.map((item, index) => validateItemObservable({
|
|
1086
|
+
...restOfContext,
|
|
1087
|
+
hidden,
|
|
1088
|
+
parent: value,
|
|
1089
|
+
value: item,
|
|
1090
|
+
path: path.concat(isKeyedObject(item) ? { _key: item._key } : index),
|
|
1091
|
+
type: resolveTypeForArrayItem(item, type.of),
|
|
1092
|
+
environment,
|
|
1093
|
+
customValidationConcurrencyLimiter
|
|
1094
|
+
})))), defer(() => merge([...selfChecks, ...nestedChecks])).pipe(mergeMap$1((validateNode) => concat(idle(), validateNode), 40), mergeAll(), toArray(), map$1(flatten), map$1((results) => rules.some((rule) => extractFieldRulesFromRule(rule).length > 0) ? deduplicateMarkers(results) : results));
|
|
1095
|
+
}
|
|
1096
|
+
function deduplicateMarkers(markers) {
|
|
1097
|
+
let buckets = /* @__PURE__ */ new Map();
|
|
1098
|
+
return markers.filter((marker) => {
|
|
1099
|
+
let key = JSON.stringify([
|
|
1100
|
+
marker.level,
|
|
1101
|
+
marker.code,
|
|
1102
|
+
marker.message,
|
|
1103
|
+
marker.path
|
|
1104
|
+
]), bucket = buckets.get(key);
|
|
1105
|
+
return bucket ? !bucket.some((existing) => isEqual(existing, marker)) && (bucket.push(marker), !0) : (buckets.set(key, [marker]), !0);
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
function hasValidationMarkerCode(marker) {
|
|
1109
|
+
return typeof marker.code == "string";
|
|
1110
|
+
}
|
|
1111
|
+
function toDocumentValidationMarker(marker) {
|
|
1112
|
+
return hasValidationMarkerCode(marker) ? marker : {
|
|
1113
|
+
...marker,
|
|
1114
|
+
code: validationMarkerCodes.validationFailed
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function idle(timeout) {
|
|
1118
|
+
return new Observable((observer) => {
|
|
1119
|
+
let handle = requestIdleCallback(() => {
|
|
1120
|
+
observer.complete();
|
|
1121
|
+
}, timeout ? { timeout } : void 0);
|
|
1122
|
+
return () => cancelIdleCallback(handle);
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
export { validateDocumentWithWorkspace as a, normalizeValidationRules as c, convertToValidationMarker as d, pathToString as f, validationMarkerCodes as h, validateDocumentObservable as i, Rule$2 as l, validationLocaleStrings as m, validateDocument as n, validateItem as o, getFallbackLocaleSource as p, validateDocumentInternal as r, getTypeChain as s, resolveTypeForArrayItem as t, typeString as u };
|
|
1126
|
+
|
|
1127
|
+
//# sourceMappingURL=validateDocument-Cq33kUmN.js.map
|