@sanity/agent-schema 0.1.0 → 0.2.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/_chunks-dts/types.d.ts +266 -0
- package/dist/_chunks-dts/types.d.ts.map +1 -0
- package/dist/_chunks-es/navigateToField.js +274 -0
- package/dist/_chunks-es/navigateToField.js.map +1 -0
- package/dist/index.d.ts +2 -146
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -264
- package/dist/index.js.map +1 -1
- package/dist/validation/index.d.ts +286 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +1919 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,1919 @@
|
|
|
1
|
+
import { isPrimitiveType, resolveFieldSchema, BUILT_IN_TYPE_FIELDS, resolveFieldSchemaAllUnions } from "../_chunks-es/navigateToField.js";
|
|
2
|
+
function createError(path, code, message, received, expected, fix) {
|
|
3
|
+
return { path, code, message, received, expected, fix };
|
|
4
|
+
}
|
|
5
|
+
function formatValue(value) {
|
|
6
|
+
if (value === void 0) return "(not provided)";
|
|
7
|
+
if (value === null) return "null";
|
|
8
|
+
const type = typeof value;
|
|
9
|
+
if (type === "string") {
|
|
10
|
+
const str = value;
|
|
11
|
+
return str.length > 50 ? `"${str.slice(0, 47)}..." (string)` : `"${str}" (string)`;
|
|
12
|
+
}
|
|
13
|
+
if (type === "number") return `${value} (number)`;
|
|
14
|
+
if (type === "boolean") return `${value} (boolean)`;
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return `[...] (array with ${value.length} items)`;
|
|
17
|
+
if (type === "object") {
|
|
18
|
+
const obj = value;
|
|
19
|
+
return obj._type ? `{_type: "${obj._type}", ...} (object)` : "{...} (object)";
|
|
20
|
+
}
|
|
21
|
+
return String(value);
|
|
22
|
+
}
|
|
23
|
+
function getExpectedJsType(schemaType) {
|
|
24
|
+
switch (schemaType) {
|
|
25
|
+
// String types
|
|
26
|
+
case "string":
|
|
27
|
+
case "text":
|
|
28
|
+
case "url":
|
|
29
|
+
case "email":
|
|
30
|
+
case "date":
|
|
31
|
+
case "datetime":
|
|
32
|
+
return "string";
|
|
33
|
+
// Number
|
|
34
|
+
case "number":
|
|
35
|
+
return "number";
|
|
36
|
+
// Boolean
|
|
37
|
+
case "boolean":
|
|
38
|
+
return "boolean";
|
|
39
|
+
// Array
|
|
40
|
+
case "array":
|
|
41
|
+
return "array";
|
|
42
|
+
// Everything else is an object (document, object, reference, image, file, slug, geopoint, block, custom types)
|
|
43
|
+
default:
|
|
44
|
+
return "object";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getActualJsType(value) {
|
|
48
|
+
return value === null ? "null" : value === void 0 ? "undefined" : Array.isArray(value) ? "array" : typeof value;
|
|
49
|
+
}
|
|
50
|
+
function getUniquenessKey(item) {
|
|
51
|
+
if (typeof item != "object" || item === null)
|
|
52
|
+
return JSON.stringify(item);
|
|
53
|
+
const itemObj = item;
|
|
54
|
+
if (itemObj._type === "reference" && typeof itemObj._ref == "string")
|
|
55
|
+
return itemObj._ref;
|
|
56
|
+
const itemForComparison = Object.fromEntries(
|
|
57
|
+
Object.entries(itemObj).filter(([k]) => k !== "_key")
|
|
58
|
+
);
|
|
59
|
+
return JSON.stringify(itemForComparison);
|
|
60
|
+
}
|
|
61
|
+
function getValueAtPath(obj, path) {
|
|
62
|
+
const segments = parsePath(path);
|
|
63
|
+
let current = obj;
|
|
64
|
+
for (const segment of segments) {
|
|
65
|
+
if (current == null)
|
|
66
|
+
return;
|
|
67
|
+
if (segment.type === "property") {
|
|
68
|
+
if (typeof current != "object" || Array.isArray(current))
|
|
69
|
+
return;
|
|
70
|
+
current = current[segment.value];
|
|
71
|
+
} else if (segment.type === "index") {
|
|
72
|
+
if (!Array.isArray(current))
|
|
73
|
+
return;
|
|
74
|
+
current = current[segment.value];
|
|
75
|
+
} else if (segment.type === "key") {
|
|
76
|
+
if (!Array.isArray(current))
|
|
77
|
+
return;
|
|
78
|
+
current = current.find(
|
|
79
|
+
(item) => typeof item == "object" && item !== null && item._key === segment.value
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return current;
|
|
84
|
+
}
|
|
85
|
+
function parsePath(path) {
|
|
86
|
+
const segments = [], parts = path.split(/\.(?![^[]*\])/);
|
|
87
|
+
for (const part of parts) {
|
|
88
|
+
const match = part.match(/^([^[]*)(?:\[(.+)\])?$/);
|
|
89
|
+
if (!match) continue;
|
|
90
|
+
const [, prop, selector] = match;
|
|
91
|
+
if (prop && segments.push({ type: "property", value: prop }), selector) {
|
|
92
|
+
const keyMatch = selector.match(/^_key\s*==\s*"([^"]+)"$/);
|
|
93
|
+
if (keyMatch)
|
|
94
|
+
segments.push({ type: "key", value: keyMatch[1] });
|
|
95
|
+
else {
|
|
96
|
+
const index = parseInt(selector, 10);
|
|
97
|
+
Number.isNaN(index) || segments.push({ type: "index", value: index });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return segments;
|
|
102
|
+
}
|
|
103
|
+
function validateNoExtraProperties(obj, allowedProperties, path, typeName, errors) {
|
|
104
|
+
for (const key of Object.keys(obj))
|
|
105
|
+
allowedProperties.has(key) || errors.push(
|
|
106
|
+
createError(
|
|
107
|
+
`${path}.${key}`,
|
|
108
|
+
"FIELD_NOT_IN_SCHEMA",
|
|
109
|
+
`Unknown property "${key}" on ${typeName} object`,
|
|
110
|
+
formatValue(obj[key]),
|
|
111
|
+
`Only allowed properties: ${[...allowedProperties].join(", ")}`,
|
|
112
|
+
`Remove the "${key}" property from the ${typeName} object`
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
function formatValidationErrors(errors) {
|
|
117
|
+
if (errors.length === 0)
|
|
118
|
+
return "";
|
|
119
|
+
const header = `Validation failed with ${errors.length} error(s):
|
|
120
|
+
|
|
121
|
+
`, errorList = errors.map((err, i) => [
|
|
122
|
+
`${i + 1}. At path "${err.path}": ${err.message}`,
|
|
123
|
+
` Received: ${err.received}`,
|
|
124
|
+
` Expected: ${err.expected}`,
|
|
125
|
+
` Fix: ${err.fix}`
|
|
126
|
+
].join(`
|
|
127
|
+
`)).join(`
|
|
128
|
+
|
|
129
|
+
`);
|
|
130
|
+
return `${header + errorList}
|
|
131
|
+
|
|
132
|
+
Please correct all errors and try again.`;
|
|
133
|
+
}
|
|
134
|
+
async function validateArray(value, fieldSchema, path, schema, errors, context, validateValue2) {
|
|
135
|
+
if (!Array.isArray(value)) {
|
|
136
|
+
errors.push(
|
|
137
|
+
createError(
|
|
138
|
+
path,
|
|
139
|
+
"TYPE_MISMATCH",
|
|
140
|
+
"Expected array",
|
|
141
|
+
formatValue(value),
|
|
142
|
+
"array",
|
|
143
|
+
"Provide an array value"
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const itemTypes = fieldSchema.of || [];
|
|
149
|
+
if (itemTypes.length === 0)
|
|
150
|
+
return;
|
|
151
|
+
const isPrimitiveArray = itemTypes.every(
|
|
152
|
+
(t) => !t.name && isPrimitiveType(t.type)
|
|
153
|
+
), seenKeys = /* @__PURE__ */ new Set();
|
|
154
|
+
for (let i = 0; i < value.length; i++) {
|
|
155
|
+
const item = value[i], itemPath = `${path}[${i}]`, errorCountBefore = errors.length, itemObj = item, itemType = itemObj?._type, itemKey = itemObj?._key;
|
|
156
|
+
!isPrimitiveArray && typeof item == "object" && item !== null && (itemKey === void 0 ? errors.push(
|
|
157
|
+
createError(
|
|
158
|
+
itemPath,
|
|
159
|
+
"ARRAY_ITEM_INVALID",
|
|
160
|
+
'Object array item missing "_key" property',
|
|
161
|
+
formatValue(item),
|
|
162
|
+
"Object with _key property",
|
|
163
|
+
"Add a unique _key property to the array item"
|
|
164
|
+
)
|
|
165
|
+
) : typeof itemKey == "string" && itemKey !== "" && (seenKeys.has(itemKey) ? errors.push(
|
|
166
|
+
createError(
|
|
167
|
+
`${itemPath}._key`,
|
|
168
|
+
"ARRAY_ITEM_INVALID",
|
|
169
|
+
`duplicate _key "${itemKey}" in array`,
|
|
170
|
+
itemKey,
|
|
171
|
+
"Unique _key value",
|
|
172
|
+
"Ensure all array items have unique _key values"
|
|
173
|
+
)
|
|
174
|
+
) : seenKeys.add(itemKey)));
|
|
175
|
+
let matchingSchema;
|
|
176
|
+
for (const typeSchema of itemTypes) {
|
|
177
|
+
if (typeSchema.name && typeSchema.name === itemType) {
|
|
178
|
+
matchingSchema = typeSchema;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
if (!typeSchema.name && typeSchema.type === itemType) {
|
|
182
|
+
matchingSchema = typeSchema;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
if (typeSchema.type && !itemType && itemTypes.length === 1 && typeof item == "object" && item !== null) {
|
|
186
|
+
matchingSchema = typeSchema;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
if (!itemType && typeSchema.type && typeof typeSchema.type == "string" && isPrimitiveType(typeSchema.type)) {
|
|
190
|
+
matchingSchema = typeSchema;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!matchingSchema && itemType) {
|
|
195
|
+
const globalTypeSchema = schema.find((s) => s.name === itemType);
|
|
196
|
+
globalTypeSchema && (matchingSchema = globalTypeSchema);
|
|
197
|
+
}
|
|
198
|
+
if (!matchingSchema) {
|
|
199
|
+
const allowedTypeNames = itemTypes.map((t) => t.name || t.type).filter(Boolean).join(", ");
|
|
200
|
+
if (!isPrimitiveArray && (typeof item == "string" || typeof item == "number" || typeof item == "boolean" || item === null)) {
|
|
201
|
+
errors.push(
|
|
202
|
+
createError(
|
|
203
|
+
itemPath,
|
|
204
|
+
"TYPE_MISMATCH",
|
|
205
|
+
"Expected object but found primitive in array",
|
|
206
|
+
formatValue(item),
|
|
207
|
+
`object of type: ${allowedTypeNames}`,
|
|
208
|
+
`Provide an object with _type set to one of: ${allowedTypeNames}`
|
|
209
|
+
)
|
|
210
|
+
), tagItemErrors(errors, errorCountBefore, i, path);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const namedTypes = itemTypes.filter((t) => t.name);
|
|
214
|
+
namedTypes.length > 0 && itemType ? errors.push(
|
|
215
|
+
createError(
|
|
216
|
+
itemPath,
|
|
217
|
+
"ARRAY_ITEM_INVALID",
|
|
218
|
+
`Wrong _type "${itemType}" for array item`,
|
|
219
|
+
formatValue(item),
|
|
220
|
+
`_type must be one of: ${namedTypes.map((t) => t.name).join(", ")}`,
|
|
221
|
+
`Use one of the allowed types: ${namedTypes.map((t) => t.name).join(", ")}`
|
|
222
|
+
)
|
|
223
|
+
) : errors.push(
|
|
224
|
+
createError(
|
|
225
|
+
itemPath,
|
|
226
|
+
"ARRAY_ITEM_INVALID",
|
|
227
|
+
itemType ? `Unknown array item type: ${itemType}` : "Invalid array item",
|
|
228
|
+
formatValue(item),
|
|
229
|
+
`One of: ${allowedTypeNames}`,
|
|
230
|
+
itemType ? `Use one of the allowed types: ${allowedTypeNames}` : `Provide a valid array item of type: ${allowedTypeNames}`
|
|
231
|
+
)
|
|
232
|
+
), tagItemErrors(errors, errorCountBefore, i, path);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
await validateValue2(item, matchingSchema, itemPath, errors, context, schema), tagItemErrors(errors, errorCountBefore, i, path);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function tagItemErrors(errors, startIndex, itemIndex, arrayPath) {
|
|
239
|
+
for (let e = startIndex; e < errors.length; e++)
|
|
240
|
+
errors[e].itemIndex = itemIndex, errors[e].arrayPath = arrayPath;
|
|
241
|
+
}
|
|
242
|
+
function validateUniqueItems(newItems, existingArray, pathPrefix, errors) {
|
|
243
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
244
|
+
for (const item of existingArray)
|
|
245
|
+
existingKeys.add(getUniquenessKey(item));
|
|
246
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
247
|
+
for (let i = 0; i < newItems.length; i++) {
|
|
248
|
+
const item = newItems[i], uniquenessKey = getUniquenessKey(item);
|
|
249
|
+
existingKeys.has(uniquenessKey) ? errors.push(
|
|
250
|
+
createError(
|
|
251
|
+
`${pathPrefix}${i}]`,
|
|
252
|
+
"DUPLICATE_ARRAY_ITEM",
|
|
253
|
+
"Item would duplicate an existing array item",
|
|
254
|
+
formatValue(item),
|
|
255
|
+
"Unique array items",
|
|
256
|
+
"Remove the duplicate item or modify it to be unique"
|
|
257
|
+
)
|
|
258
|
+
) : newKeys.has(uniquenessKey) && errors.push(
|
|
259
|
+
createError(
|
|
260
|
+
`${pathPrefix}${i}]`,
|
|
261
|
+
"DUPLICATE_ARRAY_ITEM",
|
|
262
|
+
"Items contain duplicates",
|
|
263
|
+
formatValue(item),
|
|
264
|
+
"Unique array items",
|
|
265
|
+
"Remove duplicate items from the operation"
|
|
266
|
+
)
|
|
267
|
+
), newKeys.add(uniquenessKey);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function pathHasArraySelectors(path) {
|
|
271
|
+
return /\[(_key==|[0-9])/.test(path);
|
|
272
|
+
}
|
|
273
|
+
function resolveFieldSchemaAtPath(path, typeSchema, allSchemas, document) {
|
|
274
|
+
if (document && pathHasArraySelectors(path)) {
|
|
275
|
+
const documentResult = resolveFieldSchemaWithDocument(path, typeSchema, allSchemas, document);
|
|
276
|
+
if (documentResult)
|
|
277
|
+
return { schema: documentResult, resolvedVia: "document" };
|
|
278
|
+
}
|
|
279
|
+
const schemaResult = resolveFieldSchema(path, typeSchema, allSchemas);
|
|
280
|
+
if (schemaResult)
|
|
281
|
+
return { schema: schemaResult, resolvedVia: "schema" };
|
|
282
|
+
if (document) {
|
|
283
|
+
const documentResult = resolveFieldSchemaFromDocument(path, document, allSchemas);
|
|
284
|
+
if (documentResult)
|
|
285
|
+
return { schema: documentResult, resolvedVia: "document" };
|
|
286
|
+
}
|
|
287
|
+
return { schema: null, resolvedVia: null };
|
|
288
|
+
}
|
|
289
|
+
function resolveItemSchema(itemType, allSchemas, parentArraySchema) {
|
|
290
|
+
const directMatch = allSchemas.find((s) => s.name === itemType);
|
|
291
|
+
if (directMatch)
|
|
292
|
+
return directMatch;
|
|
293
|
+
if (parentArraySchema?.of) {
|
|
294
|
+
for (const ofItem of parentArraySchema.of)
|
|
295
|
+
if (ofItem.name === itemType && ofItem.type) {
|
|
296
|
+
const resolvedSchema = allSchemas.find((s) => s.name === ofItem.type);
|
|
297
|
+
if (resolvedSchema)
|
|
298
|
+
return resolvedSchema;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
function parsePathIntoFieldSegments(path) {
|
|
304
|
+
const segments = [], parts = path.split(/\.(?![^[]*\])/);
|
|
305
|
+
for (const part of parts) {
|
|
306
|
+
const match = part.match(/^([^[]+)(\[.+\])?$/);
|
|
307
|
+
match && segments.push({
|
|
308
|
+
field: match[1],
|
|
309
|
+
selector: match[2] || null
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return segments;
|
|
313
|
+
}
|
|
314
|
+
function resolveFieldSchemaWithDocument(path, typeSchema, allSchemas, document) {
|
|
315
|
+
const segments = parsePathIntoFieldSegments(path);
|
|
316
|
+
let currentSchema = typeSchema, currentPath = "";
|
|
317
|
+
for (let i = 0; i < segments.length; i++) {
|
|
318
|
+
const segment = segments[i], isLastSegment = i === segments.length - 1;
|
|
319
|
+
if (currentPath ? currentPath += `.${segment.field}` : currentPath = segment.field, segment.selector && (currentPath += segment.selector), BUILT_IN_TYPE_FIELDS[currentSchema.type]?.[segment.field]) {
|
|
320
|
+
const builtInField = BUILT_IN_TYPE_FIELDS[currentSchema.type][segment.field];
|
|
321
|
+
if (isLastSegment)
|
|
322
|
+
return builtInField;
|
|
323
|
+
if (builtInField.type === "array" && segment.selector && builtInField.of) {
|
|
324
|
+
const itemValue = getValueAtPath(document, currentPath);
|
|
325
|
+
if (itemValue && typeof itemValue == "object" && !Array.isArray(itemValue)) {
|
|
326
|
+
const itemType = itemValue._type;
|
|
327
|
+
if (itemType) {
|
|
328
|
+
if (BUILT_IN_TYPE_FIELDS[itemType]) {
|
|
329
|
+
currentSchema = { name: itemType, type: itemType };
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const itemSchema = resolveItemSchema(itemType, allSchemas, builtInField);
|
|
333
|
+
if (itemSchema) {
|
|
334
|
+
currentSchema = itemSchema;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (builtInField.of.length > 0) {
|
|
340
|
+
const firstItemType = builtInField.of[0].type;
|
|
341
|
+
if (BUILT_IN_TYPE_FIELDS[firstItemType]) {
|
|
342
|
+
currentSchema = { name: firstItemType, type: firstItemType };
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
currentSchema = allSchemas.find((s) => s.name === firstItemType) || builtInField.of[0];
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
currentSchema = builtInField;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
let field;
|
|
353
|
+
if (currentSchema.fields && (field = currentSchema.fields.find((f) => f.name === segment.field)), !field && currentSchema.type === "array" && currentSchema.of) {
|
|
354
|
+
const parentPath = segments.slice(0, i).map((s) => s.field + (s.selector || "")).join("."), parentValue = parentPath ? getValueAtPath(document, parentPath) : document;
|
|
355
|
+
if (parentValue && typeof parentValue == "object" && !Array.isArray(parentValue)) {
|
|
356
|
+
const itemType = parentValue._type;
|
|
357
|
+
if (itemType) {
|
|
358
|
+
const itemSchema = resolveItemSchema(itemType, allSchemas, currentSchema);
|
|
359
|
+
itemSchema?.fields && (field = itemSchema.fields.find((f) => f.name === segment.field));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (!field) {
|
|
364
|
+
const itemValue = getValueAtPath(document, currentPath);
|
|
365
|
+
if (itemValue && typeof itemValue == "object" && !Array.isArray(itemValue)) {
|
|
366
|
+
const itemType = itemValue._type;
|
|
367
|
+
if (itemType) {
|
|
368
|
+
const itemSchema = resolveItemSchema(itemType, allSchemas);
|
|
369
|
+
if (itemSchema) {
|
|
370
|
+
if (isLastSegment)
|
|
371
|
+
return itemSchema;
|
|
372
|
+
currentSchema = itemSchema;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
if (isLastSegment)
|
|
380
|
+
return field;
|
|
381
|
+
let resolvedFieldForNav = field;
|
|
382
|
+
if (field.type !== "array" && field.type) {
|
|
383
|
+
const referencedArray = allSchemas.find((s) => s.name === field.type && s.type === "array");
|
|
384
|
+
referencedArray && (resolvedFieldForNav = referencedArray);
|
|
385
|
+
}
|
|
386
|
+
if (resolvedFieldForNav.type === "array" && segment.selector) {
|
|
387
|
+
const itemValue = getValueAtPath(document, currentPath);
|
|
388
|
+
if (itemValue && typeof itemValue == "object" && !Array.isArray(itemValue)) {
|
|
389
|
+
const itemType = itemValue._type;
|
|
390
|
+
if (itemType) {
|
|
391
|
+
const itemSchema = resolveItemSchema(itemType, allSchemas, resolvedFieldForNav);
|
|
392
|
+
if (itemSchema) {
|
|
393
|
+
currentSchema = itemSchema;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (resolvedFieldForNav.of && resolvedFieldForNav.of.length > 0) {
|
|
399
|
+
const firstItemType = resolvedFieldForNav.of[0].type;
|
|
400
|
+
currentSchema = allSchemas.find((s) => s.name === firstItemType) || resolvedFieldForNav.of[0];
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (field.type && field.type !== "array") {
|
|
405
|
+
const referencedSchema = allSchemas.find((s) => s.name === field.type);
|
|
406
|
+
if (referencedSchema) {
|
|
407
|
+
currentSchema = referencedSchema;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
currentSchema = field;
|
|
412
|
+
}
|
|
413
|
+
return currentSchema;
|
|
414
|
+
}
|
|
415
|
+
function resolveFieldSchemaFromDocument(path, document, allSchemas) {
|
|
416
|
+
const lastDotIndex = path.lastIndexOf(".");
|
|
417
|
+
if (lastDotIndex === -1)
|
|
418
|
+
return null;
|
|
419
|
+
const parentPath = path.substring(0, lastDotIndex), fieldName = path.substring(lastDotIndex + 1), parentValue = getValueAtPath(document, parentPath);
|
|
420
|
+
if (parentValue == null || typeof parentValue != "object" || Array.isArray(parentValue))
|
|
421
|
+
return null;
|
|
422
|
+
const parentType = parentValue._type;
|
|
423
|
+
if (!parentType)
|
|
424
|
+
return null;
|
|
425
|
+
const typeSchema = resolveItemSchema(parentType, allSchemas);
|
|
426
|
+
if (!typeSchema)
|
|
427
|
+
return null;
|
|
428
|
+
if (typeSchema.fields) {
|
|
429
|
+
const field = typeSchema.fields.find((f) => f.name === fieldName);
|
|
430
|
+
if (field)
|
|
431
|
+
return field;
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
function validateDocumentLanguageField(value, languageFieldName, availableLanguages, path, errors) {
|
|
436
|
+
if (!(!availableLanguages || availableLanguages.length === 0) && value != null) {
|
|
437
|
+
if (typeof value != "string") {
|
|
438
|
+
errors.push(
|
|
439
|
+
createError(
|
|
440
|
+
path,
|
|
441
|
+
"TYPE_MISMATCH",
|
|
442
|
+
`Language field "${languageFieldName}" must be a string`,
|
|
443
|
+
formatValue(value),
|
|
444
|
+
"string (language code)",
|
|
445
|
+
`Provide a language code like "${availableLanguages[0]}"`
|
|
446
|
+
)
|
|
447
|
+
);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
availableLanguages.includes(value) || errors.push(
|
|
451
|
+
createError(
|
|
452
|
+
path,
|
|
453
|
+
"INVALID_LANGUAGE",
|
|
454
|
+
`Invalid language code for "${languageFieldName}"`,
|
|
455
|
+
formatValue(value),
|
|
456
|
+
`One of: ${availableLanguages.map((l) => `"${l}"`).join(", ")}`,
|
|
457
|
+
`Use a valid language code: ${availableLanguages.join(", ")}`
|
|
458
|
+
)
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function validateFieldLevelLanguageKey(key, allowedLanguages, path, errors) {
|
|
463
|
+
if (!allowedLanguages || allowedLanguages.length === 0)
|
|
464
|
+
return;
|
|
465
|
+
const languageIds = allowedLanguages.map((lang) => lang.id);
|
|
466
|
+
if (!languageIds.includes(key)) {
|
|
467
|
+
const languageList = allowedLanguages.map((lang) => `"${lang.id}"${lang.title ? ` (${lang.title})` : ""}`).join(", ");
|
|
468
|
+
errors.push(
|
|
469
|
+
createError(
|
|
470
|
+
path,
|
|
471
|
+
"INVALID_LANGUAGE",
|
|
472
|
+
`Invalid language key "${key}" in internationalized array`,
|
|
473
|
+
`"${key}"`,
|
|
474
|
+
`One of: ${languageList}`,
|
|
475
|
+
`Use a valid language key: ${languageIds.join(", ")}`
|
|
476
|
+
)
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function validateFieldLevelLanguageField(languageValue, allowedLanguages, path, errors) {
|
|
481
|
+
if (!allowedLanguages || allowedLanguages.length === 0)
|
|
482
|
+
return;
|
|
483
|
+
if (typeof languageValue != "string" || languageValue === "") {
|
|
484
|
+
const languageIds2 = allowedLanguages.map((lang) => lang.id);
|
|
485
|
+
errors.push(
|
|
486
|
+
createError(
|
|
487
|
+
path,
|
|
488
|
+
"INVALID_LANGUAGE",
|
|
489
|
+
'Missing or invalid "language" field in internationalized array item',
|
|
490
|
+
formatValue(languageValue),
|
|
491
|
+
`One of: ${allowedLanguages.map((l) => `"${l.id}"${l.title ? ` (${l.title})` : ""}`).join(", ")}`,
|
|
492
|
+
`Set the "language" field to a valid language code: ${languageIds2.join(", ")}`
|
|
493
|
+
)
|
|
494
|
+
);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const languageIds = allowedLanguages.map((lang) => lang.id);
|
|
498
|
+
if (!languageIds.includes(languageValue)) {
|
|
499
|
+
const languageList = allowedLanguages.map((lang) => `"${lang.id}"${lang.title ? ` (${lang.title})` : ""}`).join(", ");
|
|
500
|
+
errors.push(
|
|
501
|
+
createError(
|
|
502
|
+
path,
|
|
503
|
+
"INVALID_LANGUAGE",
|
|
504
|
+
`Invalid language "${languageValue}" in internationalized array item`,
|
|
505
|
+
`"${languageValue}"`,
|
|
506
|
+
`One of: ${languageList}`,
|
|
507
|
+
`Use a valid language: ${languageIds.join(", ")}`
|
|
508
|
+
)
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function isV5InternationalizedArray(fieldSchema, allSchemas) {
|
|
513
|
+
const itemType = (allSchemas.find((s) => s.name === fieldSchema.name || s.name === fieldSchema.type) ?? fieldSchema).of?.[0];
|
|
514
|
+
if (!itemType) return !1;
|
|
515
|
+
const valueTypeName = itemType.name || itemType.type;
|
|
516
|
+
return valueTypeName ? !!(allSchemas.find((s) => s.name === valueTypeName)?.fields || itemType.fields)?.some((f) => f.name === "language") : !1;
|
|
517
|
+
}
|
|
518
|
+
function isInternationalizedArrayField(fieldSchema) {
|
|
519
|
+
return !!fieldSchema.type?.startsWith("internationalizedArray") || !!fieldSchema.name?.startsWith("internationalizedArray");
|
|
520
|
+
}
|
|
521
|
+
function resolveLanguages(fieldSchema, allSchemas) {
|
|
522
|
+
let languages = fieldSchema.options?.languages;
|
|
523
|
+
return languages?.length || (languages = allSchemas.find((s) => s.name === fieldSchema.type)?.options?.languages), languages?.length ? languages : void 0;
|
|
524
|
+
}
|
|
525
|
+
function validateInternationalizedArraySet(path, value, fieldSchema, typeSchema, allSchemas, errors) {
|
|
526
|
+
if (isInternationalizedArrayField(fieldSchema) && Array.isArray(value)) {
|
|
527
|
+
validateInternationalizedArrayItems(value, fieldSchema, allSchemas, path, errors);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (path.endsWith("._key") && typeof value == "string") {
|
|
531
|
+
const arrayPath = path.replace(/\[[^\]]+\]\._key$/, ""), arrayFieldSchema = resolveFieldSchema(arrayPath, typeSchema, allSchemas);
|
|
532
|
+
if (!arrayFieldSchema || !isInternationalizedArrayField(arrayFieldSchema) || isV5InternationalizedArray(arrayFieldSchema, allSchemas))
|
|
533
|
+
return;
|
|
534
|
+
const languages = resolveLanguages(arrayFieldSchema, allSchemas);
|
|
535
|
+
if (!languages)
|
|
536
|
+
return;
|
|
537
|
+
validateFieldLevelLanguageKey(value, languages, path, errors);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (path.endsWith(".language") && typeof value == "string") {
|
|
541
|
+
const arrayPath = path.replace(/\[[^\]]+\]\.language$/, ""), arrayFieldSchema = resolveFieldSchema(arrayPath, typeSchema, allSchemas);
|
|
542
|
+
if (!arrayFieldSchema || !isInternationalizedArrayField(arrayFieldSchema) || !isV5InternationalizedArray(arrayFieldSchema, allSchemas))
|
|
543
|
+
return;
|
|
544
|
+
const languages = resolveLanguages(arrayFieldSchema, allSchemas);
|
|
545
|
+
if (!languages)
|
|
546
|
+
return;
|
|
547
|
+
validateFieldLevelLanguageField(value, languages, path, errors);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function validateInternationalizedArrayItems(items, fieldSchema, allSchemas, path, errors) {
|
|
551
|
+
const languages = resolveLanguages(fieldSchema, allSchemas);
|
|
552
|
+
if (!languages)
|
|
553
|
+
return;
|
|
554
|
+
const v5 = isV5InternationalizedArray(fieldSchema, allSchemas);
|
|
555
|
+
for (let i = 0; i < items.length; i++) {
|
|
556
|
+
const item = items[i];
|
|
557
|
+
if (typeof item == "object" && item !== null) {
|
|
558
|
+
const itemObj = item;
|
|
559
|
+
v5 ? validateFieldLevelLanguageField(
|
|
560
|
+
itemObj.language,
|
|
561
|
+
languages,
|
|
562
|
+
`${path}[${i}].language`,
|
|
563
|
+
errors
|
|
564
|
+
) : itemObj._key && typeof itemObj._key == "string" && validateFieldLevelLanguageKey(itemObj._key, languages, `${path}[${i}]._key`, errors);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async function validateObject(value, fieldSchema, path, schema, errors, context, validateValue2) {
|
|
569
|
+
if (typeof value != "object" || value === null || Array.isArray(value)) {
|
|
570
|
+
errors.push(
|
|
571
|
+
createError(
|
|
572
|
+
path,
|
|
573
|
+
"TYPE_MISMATCH",
|
|
574
|
+
"Expected object",
|
|
575
|
+
formatValue(value),
|
|
576
|
+
"object",
|
|
577
|
+
"Provide an object value"
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const objValue = value;
|
|
583
|
+
let fields = fieldSchema.fields || [];
|
|
584
|
+
if (fields.length === 0 && fieldSchema.type) {
|
|
585
|
+
const referencedType = schema.find((s) => s.name === fieldSchema.type);
|
|
586
|
+
referencedType?.fields && (fields = referencedType.fields);
|
|
587
|
+
}
|
|
588
|
+
if (fields.length === 0)
|
|
589
|
+
return;
|
|
590
|
+
const validFieldNames = new Set(fields.map((f) => f.name));
|
|
591
|
+
(fieldSchema.type === "block" || fieldSchema.name === "block") && (validFieldNames.add("children"), validFieldNames.add("markDefs"), validFieldNames.add("style"), validFieldNames.add("listItem"), validFieldNames.add("level"));
|
|
592
|
+
for (const key of Object.keys(objValue))
|
|
593
|
+
if (!key.startsWith("_") && !validFieldNames.has(key)) {
|
|
594
|
+
const validFieldsList = fields.map((f) => f.name).join(", ");
|
|
595
|
+
errors.push(
|
|
596
|
+
createError(
|
|
597
|
+
path ? `${path}.${key}` : key,
|
|
598
|
+
"FIELD_NOT_IN_SCHEMA",
|
|
599
|
+
"Field does not exist in schema",
|
|
600
|
+
formatValue(objValue[key]),
|
|
601
|
+
`Field must be one of: ${validFieldsList}`,
|
|
602
|
+
"Remove this field or check the schema for valid field names"
|
|
603
|
+
)
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
for (const field of fields) {
|
|
607
|
+
const fieldValue = objValue[field.name], fieldPath = path ? `${path}.${field.name}` : field.name;
|
|
608
|
+
fieldValue !== void 0 && await validateValue2(fieldValue, field, fieldPath, errors, context, schema);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function getValidImageMetadataFields(imagePath, typeSchema, schema) {
|
|
612
|
+
const imageFieldSchema = resolveFieldSchema(imagePath, typeSchema, schema);
|
|
613
|
+
if (!imageFieldSchema)
|
|
614
|
+
return null;
|
|
615
|
+
const validFieldNames = /* @__PURE__ */ new Set();
|
|
616
|
+
if (validFieldNames.add("hotspot"), validFieldNames.add("crop"), imageFieldSchema.fields)
|
|
617
|
+
for (const field of imageFieldSchema.fields)
|
|
618
|
+
validFieldNames.add(field.name);
|
|
619
|
+
return validFieldNames;
|
|
620
|
+
}
|
|
621
|
+
function getFieldSchemaAtPath(path, documentType, schema, existingDocument) {
|
|
622
|
+
const errors = [];
|
|
623
|
+
if (!path)
|
|
624
|
+
return errors.push(
|
|
625
|
+
createError(
|
|
626
|
+
"",
|
|
627
|
+
"INVALID_PATH",
|
|
628
|
+
"Empty path provided",
|
|
629
|
+
"(empty)",
|
|
630
|
+
"Non-empty path string",
|
|
631
|
+
"Provide a valid path to a field"
|
|
632
|
+
)
|
|
633
|
+
), { schemas: null, errors };
|
|
634
|
+
const typeSchema = schema.find((s) => s.name === documentType);
|
|
635
|
+
if (!typeSchema)
|
|
636
|
+
return errors.push(
|
|
637
|
+
createError(
|
|
638
|
+
path,
|
|
639
|
+
"FIELD_NOT_IN_SCHEMA",
|
|
640
|
+
`Document type "${documentType}" not found in schema`,
|
|
641
|
+
documentType,
|
|
642
|
+
"Valid document type",
|
|
643
|
+
"Check the document type name"
|
|
644
|
+
)
|
|
645
|
+
), { schemas: null, errors };
|
|
646
|
+
let fieldSchemas = resolveFieldSchemaAllUnions(path, typeSchema, schema);
|
|
647
|
+
if ((!fieldSchemas || fieldSchemas.length === 0) && existingDocument) {
|
|
648
|
+
const documentResolvedSchema = resolveFieldSchemaFromDocument(path, existingDocument, schema);
|
|
649
|
+
documentResolvedSchema && (fieldSchemas = [documentResolvedSchema]);
|
|
650
|
+
}
|
|
651
|
+
if (!fieldSchemas || fieldSchemas.length === 0)
|
|
652
|
+
return errors.push(
|
|
653
|
+
createError(
|
|
654
|
+
path,
|
|
655
|
+
"FIELD_NOT_IN_SCHEMA",
|
|
656
|
+
`Field at path "${path}" does not exist in schema`,
|
|
657
|
+
path,
|
|
658
|
+
"Valid field path",
|
|
659
|
+
"Check the path matches a field in the schema"
|
|
660
|
+
)
|
|
661
|
+
), { schemas: null, errors };
|
|
662
|
+
if (existingDocument && (path.includes(".") || path.includes("[")) && getValueAtPath(existingDocument, path) === void 0) {
|
|
663
|
+
const lastDotIndex = path.lastIndexOf(".");
|
|
664
|
+
if (lastDotIndex > 0) {
|
|
665
|
+
const parentPath = path.substring(0, lastDotIndex);
|
|
666
|
+
if (getValueAtPath(existingDocument, parentPath) === void 0)
|
|
667
|
+
return path.includes("[") ? errors.push(
|
|
668
|
+
createError(
|
|
669
|
+
path,
|
|
670
|
+
"INVALID_PATH",
|
|
671
|
+
"Array item does not exist in document",
|
|
672
|
+
path,
|
|
673
|
+
"Existing array item",
|
|
674
|
+
"Ensure the array item exists before setting fields on it"
|
|
675
|
+
)
|
|
676
|
+
) : errors.push(
|
|
677
|
+
createError(
|
|
678
|
+
path,
|
|
679
|
+
"INVALID_PATH",
|
|
680
|
+
`Parent object at "${parentPath}" does not exist in document`,
|
|
681
|
+
path,
|
|
682
|
+
"Existing parent object",
|
|
683
|
+
"Ensure the parent object exists before setting nested fields"
|
|
684
|
+
)
|
|
685
|
+
), { schemas: fieldSchemas, errors };
|
|
686
|
+
} else if (path.includes("["))
|
|
687
|
+
return errors.push(
|
|
688
|
+
createError(
|
|
689
|
+
path,
|
|
690
|
+
"INVALID_PATH",
|
|
691
|
+
"Array item does not exist in document",
|
|
692
|
+
path,
|
|
693
|
+
"Existing array item",
|
|
694
|
+
"Ensure the array item exists before accessing it"
|
|
695
|
+
)
|
|
696
|
+
), { schemas: fieldSchemas, errors };
|
|
697
|
+
}
|
|
698
|
+
return { schemas: fieldSchemas, errors: [] };
|
|
699
|
+
}
|
|
700
|
+
function validateImagePath(path, documentType, schema, existingDocument) {
|
|
701
|
+
const { schemas, errors } = getFieldSchemaAtPath(path, documentType, schema, existingDocument);
|
|
702
|
+
if (errors.length > 0)
|
|
703
|
+
return { valid: !1, errors, warnings: [] };
|
|
704
|
+
if (!schemas || schemas.length === 0)
|
|
705
|
+
return {
|
|
706
|
+
valid: !1,
|
|
707
|
+
warnings: [],
|
|
708
|
+
errors: [
|
|
709
|
+
createError(
|
|
710
|
+
path,
|
|
711
|
+
"FIELD_NOT_IN_SCHEMA",
|
|
712
|
+
`Field at path "${path}" does not exist in schema`,
|
|
713
|
+
path,
|
|
714
|
+
"Valid image field path",
|
|
715
|
+
"Check the path matches an image field in the schema"
|
|
716
|
+
)
|
|
717
|
+
]
|
|
718
|
+
};
|
|
719
|
+
if (!schemas.some((s) => s.type === "image")) {
|
|
720
|
+
const types = schemas.map((s) => s.type || "unknown").join(", ");
|
|
721
|
+
return {
|
|
722
|
+
valid: !1,
|
|
723
|
+
warnings: [],
|
|
724
|
+
errors: [
|
|
725
|
+
createError(
|
|
726
|
+
path,
|
|
727
|
+
"TYPE_MISMATCH",
|
|
728
|
+
`Field at path "${path}" is not an image field`,
|
|
729
|
+
`type: ${types}`,
|
|
730
|
+
"type: image",
|
|
731
|
+
"Provide a path to an image field in the schema"
|
|
732
|
+
)
|
|
733
|
+
]
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
return { valid: !0, errors: [], warnings: [] };
|
|
737
|
+
}
|
|
738
|
+
function validateImageMetadata(metadata, imagePath, typeSchema, schema) {
|
|
739
|
+
if (!metadata || typeof metadata != "object" || Object.keys(metadata).length === 0)
|
|
740
|
+
return;
|
|
741
|
+
const simpleValidFields = getValidImageMetadataFields(imagePath, typeSchema, schema);
|
|
742
|
+
let validFieldNames;
|
|
743
|
+
if (simpleValidFields && simpleValidFields.size > 0)
|
|
744
|
+
validFieldNames = simpleValidFields;
|
|
745
|
+
else {
|
|
746
|
+
const imageFieldSchemas = resolveFieldSchemaAllUnions(imagePath, typeSchema, schema);
|
|
747
|
+
if (!imageFieldSchemas || imageFieldSchemas.length === 0)
|
|
748
|
+
return;
|
|
749
|
+
validFieldNames = /* @__PURE__ */ new Set();
|
|
750
|
+
for (const fieldSchema of imageFieldSchemas)
|
|
751
|
+
if (fieldSchema?.fields && Array.isArray(fieldSchema.fields))
|
|
752
|
+
for (const field of fieldSchema.fields)
|
|
753
|
+
validFieldNames.add(field.name);
|
|
754
|
+
}
|
|
755
|
+
if (validFieldNames.size === 0)
|
|
756
|
+
return;
|
|
757
|
+
const cleanedMetadata = {};
|
|
758
|
+
for (const [key, value] of Object.entries(metadata))
|
|
759
|
+
validFieldNames.has(key) && typeof value == "string" && (cleanedMetadata[key] = value);
|
|
760
|
+
return Object.keys(cleanedMetadata).length > 0 ? cleanedMetadata : void 0;
|
|
761
|
+
}
|
|
762
|
+
async function validateBlock(value, fieldSchema, path, schema, errors, context, validateValue2) {
|
|
763
|
+
if (typeof value != "object" || value === null || Array.isArray(value)) {
|
|
764
|
+
errors.push(
|
|
765
|
+
createError(
|
|
766
|
+
path,
|
|
767
|
+
"TYPE_MISMATCH",
|
|
768
|
+
"Invalid block value",
|
|
769
|
+
formatValue(value),
|
|
770
|
+
'Object with { _type: "block", children: [...], markDefs?: [...] }',
|
|
771
|
+
"Provide a valid Portable Text block object"
|
|
772
|
+
)
|
|
773
|
+
);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const blockObj = value;
|
|
777
|
+
if (blockObj.children === void 0)
|
|
778
|
+
errors.push(
|
|
779
|
+
createError(
|
|
780
|
+
`${path}.children`,
|
|
781
|
+
"REQUIRED_FIELD_MISSING",
|
|
782
|
+
'Block is missing required "children" field',
|
|
783
|
+
"undefined",
|
|
784
|
+
"Array of spans and inline objects",
|
|
785
|
+
'Provide a children array, e.g., children: [{ _type: "span", text: "Hello", _key: "s1", marks: [] }]'
|
|
786
|
+
)
|
|
787
|
+
);
|
|
788
|
+
else if (!Array.isArray(blockObj.children))
|
|
789
|
+
errors.push(
|
|
790
|
+
createError(
|
|
791
|
+
`${path}.children`,
|
|
792
|
+
"TYPE_MISMATCH",
|
|
793
|
+
"Block children must be an array",
|
|
794
|
+
formatValue(blockObj.children),
|
|
795
|
+
"Array of spans and inline objects",
|
|
796
|
+
'Provide an array of children, e.g., [{ _type: "span", text: "Hello", _key: "s1" }]'
|
|
797
|
+
)
|
|
798
|
+
);
|
|
799
|
+
else {
|
|
800
|
+
const markDefKeys = /* @__PURE__ */ new Set();
|
|
801
|
+
if (Array.isArray(blockObj.markDefs)) {
|
|
802
|
+
for (const markDef of blockObj.markDefs)
|
|
803
|
+
if (typeof markDef == "object" && markDef !== null) {
|
|
804
|
+
const key = markDef._key;
|
|
805
|
+
typeof key == "string" && markDefKeys.add(key);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const schemaDecorators = fieldSchema.marks?.decorators?.map((d) => d.value) || [], defaultDecorators = ["strong", "em", "code", "underline", "strike-through"], allowedDecorators = new Set(
|
|
809
|
+
schemaDecorators.length > 0 ? schemaDecorators : defaultDecorators
|
|
810
|
+
);
|
|
811
|
+
await validateBlockChildren(
|
|
812
|
+
blockObj.children,
|
|
813
|
+
fieldSchema,
|
|
814
|
+
`${path}.children`,
|
|
815
|
+
schema,
|
|
816
|
+
errors,
|
|
817
|
+
context,
|
|
818
|
+
markDefKeys,
|
|
819
|
+
allowedDecorators,
|
|
820
|
+
validateValue2
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
blockObj.markDefs !== void 0 && (Array.isArray(blockObj.markDefs) ? await validateBlockMarkDefs(
|
|
824
|
+
blockObj.markDefs,
|
|
825
|
+
fieldSchema,
|
|
826
|
+
`${path}.markDefs`,
|
|
827
|
+
schema,
|
|
828
|
+
errors,
|
|
829
|
+
context,
|
|
830
|
+
validateValue2
|
|
831
|
+
) : errors.push(
|
|
832
|
+
createError(
|
|
833
|
+
`${path}.markDefs`,
|
|
834
|
+
"TYPE_MISMATCH",
|
|
835
|
+
"Block markDefs must be an array",
|
|
836
|
+
formatValue(blockObj.markDefs),
|
|
837
|
+
"Array of annotation objects",
|
|
838
|
+
'Provide an array of markDefs, e.g., [{ _type: "link", _key: "l1", href: "..." }]'
|
|
839
|
+
)
|
|
840
|
+
));
|
|
841
|
+
const schemaStyles = fieldSchema.styles?.map((s) => s.value) || [], defaultStyles = ["normal", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote"], allowedStyles = new Set(schemaStyles.length > 0 ? schemaStyles : defaultStyles);
|
|
842
|
+
if (blockObj.style !== void 0) {
|
|
843
|
+
if (typeof blockObj.style != "string")
|
|
844
|
+
errors.push(
|
|
845
|
+
createError(
|
|
846
|
+
`${path}.style`,
|
|
847
|
+
"TYPE_MISMATCH",
|
|
848
|
+
"Block style must be a string",
|
|
849
|
+
formatValue(blockObj.style),
|
|
850
|
+
'string (e.g., "normal", "h1", "blockquote")',
|
|
851
|
+
"Provide a valid style string"
|
|
852
|
+
)
|
|
853
|
+
);
|
|
854
|
+
else if (!allowedStyles.has(blockObj.style)) {
|
|
855
|
+
const styleList = [...allowedStyles].join(", ");
|
|
856
|
+
errors.push(
|
|
857
|
+
createError(
|
|
858
|
+
`${path}.style`,
|
|
859
|
+
"INVALID_ENUM_VALUE",
|
|
860
|
+
`Block style "${blockObj.style}" is not allowed`,
|
|
861
|
+
blockObj.style,
|
|
862
|
+
`One of: ${styleList}`,
|
|
863
|
+
`Use one of the allowed styles: ${styleList}`
|
|
864
|
+
)
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const schemaLists = fieldSchema.lists?.map((l) => l.value) || [], defaultLists = ["bullet", "number"], allowedLists = new Set(schemaLists.length > 0 ? schemaLists : defaultLists);
|
|
869
|
+
if (blockObj.listItem !== void 0) {
|
|
870
|
+
if (typeof blockObj.listItem != "string")
|
|
871
|
+
errors.push(
|
|
872
|
+
createError(
|
|
873
|
+
`${path}.listItem`,
|
|
874
|
+
"TYPE_MISMATCH",
|
|
875
|
+
"Block listItem must be a string",
|
|
876
|
+
formatValue(blockObj.listItem),
|
|
877
|
+
'string (e.g., "bullet", "number")',
|
|
878
|
+
"Provide a valid listItem string"
|
|
879
|
+
)
|
|
880
|
+
);
|
|
881
|
+
else if (!allowedLists.has(blockObj.listItem)) {
|
|
882
|
+
const listItemList = [...allowedLists].join(", ");
|
|
883
|
+
errors.push(
|
|
884
|
+
createError(
|
|
885
|
+
`${path}.listItem`,
|
|
886
|
+
"INVALID_ENUM_VALUE",
|
|
887
|
+
`Block listItem "${blockObj.listItem}" is not allowed`,
|
|
888
|
+
blockObj.listItem,
|
|
889
|
+
`One of: ${listItemList}`,
|
|
890
|
+
`Use one of the allowed list types: ${listItemList}`
|
|
891
|
+
)
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
blockObj.level !== void 0 && typeof blockObj.level != "number" && errors.push(
|
|
896
|
+
createError(
|
|
897
|
+
`${path}.level`,
|
|
898
|
+
"TYPE_MISMATCH",
|
|
899
|
+
"Block level must be a number",
|
|
900
|
+
formatValue(blockObj.level),
|
|
901
|
+
"number (list nesting level, 1-based)",
|
|
902
|
+
"Provide a valid level number"
|
|
903
|
+
)
|
|
904
|
+
), blockObj.listItem !== void 0 && blockObj.level === void 0 && errors.push(
|
|
905
|
+
createError(
|
|
906
|
+
`${path}.level`,
|
|
907
|
+
"REQUIRED_FIELD_MISSING",
|
|
908
|
+
"Block with listItem must have a level",
|
|
909
|
+
"undefined",
|
|
910
|
+
"number (list nesting level, 1-based)",
|
|
911
|
+
"Add a level property (e.g., level: 1) when using listItem"
|
|
912
|
+
)
|
|
913
|
+
), blockObj.level !== void 0 && blockObj.listItem === void 0 && errors.push(
|
|
914
|
+
createError(
|
|
915
|
+
`${path}.listItem`,
|
|
916
|
+
"REQUIRED_FIELD_MISSING",
|
|
917
|
+
"Block with level must have a listItem",
|
|
918
|
+
"undefined",
|
|
919
|
+
'string (e.g., "bullet", "number")',
|
|
920
|
+
'Add a listItem property (e.g., listItem: "bullet") when using level'
|
|
921
|
+
)
|
|
922
|
+
);
|
|
923
|
+
const blockFields = fieldSchema.fields || [];
|
|
924
|
+
if (blockFields.length > 0) {
|
|
925
|
+
const validFieldNames = new Set(blockFields.map((f) => f.name));
|
|
926
|
+
validFieldNames.add("children"), validFieldNames.add("markDefs"), validFieldNames.add("style"), validFieldNames.add("listItem"), validFieldNames.add("level");
|
|
927
|
+
for (const key of Object.keys(blockObj))
|
|
928
|
+
if (!key.startsWith("_") && !validFieldNames.has(key)) {
|
|
929
|
+
const validFieldsList = [...validFieldNames].join(", ");
|
|
930
|
+
errors.push(
|
|
931
|
+
createError(
|
|
932
|
+
`${path}.${key}`,
|
|
933
|
+
"FIELD_NOT_IN_SCHEMA",
|
|
934
|
+
"Field does not exist in block schema",
|
|
935
|
+
formatValue(blockObj[key]),
|
|
936
|
+
`Field must be one of: ${validFieldsList}`,
|
|
937
|
+
"Remove this field or check the schema for valid field names"
|
|
938
|
+
)
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
for (const field of blockFields) {
|
|
942
|
+
if (["style", "listItem", "level", "children", "markDefs"].includes(field.name))
|
|
943
|
+
continue;
|
|
944
|
+
const fieldValue = blockObj[field.name];
|
|
945
|
+
if (fieldValue !== void 0) {
|
|
946
|
+
const fieldPath = `${path}.${field.name}`;
|
|
947
|
+
await validateValue2(fieldValue, field, fieldPath, errors, context, schema);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
async function validateBlockChildren(children, blockSchema, path, schema, errors, context, markDefKeys, allowedDecorators, validateValue2) {
|
|
953
|
+
const inlineTypes = blockSchema.of || [], seenKeys = /* @__PURE__ */ new Set();
|
|
954
|
+
for (let i = 0; i < children.length; i++) {
|
|
955
|
+
const child = children[i], childPath = `${path}[${i}]`;
|
|
956
|
+
if (typeof child != "object" || child === null) {
|
|
957
|
+
errors.push(
|
|
958
|
+
createError(
|
|
959
|
+
childPath,
|
|
960
|
+
"TYPE_MISMATCH",
|
|
961
|
+
"Block child must be an object",
|
|
962
|
+
formatValue(child),
|
|
963
|
+
"Object (span or inline object)",
|
|
964
|
+
"Provide a span or inline object"
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
const childObj = child, childType = childObj._type, childKey = childObj._key;
|
|
970
|
+
if (childKey === void 0 ? errors.push(
|
|
971
|
+
createError(
|
|
972
|
+
childPath,
|
|
973
|
+
"ARRAY_ITEM_INVALID",
|
|
974
|
+
'Block child missing "_key" property',
|
|
975
|
+
formatValue(child),
|
|
976
|
+
"Object with _key property",
|
|
977
|
+
"Add a unique _key property to the child"
|
|
978
|
+
)
|
|
979
|
+
) : typeof childKey == "string" && childKey !== "" && (seenKeys.has(childKey) ? errors.push(
|
|
980
|
+
createError(
|
|
981
|
+
`${childPath}._key`,
|
|
982
|
+
"ARRAY_ITEM_INVALID",
|
|
983
|
+
`duplicate _key "${childKey}" in children array`,
|
|
984
|
+
childKey,
|
|
985
|
+
"Unique _key value",
|
|
986
|
+
"Ensure all children have unique _key values"
|
|
987
|
+
)
|
|
988
|
+
) : seenKeys.add(childKey)), childType === "span") {
|
|
989
|
+
validateSpan(childObj, childPath, errors, markDefKeys, allowedDecorators);
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
let matchingSchema;
|
|
993
|
+
for (const inlineSchema of inlineTypes)
|
|
994
|
+
if (inlineSchema.name === childType) {
|
|
995
|
+
matchingSchema = inlineSchema;
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
if (matchingSchema)
|
|
999
|
+
await validateValue2(child, matchingSchema, childPath, errors, context, schema);
|
|
1000
|
+
else if (childType) {
|
|
1001
|
+
const allowedTypes = ["span", ...inlineTypes.map((t) => t.name).filter(Boolean)];
|
|
1002
|
+
errors.push(
|
|
1003
|
+
createError(
|
|
1004
|
+
childPath,
|
|
1005
|
+
"ARRAY_ITEM_INVALID",
|
|
1006
|
+
`Unknown type "${childType}" in block children`,
|
|
1007
|
+
formatValue(child),
|
|
1008
|
+
`One of: ${allowedTypes.join(", ")}`,
|
|
1009
|
+
`Use one of the allowed types: ${allowedTypes.join(", ")}`
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
} else
|
|
1013
|
+
errors.push(
|
|
1014
|
+
createError(
|
|
1015
|
+
childPath,
|
|
1016
|
+
"ARRAY_ITEM_INVALID",
|
|
1017
|
+
"Block child missing _type property",
|
|
1018
|
+
formatValue(child),
|
|
1019
|
+
'Object with _type property ("span" or inline object type)',
|
|
1020
|
+
"Add a _type property to identify the child type"
|
|
1021
|
+
)
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function validateSpan(spanObj, path, errors, markDefKeys, allowedDecorators) {
|
|
1026
|
+
if (spanObj.text === void 0 ? errors.push(
|
|
1027
|
+
createError(
|
|
1028
|
+
`${path}.text`,
|
|
1029
|
+
"REQUIRED_FIELD_MISSING",
|
|
1030
|
+
'Span missing "text" property',
|
|
1031
|
+
"(not provided)",
|
|
1032
|
+
"string",
|
|
1033
|
+
"Add a text property to the span"
|
|
1034
|
+
)
|
|
1035
|
+
) : typeof spanObj.text != "string" && errors.push(
|
|
1036
|
+
createError(
|
|
1037
|
+
`${path}.text`,
|
|
1038
|
+
"TYPE_MISMATCH",
|
|
1039
|
+
"Span text must be a string",
|
|
1040
|
+
formatValue(spanObj.text),
|
|
1041
|
+
"string",
|
|
1042
|
+
"Provide a string value for text"
|
|
1043
|
+
)
|
|
1044
|
+
), spanObj.marks !== void 0)
|
|
1045
|
+
if (!Array.isArray(spanObj.marks))
|
|
1046
|
+
errors.push(
|
|
1047
|
+
createError(
|
|
1048
|
+
`${path}.marks`,
|
|
1049
|
+
"TYPE_MISMATCH",
|
|
1050
|
+
"Span marks must be an array",
|
|
1051
|
+
formatValue(spanObj.marks),
|
|
1052
|
+
"Array of strings (decorator names or annotation _keys)",
|
|
1053
|
+
"Provide an array of mark strings"
|
|
1054
|
+
)
|
|
1055
|
+
);
|
|
1056
|
+
else
|
|
1057
|
+
for (let i = 0; i < spanObj.marks.length; i++) {
|
|
1058
|
+
const mark = spanObj.marks[i];
|
|
1059
|
+
if (typeof mark != "string")
|
|
1060
|
+
errors.push(
|
|
1061
|
+
createError(
|
|
1062
|
+
`${path}.marks[${i}]`,
|
|
1063
|
+
"TYPE_MISMATCH",
|
|
1064
|
+
"Span mark must be a string",
|
|
1065
|
+
formatValue(mark),
|
|
1066
|
+
"string (decorator name or annotation _key)",
|
|
1067
|
+
"Provide a string mark value"
|
|
1068
|
+
)
|
|
1069
|
+
);
|
|
1070
|
+
else {
|
|
1071
|
+
const isDecorator = allowedDecorators.has(mark), isMarkDefRef = markDefKeys.has(mark);
|
|
1072
|
+
if (!isDecorator && !isMarkDefRef) {
|
|
1073
|
+
const decoratorList = [...allowedDecorators].join(", ");
|
|
1074
|
+
errors.push(
|
|
1075
|
+
createError(
|
|
1076
|
+
`${path}.marks[${i}]`,
|
|
1077
|
+
"INVALID_MARK_REFERENCE",
|
|
1078
|
+
`Mark "${mark}" is not a valid decorator and does not reference a markDef`,
|
|
1079
|
+
mark,
|
|
1080
|
+
`Decorator (${decoratorList}) or _key from markDefs`,
|
|
1081
|
+
markDefKeys.size > 0 ? `Use a decorator (${decoratorList}) or one of the markDef keys: ${[...markDefKeys].join(", ")}` : `Use a decorator (${decoratorList}) or add a markDef with _key "${mark}"`
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async function validateBlockMarkDefs(markDefs, blockSchema, path, schema, errors, context, validateValue2) {
|
|
1089
|
+
const schemaAnnotations = blockSchema.marks?.annotations || [], defaultAnnotationTypes = ["link"], annotationTypes = schemaAnnotations.length > 0 ? schemaAnnotations : [], allowedAnnotationNames = schemaAnnotations.length > 0 ? schemaAnnotations.map((t) => t.name).filter(Boolean) : defaultAnnotationTypes, seenKeys = /* @__PURE__ */ new Set();
|
|
1090
|
+
for (let i = 0; i < markDefs.length; i++) {
|
|
1091
|
+
const markDef = markDefs[i], markPath = `${path}[${i}]`;
|
|
1092
|
+
if (typeof markDef != "object" || markDef === null) {
|
|
1093
|
+
errors.push(
|
|
1094
|
+
createError(
|
|
1095
|
+
markPath,
|
|
1096
|
+
"TYPE_MISMATCH",
|
|
1097
|
+
"markDef must be an object",
|
|
1098
|
+
formatValue(markDef),
|
|
1099
|
+
"Annotation object with _type and _key",
|
|
1100
|
+
"Provide an annotation object"
|
|
1101
|
+
)
|
|
1102
|
+
);
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
const markObj = markDef, markType = markObj._type, markKey = markObj._key;
|
|
1106
|
+
if (markKey === void 0 ? errors.push(
|
|
1107
|
+
createError(
|
|
1108
|
+
markPath,
|
|
1109
|
+
"ARRAY_ITEM_INVALID",
|
|
1110
|
+
'markDef missing "_key" property',
|
|
1111
|
+
formatValue(markDef),
|
|
1112
|
+
"Object with _key property",
|
|
1113
|
+
"Add a unique _key property to the markDef"
|
|
1114
|
+
)
|
|
1115
|
+
) : typeof markKey == "string" && markKey !== "" && (seenKeys.has(markKey) ? errors.push(
|
|
1116
|
+
createError(
|
|
1117
|
+
`${markPath}._key`,
|
|
1118
|
+
"ARRAY_ITEM_INVALID",
|
|
1119
|
+
`duplicate _key "${markKey}" in markDefs array`,
|
|
1120
|
+
markKey,
|
|
1121
|
+
"Unique _key value",
|
|
1122
|
+
"Ensure all markDefs have unique _key values"
|
|
1123
|
+
)
|
|
1124
|
+
) : seenKeys.add(markKey)), markType === void 0) {
|
|
1125
|
+
errors.push(
|
|
1126
|
+
createError(
|
|
1127
|
+
markPath,
|
|
1128
|
+
"ARRAY_ITEM_INVALID",
|
|
1129
|
+
'markDef missing "_type" property',
|
|
1130
|
+
formatValue(markDef),
|
|
1131
|
+
"Object with _type property (annotation type)",
|
|
1132
|
+
'Add a _type property to identify the annotation type, e.g., { _type: "link", _key: "l1", href: "..." }'
|
|
1133
|
+
)
|
|
1134
|
+
);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
let matchingSchema;
|
|
1138
|
+
for (const annSchema of annotationTypes)
|
|
1139
|
+
if (annSchema.name === markType) {
|
|
1140
|
+
matchingSchema = annSchema;
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
matchingSchema ? await validateValue2(markDef, matchingSchema, markPath, errors, context, schema) : allowedAnnotationNames.includes(markType) || errors.push(
|
|
1144
|
+
createError(
|
|
1145
|
+
markPath,
|
|
1146
|
+
"ARRAY_ITEM_INVALID",
|
|
1147
|
+
`Unknown annotation type "${markType}" in markDefs`,
|
|
1148
|
+
formatValue(markDef),
|
|
1149
|
+
`One of: ${allowedAnnotationNames.join(", ")}`,
|
|
1150
|
+
`Use one of the allowed annotation types: ${allowedAnnotationNames.join(", ")}`
|
|
1151
|
+
)
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
function resolveEncodedValue(v) {
|
|
1156
|
+
if (typeof v == "object" && v !== null && "__type" in v) {
|
|
1157
|
+
const marker = v;
|
|
1158
|
+
if (marker.__type === "number" && typeof marker.value == "string")
|
|
1159
|
+
return parseFloat(marker.value);
|
|
1160
|
+
if (marker.__type === "object" && typeof marker.value == "object" && marker.value !== null)
|
|
1161
|
+
return marker.value;
|
|
1162
|
+
if (marker.__type === "undefined")
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
return v;
|
|
1166
|
+
}
|
|
1167
|
+
function validatePrimitive(value, schemaType, path, errors, fieldSchema) {
|
|
1168
|
+
const expectedType = getExpectedJsType(schemaType), actualType = getActualJsType(value);
|
|
1169
|
+
if (!(actualType === "null" || actualType === "undefined")) {
|
|
1170
|
+
if (actualType !== expectedType) {
|
|
1171
|
+
errors.push(
|
|
1172
|
+
createError(
|
|
1173
|
+
path,
|
|
1174
|
+
"TYPE_MISMATCH",
|
|
1175
|
+
"Type mismatch",
|
|
1176
|
+
formatValue(value),
|
|
1177
|
+
expectedType,
|
|
1178
|
+
`Provide a ${expectedType} value`
|
|
1179
|
+
)
|
|
1180
|
+
);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (actualType === "string") {
|
|
1184
|
+
const strValue = value;
|
|
1185
|
+
schemaType === "date" ? validateDate(strValue, path, errors) : schemaType === "datetime" ? validateDatetime(strValue, path, errors) : schemaType === "email" ? validateEmail(strValue, path, errors) : schemaType === "url" && validateUrl(strValue, path, errors, fieldSchema);
|
|
1186
|
+
}
|
|
1187
|
+
if (actualType === "number") {
|
|
1188
|
+
const numValue = value;
|
|
1189
|
+
Number.isNaN(numValue) ? errors.push(
|
|
1190
|
+
createError(
|
|
1191
|
+
path,
|
|
1192
|
+
"TYPE_MISMATCH",
|
|
1193
|
+
"Invalid number (NaN)",
|
|
1194
|
+
"NaN",
|
|
1195
|
+
"A valid number",
|
|
1196
|
+
"Provide a valid numeric value"
|
|
1197
|
+
)
|
|
1198
|
+
) : Number.isFinite(numValue) || errors.push(
|
|
1199
|
+
createError(
|
|
1200
|
+
path,
|
|
1201
|
+
"TYPE_MISMATCH",
|
|
1202
|
+
"Invalid number (Infinity)",
|
|
1203
|
+
numValue > 0 ? "Infinity" : "-Infinity",
|
|
1204
|
+
"A finite number",
|
|
1205
|
+
"Provide a valid finite numeric value"
|
|
1206
|
+
)
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function validateDate(value, path, errors) {
|
|
1212
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
1213
|
+
errors.push(
|
|
1214
|
+
createError(
|
|
1215
|
+
path,
|
|
1216
|
+
"INVALID_FORMAT",
|
|
1217
|
+
"Invalid date format",
|
|
1218
|
+
formatValue(value),
|
|
1219
|
+
"ISO date format (YYYY-MM-DD)",
|
|
1220
|
+
'Provide a date like "2024-01-15"'
|
|
1221
|
+
)
|
|
1222
|
+
);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
1226
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
1227
|
+
errors.push(
|
|
1228
|
+
createError(
|
|
1229
|
+
path,
|
|
1230
|
+
"INVALID_FORMAT",
|
|
1231
|
+
"Invalid date value",
|
|
1232
|
+
formatValue(value),
|
|
1233
|
+
"A valid date (e.g., month 01-12, valid day for month)",
|
|
1234
|
+
'Provide a valid date like "2024-01-15"'
|
|
1235
|
+
)
|
|
1236
|
+
);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
1240
|
+
(parsed.getUTCFullYear() !== year || parsed.getUTCMonth() + 1 !== month || parsed.getUTCDate() !== day) && errors.push(
|
|
1241
|
+
createError(
|
|
1242
|
+
path,
|
|
1243
|
+
"INVALID_FORMAT",
|
|
1244
|
+
"Invalid date value",
|
|
1245
|
+
formatValue(value),
|
|
1246
|
+
"A valid date (e.g., valid day for the given month)",
|
|
1247
|
+
'Provide a valid date like "2024-01-15"'
|
|
1248
|
+
)
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
function validateDatetime(value, path, errors) {
|
|
1252
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
|
|
1253
|
+
errors.push(
|
|
1254
|
+
createError(
|
|
1255
|
+
path,
|
|
1256
|
+
"INVALID_FORMAT",
|
|
1257
|
+
"Invalid datetime format",
|
|
1258
|
+
formatValue(value),
|
|
1259
|
+
"ISO datetime format (YYYY-MM-DDTHH:mm:ss)",
|
|
1260
|
+
'Provide a datetime like "2024-01-15T10:30:00Z"'
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const parsed = new Date(value);
|
|
1266
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
1267
|
+
errors.push(
|
|
1268
|
+
createError(
|
|
1269
|
+
path,
|
|
1270
|
+
"INVALID_FORMAT",
|
|
1271
|
+
"Invalid datetime value",
|
|
1272
|
+
formatValue(value),
|
|
1273
|
+
"A valid datetime (valid date and time components)",
|
|
1274
|
+
'Provide a valid datetime like "2024-01-15T10:30:00Z"'
|
|
1275
|
+
)
|
|
1276
|
+
);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const datePart = value.slice(0, 10), [year, month, day] = datePart.split("-").map(Number);
|
|
1280
|
+
(parsed.getUTCFullYear() !== year || parsed.getUTCMonth() + 1 !== month || parsed.getUTCDate() !== day) && errors.push(
|
|
1281
|
+
createError(
|
|
1282
|
+
path,
|
|
1283
|
+
"INVALID_FORMAT",
|
|
1284
|
+
"Invalid datetime value",
|
|
1285
|
+
formatValue(value),
|
|
1286
|
+
"A valid datetime (valid day for the given month)",
|
|
1287
|
+
'Provide a valid datetime like "2024-01-15T10:30:00Z"'
|
|
1288
|
+
)
|
|
1289
|
+
);
|
|
1290
|
+
const timeMatch = value.match(/T(\d{2}):(\d{2}):(\d{2})/);
|
|
1291
|
+
if (timeMatch) {
|
|
1292
|
+
const [, hour, minute] = timeMatch.map(Number);
|
|
1293
|
+
(hour > 23 || minute > 59) && errors.push(
|
|
1294
|
+
createError(
|
|
1295
|
+
path,
|
|
1296
|
+
"INVALID_FORMAT",
|
|
1297
|
+
"Invalid datetime value",
|
|
1298
|
+
formatValue(value),
|
|
1299
|
+
"A valid time (hour 00-23, minute 00-59)",
|
|
1300
|
+
'Provide a valid datetime like "2024-01-15T10:30:00Z"'
|
|
1301
|
+
)
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
function validateEmail(value, path, errors) {
|
|
1306
|
+
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || errors.push(
|
|
1307
|
+
createError(
|
|
1308
|
+
path,
|
|
1309
|
+
"INVALID_FORMAT",
|
|
1310
|
+
"Invalid email format",
|
|
1311
|
+
formatValue(value),
|
|
1312
|
+
'Valid email address (e.g., "user@example.com")',
|
|
1313
|
+
"Provide a valid email address"
|
|
1314
|
+
)
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
function validateUrl(value, path, errors, fieldSchema) {
|
|
1318
|
+
const allowRelative = fieldSchema?.validation?.some(
|
|
1319
|
+
(group) => group.rules.some(
|
|
1320
|
+
(rule) => (rule.type === "uri" || rule.flag === "uri") && rule.allowRelative === !0
|
|
1321
|
+
)
|
|
1322
|
+
);
|
|
1323
|
+
if (!(allowRelative && value.startsWith("/")))
|
|
1324
|
+
try {
|
|
1325
|
+
new URL(value);
|
|
1326
|
+
} catch {
|
|
1327
|
+
errors.push(
|
|
1328
|
+
createError(
|
|
1329
|
+
path,
|
|
1330
|
+
"INVALID_FORMAT",
|
|
1331
|
+
"Invalid URL format",
|
|
1332
|
+
formatValue(value),
|
|
1333
|
+
allowRelative ? 'Valid URL (e.g., "https://example.com" or "/path")' : 'Valid URL (e.g., "https://example.com")',
|
|
1334
|
+
allowRelative ? "Provide a valid URL starting with http://, https://, or /" : "Provide a valid URL starting with http:// or https://"
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function validateSchemaRules(value, fieldSchema, path, errors, warnings) {
|
|
1340
|
+
if (fieldSchema.validation)
|
|
1341
|
+
for (const group of fieldSchema.validation) {
|
|
1342
|
+
const target = group.level === "warning" || group.level === "info" ? warnings ?? null : errors;
|
|
1343
|
+
if (target !== null)
|
|
1344
|
+
for (const rule of group.rules) {
|
|
1345
|
+
const r = rule, ruleType = r.type || r.flag;
|
|
1346
|
+
ruleType === "min" || ruleType === "minimum" ? validateBound(value, "min", getConstraint(r), path, target) : ruleType === "max" || ruleType === "maximum" ? validateBound(value, "max", getConstraint(r), path, target) : ruleType === "enum" ? validateEnum(value, r, path, target) : ruleType === "regex" ? validateRegex(value, r, path, target) : (ruleType === "unique" || ruleType === "uniqueItems") && validateUniqueRule(value, path, target);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function getConstraint(r) {
|
|
1351
|
+
if (r.value !== void 0) return parseFloat(r.value);
|
|
1352
|
+
if (r.constraint !== void 0) return r.constraint;
|
|
1353
|
+
}
|
|
1354
|
+
function validateBound(value, direction, bound, path, target) {
|
|
1355
|
+
if (bound === void 0) return;
|
|
1356
|
+
const isMin = direction === "min", fails = isMin ? (actual) => actual < bound : (actual) => actual > bound;
|
|
1357
|
+
typeof value == "string" && fails(value.length) ? target.push(
|
|
1358
|
+
createError(
|
|
1359
|
+
path,
|
|
1360
|
+
"VALUE_OUT_OF_RANGE",
|
|
1361
|
+
isMin ? `Value is below minimum length of ${bound}` : `Value exceeds maximum length of ${bound}`,
|
|
1362
|
+
`${value.length} characters`,
|
|
1363
|
+
`${isMin ? "At least" : "At most"} ${bound} characters`,
|
|
1364
|
+
`Provide a value with ${isMin ? "at least" : "at most"} ${bound} characters`
|
|
1365
|
+
)
|
|
1366
|
+
) : typeof value == "number" && fails(value) ? target.push(
|
|
1367
|
+
createError(
|
|
1368
|
+
path,
|
|
1369
|
+
"VALUE_OUT_OF_RANGE",
|
|
1370
|
+
isMin ? `Value is below minimum of ${bound}` : `Value exceeds maximum of ${bound}`,
|
|
1371
|
+
String(value),
|
|
1372
|
+
`${isMin ? "At least" : "At most"} ${bound}`,
|
|
1373
|
+
`Provide a value of ${isMin ? "at least" : "at most"} ${bound}`
|
|
1374
|
+
)
|
|
1375
|
+
) : Array.isArray(value) && fails(value.length) && target.push(
|
|
1376
|
+
createError(
|
|
1377
|
+
path,
|
|
1378
|
+
"VALUE_OUT_OF_RANGE",
|
|
1379
|
+
isMin ? `Array has fewer than ${bound} items` : `Array has more than ${bound} items`,
|
|
1380
|
+
`${value.length} items`,
|
|
1381
|
+
`${isMin ? "At least" : "At most"} ${bound} items`,
|
|
1382
|
+
`Provide ${isMin ? "at least" : "at most"} ${bound} items`
|
|
1383
|
+
)
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
function validateEnum(value, r, path, target) {
|
|
1387
|
+
const allowedValues = r.values || r.constraint;
|
|
1388
|
+
if (!allowedValues) return;
|
|
1389
|
+
const resolvedValues = allowedValues.map(resolveEncodedValue), formatAllowed = () => resolvedValues.map((v) => JSON.stringify(v)).join(", ");
|
|
1390
|
+
if (Array.isArray(value))
|
|
1391
|
+
for (let i = 0; i < value.length; i++)
|
|
1392
|
+
resolvedValues.includes(value[i]) || target.push(
|
|
1393
|
+
createError(
|
|
1394
|
+
`${path}[${i}]`,
|
|
1395
|
+
"INVALID_ENUM_VALUE",
|
|
1396
|
+
"Array item is not one of the allowed options",
|
|
1397
|
+
formatValue(value[i]),
|
|
1398
|
+
`One of: ${formatAllowed()}`,
|
|
1399
|
+
`Use one of the allowed values: ${formatAllowed()}`
|
|
1400
|
+
)
|
|
1401
|
+
);
|
|
1402
|
+
else resolvedValues.includes(value) || target.push(
|
|
1403
|
+
createError(
|
|
1404
|
+
path,
|
|
1405
|
+
"INVALID_ENUM_VALUE",
|
|
1406
|
+
"Value is not one of the allowed options",
|
|
1407
|
+
formatValue(value),
|
|
1408
|
+
`One of: ${formatAllowed()}`,
|
|
1409
|
+
`Use one of the allowed values: ${formatAllowed()}`
|
|
1410
|
+
)
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
function validateRegex(value, r, path, target) {
|
|
1414
|
+
if (!r.pattern || typeof value != "string") return;
|
|
1415
|
+
const pattern = r.pattern, flags = r.flags || "";
|
|
1416
|
+
try {
|
|
1417
|
+
new RegExp(pattern, flags).test(value) || target.push(
|
|
1418
|
+
createError(
|
|
1419
|
+
path,
|
|
1420
|
+
"INVALID_FORMAT",
|
|
1421
|
+
"Value does not match required regex pattern",
|
|
1422
|
+
formatValue(value),
|
|
1423
|
+
`String matching pattern: ${pattern}`,
|
|
1424
|
+
"Provide a value that matches the required format"
|
|
1425
|
+
)
|
|
1426
|
+
);
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function validateUniqueRule(value, path, target) {
|
|
1431
|
+
if (!Array.isArray(value)) return;
|
|
1432
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1433
|
+
for (let i = 0; i < value.length; i++) {
|
|
1434
|
+
const key = getUniquenessKey(value[i]);
|
|
1435
|
+
if (seen.has(key)) {
|
|
1436
|
+
target.push(
|
|
1437
|
+
createError(
|
|
1438
|
+
`${path}[${i}]`,
|
|
1439
|
+
"DUPLICATE_ARRAY_ITEM",
|
|
1440
|
+
"Duplicate item in array with unique constraint",
|
|
1441
|
+
formatValue(value[i]),
|
|
1442
|
+
"Unique array items",
|
|
1443
|
+
"Remove or modify duplicate items in the array"
|
|
1444
|
+
)
|
|
1445
|
+
);
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
seen.add(key);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
async function validateReference(value, fieldSchema, path, errors, context) {
|
|
1452
|
+
if (typeof value != "object" || value === null) {
|
|
1453
|
+
errors.push(
|
|
1454
|
+
createError(
|
|
1455
|
+
path,
|
|
1456
|
+
"TYPE_MISMATCH",
|
|
1457
|
+
"Invalid reference value",
|
|
1458
|
+
formatValue(value),
|
|
1459
|
+
'Object with { _type: "reference", _ref: "document-id" }',
|
|
1460
|
+
'Provide a reference object like { _type: "reference", _ref: "person-123" }'
|
|
1461
|
+
)
|
|
1462
|
+
);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const refObj = value;
|
|
1466
|
+
if (refObj._type === "globalDocumentReference")
|
|
1467
|
+
return;
|
|
1468
|
+
if (typeof refObj._ref != "string") {
|
|
1469
|
+
errors.push(
|
|
1470
|
+
createError(
|
|
1471
|
+
`${path}._ref`,
|
|
1472
|
+
"INVALID_REFERENCE",
|
|
1473
|
+
'Reference missing "_ref" property',
|
|
1474
|
+
formatValue(refObj._ref),
|
|
1475
|
+
"string (document ID)",
|
|
1476
|
+
"Provide a valid document ID in the _ref property"
|
|
1477
|
+
)
|
|
1478
|
+
);
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (refObj._ref === "") {
|
|
1482
|
+
errors.push(
|
|
1483
|
+
createError(
|
|
1484
|
+
`${path}._ref`,
|
|
1485
|
+
"INVALID_REFERENCE",
|
|
1486
|
+
'Reference "_ref" cannot be empty',
|
|
1487
|
+
'""',
|
|
1488
|
+
"Non-empty string (document ID)",
|
|
1489
|
+
"Provide a valid document ID in the _ref property"
|
|
1490
|
+
)
|
|
1491
|
+
);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
validateNoExtraProperties(refObj, /* @__PURE__ */ new Set([
|
|
1495
|
+
"_ref",
|
|
1496
|
+
"_type",
|
|
1497
|
+
"_weak",
|
|
1498
|
+
"_key",
|
|
1499
|
+
"_strengthenOnPublish"
|
|
1500
|
+
]), path, "reference", errors);
|
|
1501
|
+
const allowedTypes = [];
|
|
1502
|
+
if (fieldSchema.to && Array.isArray(fieldSchema.to))
|
|
1503
|
+
for (const target of fieldSchema.to) {
|
|
1504
|
+
const targetType = target.type;
|
|
1505
|
+
targetType && allowedTypes.push(targetType);
|
|
1506
|
+
}
|
|
1507
|
+
if (!refObj._weak && context.client)
|
|
1508
|
+
try {
|
|
1509
|
+
const doc = await context.client.getDocument(refObj._ref);
|
|
1510
|
+
if (!doc) {
|
|
1511
|
+
errors.push(
|
|
1512
|
+
createError(
|
|
1513
|
+
`${path}._ref`,
|
|
1514
|
+
"REFERENCE_NOT_FOUND",
|
|
1515
|
+
"Referenced document does not exist",
|
|
1516
|
+
formatValue(refObj._ref),
|
|
1517
|
+
allowedTypes.length > 0 ? `Valid document ID of type "${allowedTypes.join('" or "')}"` : "Valid document ID",
|
|
1518
|
+
"Use an existing document ID, or create the document first"
|
|
1519
|
+
)
|
|
1520
|
+
);
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
allowedTypes.length > 0 && !allowedTypes.includes(doc._type) && errors.push(
|
|
1524
|
+
createError(
|
|
1525
|
+
`${path}._ref`,
|
|
1526
|
+
"REFERENCE_WRONG_TYPE",
|
|
1527
|
+
"Reference points to wrong document type",
|
|
1528
|
+
`Document "${refObj._ref}" (type: ${doc._type})`,
|
|
1529
|
+
`Reference to document of type "${allowedTypes.join('" or "')}"`,
|
|
1530
|
+
`Use a document ID that references a "${allowedTypes[0]}" document`
|
|
1531
|
+
)
|
|
1532
|
+
);
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function validateSlug(value, path, errors) {
|
|
1537
|
+
if (typeof value != "object" || value === null) {
|
|
1538
|
+
errors.push(
|
|
1539
|
+
createError(
|
|
1540
|
+
path,
|
|
1541
|
+
"TYPE_MISMATCH",
|
|
1542
|
+
"Invalid slug value",
|
|
1543
|
+
formatValue(value),
|
|
1544
|
+
'Object with { _type: "slug", current: "slug-value" }',
|
|
1545
|
+
'Provide a slug object like { _type: "slug", current: "my-page" }'
|
|
1546
|
+
)
|
|
1547
|
+
);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const slugObj = value;
|
|
1551
|
+
slugObj._type !== void 0 && slugObj._type !== "slug" && errors.push(
|
|
1552
|
+
createError(
|
|
1553
|
+
path,
|
|
1554
|
+
"TYPE_MISMATCH",
|
|
1555
|
+
"Slug has wrong _type value",
|
|
1556
|
+
formatValue(slugObj._type),
|
|
1557
|
+
'_type: "slug"',
|
|
1558
|
+
'Set _type to "slug", e.g., { _type: "slug", current: "my-page" }'
|
|
1559
|
+
)
|
|
1560
|
+
), typeof slugObj.current != "string" && errors.push(
|
|
1561
|
+
createError(
|
|
1562
|
+
path,
|
|
1563
|
+
"TYPE_MISMATCH",
|
|
1564
|
+
'Slug missing "current" property',
|
|
1565
|
+
formatValue(value),
|
|
1566
|
+
'Object with "current" string property',
|
|
1567
|
+
'Add a "current" property with the slug string, e.g., { _type: "slug", current: "my-page" }'
|
|
1568
|
+
)
|
|
1569
|
+
), validateNoExtraProperties(slugObj, /* @__PURE__ */ new Set(["_type", "current"]), path, "slug", errors);
|
|
1570
|
+
}
|
|
1571
|
+
function validateGeopoint(value, path, errors) {
|
|
1572
|
+
if (typeof value != "object" || value === null) {
|
|
1573
|
+
errors.push(
|
|
1574
|
+
createError(
|
|
1575
|
+
path,
|
|
1576
|
+
"TYPE_MISMATCH",
|
|
1577
|
+
"Invalid geopoint value",
|
|
1578
|
+
formatValue(value),
|
|
1579
|
+
'Object with { _type: "geopoint", lat: number, lng: number }',
|
|
1580
|
+
'Provide a geopoint object like { _type: "geopoint", lat: 37.7749, lng: -122.4194 }'
|
|
1581
|
+
)
|
|
1582
|
+
);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const geoObj = value;
|
|
1586
|
+
typeof geoObj.lat != "number" ? errors.push(
|
|
1587
|
+
createError(
|
|
1588
|
+
`${path}.lat`,
|
|
1589
|
+
"TYPE_MISMATCH",
|
|
1590
|
+
'Geopoint missing valid "lat" property',
|
|
1591
|
+
formatValue(geoObj.lat),
|
|
1592
|
+
"number (latitude in degrees)",
|
|
1593
|
+
"Provide a numeric latitude value, e.g., 37.7749"
|
|
1594
|
+
)
|
|
1595
|
+
) : Number.isNaN(geoObj.lat) ? errors.push(
|
|
1596
|
+
createError(
|
|
1597
|
+
`${path}.lat`,
|
|
1598
|
+
"TYPE_MISMATCH",
|
|
1599
|
+
"Geopoint latitude is NaN",
|
|
1600
|
+
"NaN",
|
|
1601
|
+
"A valid numeric latitude (-90 to 90)",
|
|
1602
|
+
"Provide a valid numeric latitude value, e.g., 37.7749"
|
|
1603
|
+
)
|
|
1604
|
+
) : Number.isFinite(geoObj.lat) ? (geoObj.lat < -90 || geoObj.lat > 90) && errors.push(
|
|
1605
|
+
createError(
|
|
1606
|
+
`${path}.lat`,
|
|
1607
|
+
"VALUE_OUT_OF_RANGE",
|
|
1608
|
+
"Geopoint latitude out of range",
|
|
1609
|
+
String(geoObj.lat),
|
|
1610
|
+
"Latitude between -90 and 90 degrees",
|
|
1611
|
+
"Provide a latitude between -90 and 90, e.g., 37.7749"
|
|
1612
|
+
)
|
|
1613
|
+
) : errors.push(
|
|
1614
|
+
createError(
|
|
1615
|
+
`${path}.lat`,
|
|
1616
|
+
"VALUE_OUT_OF_RANGE",
|
|
1617
|
+
"Geopoint latitude is infinite",
|
|
1618
|
+
geoObj.lat > 0 ? "Infinity" : "-Infinity",
|
|
1619
|
+
"A finite numeric latitude (-90 to 90)",
|
|
1620
|
+
"Provide a valid numeric latitude value, e.g., 37.7749"
|
|
1621
|
+
)
|
|
1622
|
+
), typeof geoObj.lng != "number" ? errors.push(
|
|
1623
|
+
createError(
|
|
1624
|
+
`${path}.lng`,
|
|
1625
|
+
"TYPE_MISMATCH",
|
|
1626
|
+
'Geopoint missing valid "lng" property',
|
|
1627
|
+
formatValue(geoObj.lng),
|
|
1628
|
+
"number (longitude in degrees)",
|
|
1629
|
+
"Provide a numeric longitude value, e.g., -122.4194"
|
|
1630
|
+
)
|
|
1631
|
+
) : Number.isNaN(geoObj.lng) ? errors.push(
|
|
1632
|
+
createError(
|
|
1633
|
+
`${path}.lng`,
|
|
1634
|
+
"TYPE_MISMATCH",
|
|
1635
|
+
"Geopoint longitude is NaN",
|
|
1636
|
+
"NaN",
|
|
1637
|
+
"A valid numeric longitude (-180 to 180)",
|
|
1638
|
+
"Provide a valid numeric longitude value, e.g., -122.4194"
|
|
1639
|
+
)
|
|
1640
|
+
) : Number.isFinite(geoObj.lng) ? (geoObj.lng < -180 || geoObj.lng > 180) && errors.push(
|
|
1641
|
+
createError(
|
|
1642
|
+
`${path}.lng`,
|
|
1643
|
+
"VALUE_OUT_OF_RANGE",
|
|
1644
|
+
"Geopoint longitude out of range",
|
|
1645
|
+
String(geoObj.lng),
|
|
1646
|
+
"Longitude between -180 and 180 degrees",
|
|
1647
|
+
"Provide a longitude between -180 and 180, e.g., -122.4194"
|
|
1648
|
+
)
|
|
1649
|
+
) : errors.push(
|
|
1650
|
+
createError(
|
|
1651
|
+
`${path}.lng`,
|
|
1652
|
+
"VALUE_OUT_OF_RANGE",
|
|
1653
|
+
"Geopoint longitude is infinite",
|
|
1654
|
+
geoObj.lng > 0 ? "Infinity" : "-Infinity",
|
|
1655
|
+
"A finite numeric longitude (-180 to 180)",
|
|
1656
|
+
"Provide a valid numeric longitude value, e.g., -122.4194"
|
|
1657
|
+
)
|
|
1658
|
+
), validateNoExtraProperties(geoObj, /* @__PURE__ */ new Set(["_type", "lat", "lng", "alt"]), path, "geopoint", errors);
|
|
1659
|
+
}
|
|
1660
|
+
function validateImage(value, path, errors, fieldSchema, schema) {
|
|
1661
|
+
if (typeof value != "object" || value === null) {
|
|
1662
|
+
errors.push(
|
|
1663
|
+
createError(
|
|
1664
|
+
path,
|
|
1665
|
+
"TYPE_MISMATCH",
|
|
1666
|
+
"Invalid image value",
|
|
1667
|
+
formatValue(value),
|
|
1668
|
+
'Object with { _type: "image", asset: { _type: "reference", _ref: "..." } }',
|
|
1669
|
+
"Provide an image object or use the create-image tool"
|
|
1670
|
+
)
|
|
1671
|
+
);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
const imgObj = value;
|
|
1675
|
+
if (imgObj._type !== void 0 && imgObj._type !== "image" && imgObj._type !== fieldSchema?.name && errors.push(
|
|
1676
|
+
createError(
|
|
1677
|
+
`${path}._type`,
|
|
1678
|
+
"TYPE_MISMATCH",
|
|
1679
|
+
'Image must have _type: "image"',
|
|
1680
|
+
formatValue(imgObj._type),
|
|
1681
|
+
'_type: "image"',
|
|
1682
|
+
'Set _type to "image"'
|
|
1683
|
+
)
|
|
1684
|
+
), imgObj.asset !== void 0 && imgObj.asset !== null) {
|
|
1685
|
+
const asset = imgObj.asset;
|
|
1686
|
+
typeof asset._ref != "string" && errors.push(
|
|
1687
|
+
createError(
|
|
1688
|
+
`${path}.asset._ref`,
|
|
1689
|
+
"TYPE_MISMATCH",
|
|
1690
|
+
'Image asset missing "_ref" property',
|
|
1691
|
+
formatValue(asset._ref),
|
|
1692
|
+
"string (asset document ID)",
|
|
1693
|
+
'Provide a valid asset reference like { _type: "reference", _ref: "image-abc123-..." }'
|
|
1694
|
+
)
|
|
1695
|
+
), asset._type !== "reference" && errors.push(
|
|
1696
|
+
createError(
|
|
1697
|
+
`${path}.asset._type`,
|
|
1698
|
+
"TYPE_MISMATCH",
|
|
1699
|
+
'Image asset must have _type: "reference"',
|
|
1700
|
+
formatValue(asset._type),
|
|
1701
|
+
'_type: "reference"',
|
|
1702
|
+
'Set asset._type to "reference", e.g., { _type: "reference", _ref: "image-abc123-..." }'
|
|
1703
|
+
)
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
if (imgObj.hotspot !== void 0 && imgObj.hotspot !== null) {
|
|
1707
|
+
const hotspot = imgObj.hotspot, hotspotProps = ["x", "y", "height", "width"];
|
|
1708
|
+
for (const prop of hotspotProps)
|
|
1709
|
+
hotspot[prop] !== void 0 && (typeof hotspot[prop] != "number" ? errors.push(
|
|
1710
|
+
createError(
|
|
1711
|
+
`${path}.hotspot.${prop}`,
|
|
1712
|
+
"TYPE_MISMATCH",
|
|
1713
|
+
`Hotspot ${prop} must be a number`,
|
|
1714
|
+
formatValue(hotspot[prop]),
|
|
1715
|
+
"number between 0 and 1",
|
|
1716
|
+
`Provide a number between 0 and 1 for hotspot.${prop}`
|
|
1717
|
+
)
|
|
1718
|
+
) : (hotspot[prop] < 0 || hotspot[prop] > 1) && errors.push(
|
|
1719
|
+
createError(
|
|
1720
|
+
`${path}.hotspot.${prop}`,
|
|
1721
|
+
"VALUE_OUT_OF_RANGE",
|
|
1722
|
+
`Hotspot ${prop} out of range`,
|
|
1723
|
+
String(hotspot[prop]),
|
|
1724
|
+
"number between 0 and 1",
|
|
1725
|
+
`Provide a number between 0 and 1 for hotspot.${prop}`
|
|
1726
|
+
)
|
|
1727
|
+
));
|
|
1728
|
+
}
|
|
1729
|
+
if (imgObj.crop !== void 0 && imgObj.crop !== null) {
|
|
1730
|
+
const crop = imgObj.crop, cropProps = ["top", "bottom", "left", "right"];
|
|
1731
|
+
for (const prop of cropProps)
|
|
1732
|
+
crop[prop] !== void 0 && (typeof crop[prop] != "number" ? errors.push(
|
|
1733
|
+
createError(
|
|
1734
|
+
`${path}.crop.${prop}`,
|
|
1735
|
+
"TYPE_MISMATCH",
|
|
1736
|
+
`Crop ${prop} must be a number`,
|
|
1737
|
+
formatValue(crop[prop]),
|
|
1738
|
+
"number between 0 and 1",
|
|
1739
|
+
`Provide a number between 0 and 1 for crop.${prop}`
|
|
1740
|
+
)
|
|
1741
|
+
) : (crop[prop] < 0 || crop[prop] > 1) && errors.push(
|
|
1742
|
+
createError(
|
|
1743
|
+
`${path}.crop.${prop}`,
|
|
1744
|
+
"VALUE_OUT_OF_RANGE",
|
|
1745
|
+
`Crop ${prop} out of range`,
|
|
1746
|
+
String(crop[prop]),
|
|
1747
|
+
"number between 0 and 1",
|
|
1748
|
+
`Provide a number between 0 and 1 for crop.${prop}`
|
|
1749
|
+
)
|
|
1750
|
+
));
|
|
1751
|
+
}
|
|
1752
|
+
if (fieldSchema?.fields && schema)
|
|
1753
|
+
for (const field of fieldSchema.fields) {
|
|
1754
|
+
const fieldValue = imgObj[field.name];
|
|
1755
|
+
if (fieldValue != null) {
|
|
1756
|
+
const expectedType = getExpectedJsType(field.type);
|
|
1757
|
+
getActualJsType(fieldValue) !== expectedType && errors.push(
|
|
1758
|
+
createError(
|
|
1759
|
+
`${path}.${field.name}`,
|
|
1760
|
+
"TYPE_MISMATCH",
|
|
1761
|
+
`Image metadata field "${field.name}" has wrong type`,
|
|
1762
|
+
formatValue(fieldValue),
|
|
1763
|
+
expectedType,
|
|
1764
|
+
`Provide a ${expectedType} value for ${field.name}`
|
|
1765
|
+
)
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const allowedImageProperties = /* @__PURE__ */ new Set(["_type", "_key", "asset", "hotspot", "crop"]);
|
|
1770
|
+
if (fieldSchema?.fields)
|
|
1771
|
+
for (const field of fieldSchema.fields)
|
|
1772
|
+
allowedImageProperties.add(field.name);
|
|
1773
|
+
if (fieldSchema?.options) {
|
|
1774
|
+
const options = fieldSchema.options;
|
|
1775
|
+
if (typeof options.captionField == "string" && allowedImageProperties.add(options.captionField), options.aiAssist && typeof options.aiAssist == "object") {
|
|
1776
|
+
const aiAssist = options.aiAssist;
|
|
1777
|
+
typeof aiAssist.imageDescriptionField == "string" && allowedImageProperties.add(aiAssist.imageDescriptionField);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
validateNoExtraProperties(imgObj, allowedImageProperties, path, "image", errors);
|
|
1781
|
+
}
|
|
1782
|
+
function validateFile(value, path, errors) {
|
|
1783
|
+
if (typeof value != "object" || value === null) {
|
|
1784
|
+
errors.push(
|
|
1785
|
+
createError(
|
|
1786
|
+
path,
|
|
1787
|
+
"TYPE_MISMATCH",
|
|
1788
|
+
"Invalid file value",
|
|
1789
|
+
formatValue(value),
|
|
1790
|
+
'Object with { _type: "file", asset: { _type: "reference", _ref: "..." } }',
|
|
1791
|
+
"Provide a file object with an asset reference"
|
|
1792
|
+
)
|
|
1793
|
+
);
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const fileObj = value;
|
|
1797
|
+
if (fileObj._type !== void 0 && fileObj._type !== "file" && errors.push(
|
|
1798
|
+
createError(
|
|
1799
|
+
`${path}._type`,
|
|
1800
|
+
"TYPE_MISMATCH",
|
|
1801
|
+
'File must have _type: "file"',
|
|
1802
|
+
formatValue(fileObj._type),
|
|
1803
|
+
'_type: "file"',
|
|
1804
|
+
'Set _type to "file"'
|
|
1805
|
+
)
|
|
1806
|
+
), fileObj.asset !== void 0 && fileObj.asset !== null) {
|
|
1807
|
+
const asset = fileObj.asset;
|
|
1808
|
+
asset._type !== "reference" && errors.push(
|
|
1809
|
+
createError(
|
|
1810
|
+
`${path}.asset._type`,
|
|
1811
|
+
"TYPE_MISMATCH",
|
|
1812
|
+
'File asset must have _type: "reference"',
|
|
1813
|
+
formatValue(asset._type),
|
|
1814
|
+
'_type: "reference"',
|
|
1815
|
+
'Set asset._type to "reference", e.g., { _type: "reference", _ref: "file-abc123-..." }'
|
|
1816
|
+
)
|
|
1817
|
+
), typeof asset._ref != "string" && errors.push(
|
|
1818
|
+
createError(
|
|
1819
|
+
`${path}.asset._ref`,
|
|
1820
|
+
"TYPE_MISMATCH",
|
|
1821
|
+
'File asset missing "_ref" property',
|
|
1822
|
+
formatValue(asset._ref),
|
|
1823
|
+
"string (asset document ID)",
|
|
1824
|
+
'Provide a valid asset reference like { _type: "reference", _ref: "file-abc123-..." }'
|
|
1825
|
+
)
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
validateNoExtraProperties(fileObj, /* @__PURE__ */ new Set(["_type", "_key", "asset"]), path, "file", errors);
|
|
1829
|
+
}
|
|
1830
|
+
async function validateValue(value, fieldSchema, path, errors, context, schema, warnings) {
|
|
1831
|
+
const schemaType = fieldSchema.type;
|
|
1832
|
+
if (value != null)
|
|
1833
|
+
switch (validateSchemaRules(value, fieldSchema, path, errors, warnings), schemaType) {
|
|
1834
|
+
// Primitive types
|
|
1835
|
+
case "string":
|
|
1836
|
+
case "text":
|
|
1837
|
+
case "number":
|
|
1838
|
+
case "boolean":
|
|
1839
|
+
case "date":
|
|
1840
|
+
case "datetime":
|
|
1841
|
+
case "url":
|
|
1842
|
+
case "email":
|
|
1843
|
+
validatePrimitive(value, schemaType, path, errors, fieldSchema);
|
|
1844
|
+
break;
|
|
1845
|
+
// Special types
|
|
1846
|
+
case "slug":
|
|
1847
|
+
validateSlug(value, path, errors);
|
|
1848
|
+
break;
|
|
1849
|
+
case "geopoint":
|
|
1850
|
+
validateGeopoint(value, path, errors);
|
|
1851
|
+
break;
|
|
1852
|
+
case "image":
|
|
1853
|
+
validateImage(value, path, errors, fieldSchema, schema);
|
|
1854
|
+
break;
|
|
1855
|
+
case "file":
|
|
1856
|
+
validateFile(value, path, errors);
|
|
1857
|
+
break;
|
|
1858
|
+
case "reference":
|
|
1859
|
+
await validateReference(value, fieldSchema, path, errors, context);
|
|
1860
|
+
break;
|
|
1861
|
+
// Array type
|
|
1862
|
+
case "array":
|
|
1863
|
+
await validateArray(value, fieldSchema, path, schema, errors, context, validateValue);
|
|
1864
|
+
break;
|
|
1865
|
+
// Block type (Portable Text) - special validation
|
|
1866
|
+
case "block":
|
|
1867
|
+
await validateBlock(value, fieldSchema, path, schema, errors, context, validateValue);
|
|
1868
|
+
break;
|
|
1869
|
+
// Object/document types
|
|
1870
|
+
case "object":
|
|
1871
|
+
case "document":
|
|
1872
|
+
await validateObject(value, fieldSchema, path, schema, errors, context, validateValue);
|
|
1873
|
+
break;
|
|
1874
|
+
// Custom types - look up in schema
|
|
1875
|
+
default: {
|
|
1876
|
+
const customSchema = schema.find((s) => s.name === schemaType);
|
|
1877
|
+
if (customSchema) {
|
|
1878
|
+
const actualType = customSchema.type;
|
|
1879
|
+
actualType === "array" ? await validateArray(value, customSchema, path, schema, errors, context, validateValue) : actualType === "object" || actualType === "document" ? await validateObject(value, customSchema, path, schema, errors, context, validateValue) : actualType === "reference" ? await validateReference(value, customSchema, path, errors, context) : isPrimitiveType(actualType) ? validatePrimitive(value, actualType, path, errors) : await validateValue(value, customSchema, path, errors, context, schema);
|
|
1880
|
+
}
|
|
1881
|
+
break;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
export {
|
|
1886
|
+
createError,
|
|
1887
|
+
formatValidationErrors,
|
|
1888
|
+
formatValue,
|
|
1889
|
+
getActualJsType,
|
|
1890
|
+
getExpectedJsType,
|
|
1891
|
+
getFieldSchemaAtPath,
|
|
1892
|
+
getUniquenessKey,
|
|
1893
|
+
getValidImageMetadataFields,
|
|
1894
|
+
getValueAtPath,
|
|
1895
|
+
isInternationalizedArrayField,
|
|
1896
|
+
isV5InternationalizedArray,
|
|
1897
|
+
resolveFieldSchemaAtPath,
|
|
1898
|
+
validateArray,
|
|
1899
|
+
validateBlock,
|
|
1900
|
+
validateDocumentLanguageField,
|
|
1901
|
+
validateFieldLevelLanguageField,
|
|
1902
|
+
validateFieldLevelLanguageKey,
|
|
1903
|
+
validateFile,
|
|
1904
|
+
validateGeopoint,
|
|
1905
|
+
validateImage,
|
|
1906
|
+
validateImageMetadata,
|
|
1907
|
+
validateImagePath,
|
|
1908
|
+
validateInternationalizedArrayItems,
|
|
1909
|
+
validateInternationalizedArraySet,
|
|
1910
|
+
validateNoExtraProperties,
|
|
1911
|
+
validateObject,
|
|
1912
|
+
validatePrimitive,
|
|
1913
|
+
validateReference,
|
|
1914
|
+
validateSchemaRules,
|
|
1915
|
+
validateSlug,
|
|
1916
|
+
validateUniqueItems,
|
|
1917
|
+
validateValue
|
|
1918
|
+
};
|
|
1919
|
+
//# sourceMappingURL=index.js.map
|