@makehq/forman-schema 1.2.4 → 1.3.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/README.md +116 -1
- package/dist/index.cjs +514 -43
- package/dist/index.d.cts +56 -3
- package/dist/index.d.ts +56 -3
- package/dist/index.js +511 -42
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
toFormanSchema: () => toFormanSchema,
|
|
24
|
-
toJSONSchema: () => toJSONSchema
|
|
24
|
+
toJSONSchema: () => toJSONSchema,
|
|
25
|
+
validateForman: () => validateForman,
|
|
26
|
+
validateFormanWithDomains: () => validateFormanWithDomains
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
27
29
|
|
|
28
30
|
// src/utils.ts
|
|
31
|
+
var FORMAN_VISUAL_TYPES = ["banner", "markdown", "html", "separator"];
|
|
29
32
|
function noEmpty(text) {
|
|
30
33
|
return text?.trim() || void 0;
|
|
31
34
|
}
|
|
@@ -35,6 +38,35 @@ function isObject(value) {
|
|
|
35
38
|
function isOptionGroup(value) {
|
|
36
39
|
return "options" in value && Array.isArray(value.options);
|
|
37
40
|
}
|
|
41
|
+
function containsIMLExpression(value) {
|
|
42
|
+
if (typeof value !== "string") return false;
|
|
43
|
+
return value.indexOf("{{") > -1 && value.indexOf("}}") > -1;
|
|
44
|
+
}
|
|
45
|
+
var API_ENDPOINTS = {
|
|
46
|
+
account: "api://connections/{{kind}}",
|
|
47
|
+
aiagent: "api://ai-agents/v1/agents",
|
|
48
|
+
datastore: "api://data-stores",
|
|
49
|
+
hook: "api://hooks/{{kind}}",
|
|
50
|
+
keychain: "api://keys/{{kind}}",
|
|
51
|
+
udt: "api://data-structures"
|
|
52
|
+
};
|
|
53
|
+
function normalizeFormanFieldType(field) {
|
|
54
|
+
const [type, kind] = field.type.split(":");
|
|
55
|
+
if (!type) return field;
|
|
56
|
+
if (!(type in API_ENDPOINTS)) return field;
|
|
57
|
+
let store = isObject(field.options) ? field.options.store : field.options;
|
|
58
|
+
if (typeof store === "string" || Array.isArray(store)) return field;
|
|
59
|
+
store = API_ENDPOINTS[type];
|
|
60
|
+
store = store.replace("/{{kind}}", kind ? `/${kind}` : "");
|
|
61
|
+
return {
|
|
62
|
+
...field,
|
|
63
|
+
type,
|
|
64
|
+
options: {
|
|
65
|
+
...field.options,
|
|
66
|
+
store
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
38
70
|
|
|
39
71
|
// src/forman.ts
|
|
40
72
|
var SchemaConversionError = class extends Error {
|
|
@@ -49,17 +81,13 @@ var SchemaConversionError = class extends Error {
|
|
|
49
81
|
this.name = "SchemaConversionError";
|
|
50
82
|
}
|
|
51
83
|
};
|
|
52
|
-
var API_ENDPOINTS = {
|
|
53
|
-
CONNECTIONS: "api://connections",
|
|
54
|
-
HOOKS: "api://hooks",
|
|
55
|
-
KEYS: "api://keys"
|
|
56
|
-
};
|
|
57
84
|
var FORMAN_TYPE_MAP = {
|
|
58
85
|
account: "number",
|
|
59
86
|
hook: "number",
|
|
60
87
|
keychain: "number",
|
|
61
88
|
datastore: "number",
|
|
62
89
|
aiagent: "string",
|
|
90
|
+
udt: "number",
|
|
63
91
|
array: "array",
|
|
64
92
|
collection: "object",
|
|
65
93
|
text: "string",
|
|
@@ -98,40 +126,6 @@ function validateFormanField(field) {
|
|
|
98
126
|
throw new SchemaConversionError(`Unknown field type: ${field.type}`, field);
|
|
99
127
|
}
|
|
100
128
|
}
|
|
101
|
-
function normalizeFieldType(field) {
|
|
102
|
-
const typeHandlers = {
|
|
103
|
-
"account:": (type) => ({
|
|
104
|
-
...field,
|
|
105
|
-
type: "account",
|
|
106
|
-
options: {
|
|
107
|
-
...field.options,
|
|
108
|
-
store: `${API_ENDPOINTS.CONNECTIONS}/${type.substring(8)}`
|
|
109
|
-
}
|
|
110
|
-
}),
|
|
111
|
-
"hook:": (type) => ({
|
|
112
|
-
...field,
|
|
113
|
-
type: "hook",
|
|
114
|
-
options: {
|
|
115
|
-
...field.options,
|
|
116
|
-
store: `${API_ENDPOINTS.HOOKS}/${type.substring(5)}`
|
|
117
|
-
}
|
|
118
|
-
}),
|
|
119
|
-
"keychain:": (type) => ({
|
|
120
|
-
...field,
|
|
121
|
-
type: "keychain",
|
|
122
|
-
options: {
|
|
123
|
-
...field.options,
|
|
124
|
-
store: `${API_ENDPOINTS.KEYS}/${type.substring(9)}`
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
};
|
|
128
|
-
for (const [prefix, handler] of Object.entries(typeHandlers)) {
|
|
129
|
-
if (field.type.startsWith(prefix)) {
|
|
130
|
-
return handler(field.type);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return field;
|
|
134
|
-
}
|
|
135
129
|
function appendQueryString(path, domain, tail) {
|
|
136
130
|
if (path.startsWith("api://")) return path;
|
|
137
131
|
const queryString = tail.map((part) => `${encodeURIComponent(part)}={{${part}}}`).join("&");
|
|
@@ -152,7 +146,7 @@ function createDefaultContext() {
|
|
|
152
146
|
}
|
|
153
147
|
function toJSONSchemaInternal(field, context = createDefaultContext()) {
|
|
154
148
|
validateFormanField(field);
|
|
155
|
-
const normalizedField =
|
|
149
|
+
const normalizedField = normalizeFormanFieldType(field);
|
|
156
150
|
const result = {
|
|
157
151
|
type: FORMAN_TYPE_MAP[normalizedField.type],
|
|
158
152
|
title: noEmpty(normalizedField.label),
|
|
@@ -170,6 +164,7 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
|
|
|
170
164
|
case "datastore":
|
|
171
165
|
case "aiagent":
|
|
172
166
|
case "file":
|
|
167
|
+
case "folder":
|
|
173
168
|
return handleSelectType(normalizedField, result, context);
|
|
174
169
|
default:
|
|
175
170
|
return handlePrimitiveType(normalizedField, result);
|
|
@@ -181,6 +176,9 @@ function handleCollectionType(field, result, context) {
|
|
|
181
176
|
required: []
|
|
182
177
|
});
|
|
183
178
|
function addField(subField, tail) {
|
|
179
|
+
if (FORMAN_VISUAL_TYPES.includes(subField.type)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
184
182
|
if (!subField.name) return;
|
|
185
183
|
if (subField.required) {
|
|
186
184
|
result.required.push(subField.name);
|
|
@@ -329,7 +327,7 @@ function handlePrimitiveType(field, result) {
|
|
|
329
327
|
}
|
|
330
328
|
if (field.validate) {
|
|
331
329
|
if (field.validate.pattern) {
|
|
332
|
-
result.pattern = field.validate.pattern;
|
|
330
|
+
result.pattern = typeof field.validate.pattern === "object" ? field.validate.pattern.regexp : field.validate.pattern;
|
|
333
331
|
}
|
|
334
332
|
if (field.validate.min !== void 0) {
|
|
335
333
|
result.minimum = field.validate.min;
|
|
@@ -344,6 +342,471 @@ function handlePrimitiveType(field, result) {
|
|
|
344
342
|
return result;
|
|
345
343
|
}
|
|
346
344
|
|
|
345
|
+
// src/validator.ts
|
|
346
|
+
var FORMAN_TYPE_MAP2 = {
|
|
347
|
+
account: "number",
|
|
348
|
+
hook: "number",
|
|
349
|
+
keychain: "number",
|
|
350
|
+
datastore: "number",
|
|
351
|
+
aiagent: "string",
|
|
352
|
+
udt: "number",
|
|
353
|
+
array: "array",
|
|
354
|
+
collection: "object",
|
|
355
|
+
text: "string",
|
|
356
|
+
number: "number",
|
|
357
|
+
boolean: "boolean",
|
|
358
|
+
date: "string",
|
|
359
|
+
json: "string",
|
|
360
|
+
buffer: "string",
|
|
361
|
+
cert: "string",
|
|
362
|
+
color: "string",
|
|
363
|
+
email: "string",
|
|
364
|
+
filename: "string",
|
|
365
|
+
file: "string",
|
|
366
|
+
folder: "string",
|
|
367
|
+
hidden: void 0,
|
|
368
|
+
integer: "number",
|
|
369
|
+
uinteger: "number",
|
|
370
|
+
password: "string",
|
|
371
|
+
path: "string",
|
|
372
|
+
pkey: "string",
|
|
373
|
+
port: "number",
|
|
374
|
+
select: void 0,
|
|
375
|
+
time: "string",
|
|
376
|
+
timestamp: "string",
|
|
377
|
+
timezone: "string",
|
|
378
|
+
upload: "array",
|
|
379
|
+
url: "string",
|
|
380
|
+
uuid: "string",
|
|
381
|
+
any: void 0
|
|
382
|
+
};
|
|
383
|
+
async function validateFormanWithDomainsInternal(domains, options) {
|
|
384
|
+
const errors = [];
|
|
385
|
+
const roots = Object.keys(domains).reduce(
|
|
386
|
+
(acc, domain) => {
|
|
387
|
+
acc[domain] = {
|
|
388
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
389
|
+
validateFields: (fields, context) => {
|
|
390
|
+
return validateFormanValue(
|
|
391
|
+
domains[domain].values,
|
|
392
|
+
{
|
|
393
|
+
name: domain,
|
|
394
|
+
type: "collection",
|
|
395
|
+
spec: fields
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
...context,
|
|
399
|
+
path: [],
|
|
400
|
+
domain
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
return acc;
|
|
406
|
+
},
|
|
407
|
+
{}
|
|
408
|
+
);
|
|
409
|
+
for (const domain of Object.keys(domains)) {
|
|
410
|
+
if (!domains[domain]) continue;
|
|
411
|
+
const result = await validateFormanValue(
|
|
412
|
+
domains[domain].values,
|
|
413
|
+
{
|
|
414
|
+
name: domain,
|
|
415
|
+
type: "collection",
|
|
416
|
+
spec: domains[domain].schema || []
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
roots,
|
|
420
|
+
domain,
|
|
421
|
+
path: [],
|
|
422
|
+
tail: [],
|
|
423
|
+
strict: options?.strict === true,
|
|
424
|
+
validateNestedFields: () => {
|
|
425
|
+
throw new Error("Cannot validate nested fields without parent field.");
|
|
426
|
+
},
|
|
427
|
+
resolveRemote: async (path, context) => {
|
|
428
|
+
if (!options?.resolveRemote) {
|
|
429
|
+
throw new Error("Remote resource not supported when resolver is not provided.");
|
|
430
|
+
}
|
|
431
|
+
const data = context.tail.reduce(
|
|
432
|
+
(acc, curr) => {
|
|
433
|
+
acc[curr.name] = curr.value;
|
|
434
|
+
return acc;
|
|
435
|
+
},
|
|
436
|
+
{}
|
|
437
|
+
);
|
|
438
|
+
return await options.resolveRemote(path, data);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
errors.push(...result.errors);
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
valid: errors.length === 0,
|
|
446
|
+
errors
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
async function validateFormanValue(value, field, context) {
|
|
450
|
+
if (FORMAN_VISUAL_TYPES.includes(field.type)) {
|
|
451
|
+
return {
|
|
452
|
+
valid: true,
|
|
453
|
+
errors: []
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
const normalizedField = normalizeFormanFieldType(field);
|
|
457
|
+
if (normalizedField.required && (value == null || value === "")) {
|
|
458
|
+
return {
|
|
459
|
+
valid: false,
|
|
460
|
+
errors: [
|
|
461
|
+
{
|
|
462
|
+
domain: context.domain,
|
|
463
|
+
path: context.path.join("."),
|
|
464
|
+
message: "Field is mandatory."
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
if (value == null) {
|
|
470
|
+
return {
|
|
471
|
+
valid: true,
|
|
472
|
+
errors: []
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const expectedType = FORMAN_TYPE_MAP2[normalizedField.type];
|
|
476
|
+
let actualType = typeof value;
|
|
477
|
+
if (actualType === "object" && Array.isArray(value)) actualType = "array";
|
|
478
|
+
if (expectedType && expectedType !== actualType) {
|
|
479
|
+
return {
|
|
480
|
+
valid: false,
|
|
481
|
+
errors: [
|
|
482
|
+
{
|
|
483
|
+
domain: context.domain,
|
|
484
|
+
path: context.path.join("."),
|
|
485
|
+
message: `Expected type '${expectedType}', got type '${actualType}'.`
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
switch (normalizedField.type) {
|
|
491
|
+
case "collection":
|
|
492
|
+
return handleCollectionType2(value, normalizedField, context);
|
|
493
|
+
case "array":
|
|
494
|
+
return handleArrayType2(value, normalizedField, context);
|
|
495
|
+
case "select":
|
|
496
|
+
case "account":
|
|
497
|
+
case "hook":
|
|
498
|
+
case "keychain":
|
|
499
|
+
case "datastore":
|
|
500
|
+
case "aiagent":
|
|
501
|
+
case "file":
|
|
502
|
+
case "folder":
|
|
503
|
+
return handleSelectType2(value, normalizedField, context);
|
|
504
|
+
default:
|
|
505
|
+
return handlePrimitiveType2(value, normalizedField, context);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async function handleCollectionType2(value, field, context) {
|
|
509
|
+
const errors = [];
|
|
510
|
+
const seen = context.path.length === 0 ? context.roots[context.domain].seenFields : /* @__PURE__ */ new Set();
|
|
511
|
+
const path = context.path;
|
|
512
|
+
if (Array.isArray(field.spec)) {
|
|
513
|
+
for (const subField of field.spec) {
|
|
514
|
+
if (FORMAN_VISUAL_TYPES.includes(subField.type)) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (!subField.name) {
|
|
518
|
+
errors.push({
|
|
519
|
+
domain: context.domain,
|
|
520
|
+
path: context.path.join("."),
|
|
521
|
+
message: "Object contains field with unknown name."
|
|
522
|
+
});
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (context.strict && !seen.has(subField.name)) seen.add(subField.name);
|
|
526
|
+
const result = await validateFormanValue(value[subField.name], subField, {
|
|
527
|
+
...context,
|
|
528
|
+
path: [...path, subField.name],
|
|
529
|
+
validateNestedFields: async (fields, context2) => {
|
|
530
|
+
for (const subField2 of fields) {
|
|
531
|
+
if (FORMAN_VISUAL_TYPES.includes(subField2.type)) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (!subField2.name) {
|
|
535
|
+
errors.push({
|
|
536
|
+
domain: context2.domain,
|
|
537
|
+
path: context2.path.join("."),
|
|
538
|
+
message: "Object contains field with unknown name."
|
|
539
|
+
});
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (context2.strict && !seen.has(subField2.name)) seen.add(subField2.name);
|
|
543
|
+
const result2 = await validateFormanValue(value[subField2.name], subField2, {
|
|
544
|
+
...context2,
|
|
545
|
+
path: [...path, subField2.name]
|
|
546
|
+
});
|
|
547
|
+
errors.push(...result2.errors);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
errors.push(...result.errors);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (context.strict) {
|
|
555
|
+
for (const key of Object.keys(value)) {
|
|
556
|
+
if (!seen.has(key)) {
|
|
557
|
+
seen.add(key);
|
|
558
|
+
errors.push({
|
|
559
|
+
domain: context.domain,
|
|
560
|
+
path: context.path.join("."),
|
|
561
|
+
message: `Unknown field '${key}'.`
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
valid: errors.length === 0,
|
|
568
|
+
errors
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
async function handleArrayType2(value, field, context) {
|
|
572
|
+
const errors = [];
|
|
573
|
+
if (field.spec) {
|
|
574
|
+
for (const [index, item] of value.entries()) {
|
|
575
|
+
const result = await validateFormanValue(
|
|
576
|
+
item,
|
|
577
|
+
Array.isArray(field.spec) ? { name: index.toString(), type: "collection", spec: field.spec } : Object.assign({}, field.spec, { name: index.toString() }),
|
|
578
|
+
{ ...context, path: [...context.path, index.toString()] }
|
|
579
|
+
);
|
|
580
|
+
errors.push(...result.errors);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (field.validate) {
|
|
584
|
+
if (field.validate.minItems !== void 0 && value.length < field.validate.minItems) {
|
|
585
|
+
errors.push({
|
|
586
|
+
domain: context.domain,
|
|
587
|
+
path: context.path.join("."),
|
|
588
|
+
message: `Array has less than ${field.validate.minItems} items.`
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (field.validate.maxItems !== void 0 && value.length > field.validate.maxItems) {
|
|
592
|
+
errors.push({
|
|
593
|
+
domain: context.domain,
|
|
594
|
+
path: context.path.join("."),
|
|
595
|
+
message: `Array has more than ${field.validate.maxItems} items.`
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
valid: errors.length === 0,
|
|
601
|
+
errors
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
async function handleSelectType2(value, field, context) {
|
|
605
|
+
const errors = [];
|
|
606
|
+
let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
|
|
607
|
+
let nested = isObject(field.options) ? field.options.nested : void 0;
|
|
608
|
+
if (typeof optionsOrGroups === "string") {
|
|
609
|
+
try {
|
|
610
|
+
optionsOrGroups = await context.resolveRemote(optionsOrGroups, context);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
return {
|
|
613
|
+
valid: false,
|
|
614
|
+
errors: [
|
|
615
|
+
...errors,
|
|
616
|
+
{
|
|
617
|
+
domain: context.domain,
|
|
618
|
+
path: context.path.join("."),
|
|
619
|
+
message: `Failed to resolve remote resource ${optionsOrGroups}: ${error}`
|
|
620
|
+
}
|
|
621
|
+
]
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (field.multiple) {
|
|
626
|
+
if (!Array.isArray(value)) {
|
|
627
|
+
return {
|
|
628
|
+
valid: false,
|
|
629
|
+
errors: [
|
|
630
|
+
...errors,
|
|
631
|
+
{
|
|
632
|
+
domain: context.domain,
|
|
633
|
+
path: context.path.join("."),
|
|
634
|
+
message: `Value is not an array.`
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
for (const singleValue of value) {
|
|
640
|
+
const found = field.grouped ? optionsOrGroups.some(
|
|
641
|
+
(group) => group.options.some((option) => option.value === singleValue)
|
|
642
|
+
) : optionsOrGroups.some((option) => option.value === singleValue);
|
|
643
|
+
if (!found) {
|
|
644
|
+
errors.push({
|
|
645
|
+
domain: context.domain,
|
|
646
|
+
path: context.path.join("."),
|
|
647
|
+
message: `Value '${singleValue}' not found in options.`
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (field.validate) {
|
|
652
|
+
if (field.validate.minItems !== void 0 && value.length < field.validate.minItems) {
|
|
653
|
+
errors.push({
|
|
654
|
+
domain: context.domain,
|
|
655
|
+
path: context.path.join("."),
|
|
656
|
+
message: `Selected less than ${field.validate.minItems} items.`
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
if (field.validate.maxItems !== void 0 && value.length > field.validate.maxItems) {
|
|
660
|
+
errors.push({
|
|
661
|
+
domain: context.domain,
|
|
662
|
+
path: context.path.join("."),
|
|
663
|
+
message: `Selected more than ${field.validate.maxItems} items.`
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
} else {
|
|
668
|
+
const item = field.grouped ? optionsOrGroups.find((group) => group.options.some((option) => option.value === value))?.options.find((option) => option.value === value) : optionsOrGroups.find((option) => option.value === value);
|
|
669
|
+
if (!item) {
|
|
670
|
+
return {
|
|
671
|
+
valid: false,
|
|
672
|
+
errors: [
|
|
673
|
+
...errors,
|
|
674
|
+
{
|
|
675
|
+
domain: context.domain,
|
|
676
|
+
path: context.path.join("."),
|
|
677
|
+
message: `Value '${value}' not found in options.`
|
|
678
|
+
}
|
|
679
|
+
]
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
if (item.nested) nested = item.nested;
|
|
683
|
+
}
|
|
684
|
+
if (nested) {
|
|
685
|
+
const result = await handleNestedFields(nested, value, field, context);
|
|
686
|
+
errors.push(...result.errors);
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
valid: errors.length === 0,
|
|
690
|
+
errors
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
async function handleNestedFields(nested, value, field, context) {
|
|
694
|
+
const errors = [];
|
|
695
|
+
let store = isObject(nested) ? nested.store : nested;
|
|
696
|
+
const domain = isObject(nested) && nested.domain ? nested.domain : void 0;
|
|
697
|
+
context = {
|
|
698
|
+
...context,
|
|
699
|
+
tail: field.name ? [...context.tail, { name: field.name, value }] : context.tail
|
|
700
|
+
};
|
|
701
|
+
if (typeof store === "string") {
|
|
702
|
+
try {
|
|
703
|
+
store = await context.resolveRemote(store, context);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
return {
|
|
706
|
+
valid: false,
|
|
707
|
+
errors: [
|
|
708
|
+
...errors,
|
|
709
|
+
{
|
|
710
|
+
domain: context.domain,
|
|
711
|
+
path: context.path.join("."),
|
|
712
|
+
message: `Failed to resolve remote resource ${store}: ${error}`
|
|
713
|
+
}
|
|
714
|
+
]
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (store && domain && domain !== context.domain) {
|
|
719
|
+
if (!context.roots[domain]) {
|
|
720
|
+
errors.push({
|
|
721
|
+
domain: context.domain,
|
|
722
|
+
path: context.path.join("."),
|
|
723
|
+
message: `Unable to process nested fields: Domain '${domain}' not found.`
|
|
724
|
+
});
|
|
725
|
+
} else {
|
|
726
|
+
const result = await context.roots[domain].validateFields(store, context);
|
|
727
|
+
errors.push(...result.errors);
|
|
728
|
+
}
|
|
729
|
+
} else if (store) {
|
|
730
|
+
await context.validateNestedFields(store, context);
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
valid: errors.length === 0,
|
|
734
|
+
errors
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
async function handlePrimitiveType2(value, field, context) {
|
|
738
|
+
const errors = [];
|
|
739
|
+
if (containsIMLExpression(value)) {
|
|
740
|
+
return {
|
|
741
|
+
valid: true,
|
|
742
|
+
errors
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
if (errors.length > 0) {
|
|
746
|
+
return {
|
|
747
|
+
valid: false,
|
|
748
|
+
errors
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
if (field.validate) {
|
|
752
|
+
if (typeof value === "string") {
|
|
753
|
+
if (field.validate.pattern && !new RegExp(
|
|
754
|
+
typeof field.validate.pattern === "object" ? field.validate.pattern.regexp : field.validate.pattern
|
|
755
|
+
).test(value)) {
|
|
756
|
+
errors.push({
|
|
757
|
+
domain: context.domain,
|
|
758
|
+
path: context.path.join("."),
|
|
759
|
+
message: `Value doesn't match the pattern: ${typeof field.validate.pattern === "object" ? field.validate.pattern.regexp : field.validate.pattern}`
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
if (field.validate.min !== void 0 && value.length < field.validate.min) {
|
|
763
|
+
errors.push({
|
|
764
|
+
domain: context.domain,
|
|
765
|
+
path: context.path.join("."),
|
|
766
|
+
message: `Value must be at least ${field.validate.min} characters long.`
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
if (field.validate.max !== void 0 && value.length > field.validate.max) {
|
|
770
|
+
errors.push({
|
|
771
|
+
domain: context.domain,
|
|
772
|
+
path: context.path.join("."),
|
|
773
|
+
message: `Value exceeded maximum length of ${field.validate.max} characters.`
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
if (field.validate.enum && !field.validate.enum.includes(value)) {
|
|
777
|
+
errors.push({
|
|
778
|
+
domain: context.domain,
|
|
779
|
+
path: context.path.join("."),
|
|
780
|
+
message: "Value must be one of the following: " + field.validate.enum.join(", ")
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
} else if (typeof value === "number") {
|
|
784
|
+
if (field.validate.min !== void 0 && value < field.validate.min) {
|
|
785
|
+
errors.push({
|
|
786
|
+
domain: context.domain,
|
|
787
|
+
path: context.path.join("."),
|
|
788
|
+
message: `Value is too small. Minimum value is ${field.validate.min}.`
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (field.validate.max !== void 0 && value > field.validate.max) {
|
|
792
|
+
errors.push({
|
|
793
|
+
domain: context.domain,
|
|
794
|
+
path: context.path.join("."),
|
|
795
|
+
message: `Value is too big. Maximum value is ${field.validate.max}.`
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (field.nested) {
|
|
801
|
+
const result = await handleNestedFields(field.nested, value, field, context);
|
|
802
|
+
errors.push(...result.errors);
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
valid: errors.length === 0,
|
|
806
|
+
errors
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
347
810
|
// src/json.ts
|
|
348
811
|
var JSON_PRIMITIVE_TYPE_MAP = {
|
|
349
812
|
string: "text",
|
|
@@ -433,8 +896,16 @@ function toFormanSchema(field) {
|
|
|
433
896
|
function toJSONSchema(field) {
|
|
434
897
|
return toJSONSchemaInternal(field);
|
|
435
898
|
}
|
|
899
|
+
function validateFormanWithDomains(domains, options) {
|
|
900
|
+
return validateFormanWithDomainsInternal(domains, options);
|
|
901
|
+
}
|
|
902
|
+
function validateForman(values, schema, options) {
|
|
903
|
+
return validateFormanWithDomains({ default: { values, schema } }, options);
|
|
904
|
+
}
|
|
436
905
|
// Annotate the CommonJS export names for ESM import in node:
|
|
437
906
|
0 && (module.exports = {
|
|
438
907
|
toFormanSchema,
|
|
439
|
-
toJSONSchema
|
|
908
|
+
toJSONSchema,
|
|
909
|
+
validateForman,
|
|
910
|
+
validateFormanWithDomains
|
|
440
911
|
});
|