@makehq/forman-schema 1.13.2 → 1.15.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 +77 -1
- package/dist/index.cjs +116 -27
- package/dist/index.d.cts +77 -3
- package/dist/index.d.ts +77 -3
- package/dist/index.js +115 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Conversion and validation utilities for Forman Schema.
|
|
4
4
|
|
|
5
|
+
## v1.14.0 — advanced field tracking
|
|
6
|
+
|
|
7
|
+
Non-breaking minor release. New surface for working with `advanced: true` Forman fields:
|
|
8
|
+
|
|
9
|
+
- `toJSONSchema(field, options?)` still returns a bare `JSONSchema7` — fully backward-compatible.
|
|
10
|
+
- Fields marked `advanced: true` are now stamped with `x-advanced: true` on the JSON Schema output, and round-trip through `toFormanSchema` (which restores `advanced: true`).
|
|
11
|
+
- New option `excludeAdvancedFields?: boolean` (default `false`). When `true`, advanced sub-fields of a collection are omitted from the schema.
|
|
12
|
+
- New function `toJSONSchemaAdvanced(field, options?)` returns `{ schema: JSONSchema7, skippedPaths?: { advanced?: string[] } }`. Use it to learn which advanced fields were dropped (e.g. to render a "show advanced" toggle). `toJSONSchema` delegates to it internally and returns just `.schema`.
|
|
13
|
+
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
7
16
|
```bash
|
|
@@ -33,6 +42,23 @@ const formanField = {
|
|
|
33
42
|
const jsonSchema = toJSONSchema(formanField);
|
|
34
43
|
```
|
|
35
44
|
|
|
45
|
+
Advanced fields (`advanced: true`) are included by default and stamped with `x-advanced: true`. To omit them from the rendered schema, pass `{ excludeAdvancedFields: true }`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const jsonSchema = toJSONSchema(formanField, { excludeAdvancedFields: true });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If you also need to know **which** advanced fields were dropped (e.g. to render a "show advanced" toggle), use `toJSONSchemaAdvanced`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { toJSONSchemaAdvanced } from '@makehq/forman-schema';
|
|
55
|
+
|
|
56
|
+
const { schema, skippedPaths } = toJSONSchemaAdvanced(formanField, { excludeAdvancedFields: true });
|
|
57
|
+
// skippedPaths?.advanced is an array of dot-notation paths like ['wrapper.field', 'wrapper.arr[].nested']
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The filter applies to **sub-fields of a collection** — including nested-by-option fields, array-of-collection items, composite expansions (`udtspec`, `udttype`), and cross-domain buffered fields. It does **not** apply to: the top-level field passed in (always converted), or the item type of an array whose `spec` is a single primitive field. To hide an entire array or any other top-level structure, mark the _parent_ field as `advanced: true`.
|
|
61
|
+
|
|
36
62
|
### Converting from JSON Schema to Forman Schema
|
|
37
63
|
|
|
38
64
|
```typescript
|
|
@@ -54,6 +80,56 @@ const jsonSchemaField = {
|
|
|
54
80
|
const formanSchema = toFormanSchema(jsonSchemaField);
|
|
55
81
|
```
|
|
56
82
|
|
|
83
|
+
### JSON fields (`type: 'json'`)
|
|
84
|
+
|
|
85
|
+
A `json` field can carry an explicit `schema` (a JSON Schema). This lets you author complex parts of a form directly in JSON Schema and mix them with primitive Forman fields:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const formanField = {
|
|
89
|
+
type: 'collection',
|
|
90
|
+
spec: [
|
|
91
|
+
{ name: 'title', type: 'text' },
|
|
92
|
+
{
|
|
93
|
+
name: 'input',
|
|
94
|
+
type: 'json',
|
|
95
|
+
schema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
name: { type: 'string' },
|
|
99
|
+
age: { type: 'number' },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
On conversion, the `schema` is **echoed verbatim** into the JSON Schema output (the field's `label`/`help` fill in `title`/`description` only when the schema omits them). An enumerable `x-json` marker is added so `toFormanSchema` can recover the `json` type; it survives JSON serialization. A `json` field **without** a `schema` renders as a plain object schema (`{ type: 'object' }`), since a JSON value is most naturally an object.
|
|
108
|
+
|
|
109
|
+
#### External validators
|
|
110
|
+
|
|
111
|
+
The library cannot validate a JSON value against an arbitrary JSON Schema on its own — it has **no JSON Schema validator built in**. Validation of `json` fields is therefore opt-in: **a `json` value is not validated unless you provide a `validateJson` callback.** Without it, the value passes through untouched.
|
|
112
|
+
|
|
113
|
+
This is the first of a general **external validator** concept: a callback that performs validation the library can't, and returns a `FormanExternalValidationResult` verdict (`{ valid, errors?, warnings? }`) that is spliced into the overall result. The callback may be async (awaited), and its `errors`/`warnings` are stamped with the field's domain and path automatically. A `valid: false` verdict always fails validation, even when it carries no messages.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { validateForman, type FormanExternalValidationResult } from '@makehq/forman-schema';
|
|
117
|
+
import Ajv from 'ajv'; // any JSON Schema validator works
|
|
118
|
+
|
|
119
|
+
const ajv = new Ajv({ allErrors: true });
|
|
120
|
+
|
|
121
|
+
const result = await validateForman({ input: { name: 'Alice', age: 30 } }, schema, {
|
|
122
|
+
async validateJson(schema, value): Promise<FormanExternalValidationResult> {
|
|
123
|
+
const validate = ajv.compile(schema);
|
|
124
|
+
if (validate(value)) return { valid: true };
|
|
125
|
+
return {
|
|
126
|
+
valid: false,
|
|
127
|
+
errors: (validate.errors ?? []).map(e => `${e.instancePath} ${e.message}`),
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
57
133
|
### Validation
|
|
58
134
|
|
|
59
135
|
Validate Forman values against a Forman Schema. Two entry points are available:
|
|
@@ -191,7 +267,7 @@ const result = await validateFormanWithDomains(
|
|
|
191
267
|
- hidden → string
|
|
192
268
|
- hook → number
|
|
193
269
|
- integer → number
|
|
194
|
-
- json →
|
|
270
|
+
- json → object (or its `schema` echoed verbatim when provided — see [JSON fields](#json-fields-type-json))
|
|
195
271
|
- keychain → number
|
|
196
272
|
- number → number
|
|
197
273
|
- path → string
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
toFormanSchema: () => toFormanSchema,
|
|
24
24
|
toJSONSchema: () => toJSONSchema,
|
|
25
|
+
toJSONSchemaAdvanced: () => toJSONSchemaAdvanced,
|
|
25
26
|
validateForman: () => validateForman,
|
|
26
27
|
validateFormanWithDomains: () => validateFormanWithDomains
|
|
27
28
|
});
|
|
@@ -560,7 +561,7 @@ var FORMAN_TYPE_MAP = {
|
|
|
560
561
|
boolean: "boolean",
|
|
561
562
|
checkbox: "boolean",
|
|
562
563
|
date: "string",
|
|
563
|
-
json: "
|
|
564
|
+
json: "object",
|
|
564
565
|
buffer: "string",
|
|
565
566
|
cert: "string",
|
|
566
567
|
color: "string",
|
|
@@ -607,13 +608,15 @@ function appendQueryString(path, domain, tail) {
|
|
|
607
608
|
const separator = path.includes("?") ? "&" : "?";
|
|
608
609
|
return `${path}${separator}${queryString}`;
|
|
609
610
|
}
|
|
610
|
-
function createDefaultContext() {
|
|
611
|
+
function createDefaultContext(options) {
|
|
611
612
|
return {
|
|
612
613
|
domain: "default",
|
|
613
614
|
tail: [],
|
|
614
615
|
path: [],
|
|
615
616
|
roots: {},
|
|
616
617
|
definitions: {},
|
|
618
|
+
excludeAdvancedFields: options?.excludeAdvancedFields ?? false,
|
|
619
|
+
skippedPaths: {},
|
|
617
620
|
addConditionalFields: () => {
|
|
618
621
|
throw new SchemaConversionError("Cannot serialize nested fields without parent field.");
|
|
619
622
|
}
|
|
@@ -668,6 +671,8 @@ function toJSONSchemaInternal(field, context) {
|
|
|
668
671
|
case "collection":
|
|
669
672
|
case "dynamicCollection":
|
|
670
673
|
return handleCollectionType(normalizedField, result, context);
|
|
674
|
+
case "json":
|
|
675
|
+
return handleJsonType(normalizedField, result);
|
|
671
676
|
case "array":
|
|
672
677
|
case "filestorage":
|
|
673
678
|
return handleArrayType(normalizedField, result, context);
|
|
@@ -695,6 +700,7 @@ function handleCollectionType(field, result, context) {
|
|
|
695
700
|
properties: {},
|
|
696
701
|
required: []
|
|
697
702
|
});
|
|
703
|
+
const collectionPath = field.name ? [...context.path, field.name] : context.path;
|
|
698
704
|
function addField(subField, tail) {
|
|
699
705
|
if (typeof subField === "string") {
|
|
700
706
|
const value = { $ref: appendQueryString(subField, context.domain, tail || context.tail) };
|
|
@@ -707,28 +713,41 @@ function handleCollectionType(field, result, context) {
|
|
|
707
713
|
}
|
|
708
714
|
if (!subField.name) return;
|
|
709
715
|
if (result.properties && Object.hasOwn(result.properties, subField.name)) return;
|
|
716
|
+
if (subField.advanced === true && context.excludeAdvancedFields) {
|
|
717
|
+
(context.skippedPaths.advanced ||= []).push([...collectionPath, subField.name].join("."));
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
710
720
|
if (subField.required) {
|
|
711
721
|
result.required.push(subField.name);
|
|
712
722
|
}
|
|
723
|
+
const subSchema = toJSONSchemaInternal(subField, {
|
|
724
|
+
...context,
|
|
725
|
+
domain: field["x-domain-root"] || context.domain,
|
|
726
|
+
tail: tail || context.tail,
|
|
727
|
+
path: collectionPath,
|
|
728
|
+
addConditionalFields: (name, value, nested) => {
|
|
729
|
+
result.allOf ||= [];
|
|
730
|
+
result.allOf.push({
|
|
731
|
+
if: {
|
|
732
|
+
properties: {
|
|
733
|
+
[name]: { const: value }
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
then: typeof nested === "string" ? { $ref: nested } : nested
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
if (subField.advanced) {
|
|
741
|
+
Object.defineProperty(subSchema, "x-advanced", {
|
|
742
|
+
configurable: true,
|
|
743
|
+
enumerable: true,
|
|
744
|
+
writable: true,
|
|
745
|
+
value: true
|
|
746
|
+
});
|
|
747
|
+
}
|
|
713
748
|
Object.defineProperty(result.properties, subField.name, {
|
|
714
749
|
enumerable: true,
|
|
715
|
-
value:
|
|
716
|
-
...context,
|
|
717
|
-
domain: field["x-domain-root"] || context.domain,
|
|
718
|
-
tail: tail || context.tail,
|
|
719
|
-
path: [...context.path, field.name],
|
|
720
|
-
addConditionalFields: (name, value, nested) => {
|
|
721
|
-
result.allOf ||= [];
|
|
722
|
-
result.allOf.push({
|
|
723
|
-
if: {
|
|
724
|
-
properties: {
|
|
725
|
-
[name]: { const: value }
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
then: typeof nested === "string" ? { $ref: nested } : nested
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
})
|
|
750
|
+
value: subSchema
|
|
732
751
|
});
|
|
733
752
|
}
|
|
734
753
|
if (field["x-domain-root"]) {
|
|
@@ -1010,6 +1029,20 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
1010
1029
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
1011
1030
|
return result;
|
|
1012
1031
|
}
|
|
1032
|
+
function handleJsonType(field, result) {
|
|
1033
|
+
if (!isObject(field.schema)) {
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
delete result.type;
|
|
1037
|
+
Object.assign(result, field.schema);
|
|
1038
|
+
Object.defineProperty(result, "x-json", {
|
|
1039
|
+
configurable: true,
|
|
1040
|
+
enumerable: true,
|
|
1041
|
+
writable: true,
|
|
1042
|
+
value: true
|
|
1043
|
+
});
|
|
1044
|
+
return result;
|
|
1045
|
+
}
|
|
1013
1046
|
function handlePrimitiveType(field, result, context) {
|
|
1014
1047
|
if (field.default !== "" && field.default != null) {
|
|
1015
1048
|
result.default = field.default;
|
|
@@ -1129,7 +1162,8 @@ var SCHEMA_STRIP_KEYS = [
|
|
|
1129
1162
|
"tags",
|
|
1130
1163
|
"extension",
|
|
1131
1164
|
"codepage",
|
|
1132
|
-
"logic"
|
|
1165
|
+
"logic",
|
|
1166
|
+
"schema"
|
|
1133
1167
|
];
|
|
1134
1168
|
function clampFieldForSchema(field) {
|
|
1135
1169
|
const clamped = { ...field };
|
|
@@ -1174,7 +1208,7 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
1174
1208
|
boolean: "boolean",
|
|
1175
1209
|
checkbox: "boolean",
|
|
1176
1210
|
date: "string",
|
|
1177
|
-
json:
|
|
1211
|
+
json: void 0,
|
|
1178
1212
|
buffer: "string",
|
|
1179
1213
|
cert: "string",
|
|
1180
1214
|
color: "string",
|
|
@@ -1269,7 +1303,8 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1269
1303
|
...data,
|
|
1270
1304
|
...localData
|
|
1271
1305
|
});
|
|
1272
|
-
}
|
|
1306
|
+
},
|
|
1307
|
+
validateJson: options?.validateJson
|
|
1273
1308
|
}
|
|
1274
1309
|
);
|
|
1275
1310
|
errors.push(...result.errors);
|
|
@@ -1401,6 +1436,8 @@ async function validateFormanValue(value, field, context) {
|
|
|
1401
1436
|
return validateFormanValue(value, udtspecExpand({ ...normalizedField }), context);
|
|
1402
1437
|
}
|
|
1403
1438
|
switch (normalizedField.type) {
|
|
1439
|
+
case "json":
|
|
1440
|
+
return handleJsonType2(value, normalizedField, context);
|
|
1404
1441
|
case "collection":
|
|
1405
1442
|
return handleCollectionType2(value, normalizedField, context);
|
|
1406
1443
|
case "array":
|
|
@@ -2001,6 +2038,35 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
2001
2038
|
warnings
|
|
2002
2039
|
};
|
|
2003
2040
|
}
|
|
2041
|
+
async function handleJsonType2(value, field, context) {
|
|
2042
|
+
const errors = [];
|
|
2043
|
+
const warnings = [];
|
|
2044
|
+
if (!isObject(field.schema) || !context.validateJson) {
|
|
2045
|
+
return { valid: true, errors, warnings };
|
|
2046
|
+
}
|
|
2047
|
+
const path = context.path.join(".");
|
|
2048
|
+
let fragment;
|
|
2049
|
+
try {
|
|
2050
|
+
fragment = await context.validateJson(field.schema, value);
|
|
2051
|
+
} catch (err) {
|
|
2052
|
+
const message = err instanceof Error ? err.message : "JSON schema validation failed unexpectedly.";
|
|
2053
|
+
return { valid: false, errors: [{ domain: context.domain, path, message }], warnings };
|
|
2054
|
+
}
|
|
2055
|
+
for (const message of fragment.errors ?? []) {
|
|
2056
|
+
errors.push({ domain: context.domain, path, message });
|
|
2057
|
+
}
|
|
2058
|
+
for (const message of fragment.warnings ?? []) {
|
|
2059
|
+
warnings.push({ domain: context.domain, path, message });
|
|
2060
|
+
}
|
|
2061
|
+
if (fragment.valid === false && errors.length === 0) {
|
|
2062
|
+
errors.push({ domain: context.domain, path, message: "JSON value failed schema validation." });
|
|
2063
|
+
}
|
|
2064
|
+
return {
|
|
2065
|
+
valid: errors.length === 0,
|
|
2066
|
+
errors,
|
|
2067
|
+
warnings
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2004
2070
|
async function handlePrimitiveType2(value, field, context) {
|
|
2005
2071
|
const errors = [];
|
|
2006
2072
|
const warnings = [];
|
|
@@ -2074,6 +2140,18 @@ var JSON_PRIMITIVE_TYPE_MAP = {
|
|
|
2074
2140
|
boolean: "boolean"
|
|
2075
2141
|
};
|
|
2076
2142
|
function toFormanSchema(field) {
|
|
2143
|
+
const result = toFormanSchemaInternal(field);
|
|
2144
|
+
if (Object.getOwnPropertyDescriptor(field, "x-advanced")?.value === true) {
|
|
2145
|
+
result.advanced = true;
|
|
2146
|
+
}
|
|
2147
|
+
return result;
|
|
2148
|
+
}
|
|
2149
|
+
function toFormanSchemaInternal(field) {
|
|
2150
|
+
if (Object.getOwnPropertyDescriptor(field, "x-json")?.value === true) {
|
|
2151
|
+
const schema = { ...field };
|
|
2152
|
+
delete schema["x-json"];
|
|
2153
|
+
return { type: "json", schema };
|
|
2154
|
+
}
|
|
2077
2155
|
const compositeType = Object.getOwnPropertyDescriptor(field, "x-composite")?.value;
|
|
2078
2156
|
if (compositeType === "udttype") return udttypeCollapse(field);
|
|
2079
2157
|
if (compositeType === "udtspec") return udtspecCollapse(field);
|
|
@@ -2209,18 +2287,28 @@ function handleSearchDirective(formanField, directive) {
|
|
|
2209
2287
|
}
|
|
2210
2288
|
|
|
2211
2289
|
// src/index.ts
|
|
2212
|
-
function
|
|
2213
|
-
const context = createDefaultContext();
|
|
2214
|
-
const
|
|
2290
|
+
function toJSONSchemaAdvanced(field, options) {
|
|
2291
|
+
const context = createDefaultContext(options);
|
|
2292
|
+
const schema = toJSONSchemaInternal(field, context);
|
|
2215
2293
|
if (Object.keys(context.definitions ?? {}).length > 0) {
|
|
2216
|
-
Object.defineProperty(
|
|
2294
|
+
Object.defineProperty(schema, "definitions", {
|
|
2217
2295
|
configurable: true,
|
|
2218
2296
|
enumerable: true,
|
|
2219
2297
|
writable: true,
|
|
2220
2298
|
value: context.definitions
|
|
2221
2299
|
});
|
|
2222
2300
|
}
|
|
2223
|
-
|
|
2301
|
+
const skippedPaths = {};
|
|
2302
|
+
if (context.skippedPaths.advanced?.length) {
|
|
2303
|
+
skippedPaths.advanced = context.skippedPaths.advanced;
|
|
2304
|
+
}
|
|
2305
|
+
return {
|
|
2306
|
+
schema,
|
|
2307
|
+
...Object.keys(skippedPaths).length > 0 ? { skippedPaths } : {}
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
function toJSONSchema(field, options) {
|
|
2311
|
+
return toJSONSchemaAdvanced(field, options).schema;
|
|
2224
2312
|
}
|
|
2225
2313
|
function validateFormanWithDomains(domains, options) {
|
|
2226
2314
|
return validateFormanWithDomainsInternal(domains, options);
|
|
@@ -2235,6 +2323,7 @@ function validateForman(values, schema, options, restoreExtras) {
|
|
|
2235
2323
|
0 && (module.exports = {
|
|
2236
2324
|
toFormanSchema,
|
|
2237
2325
|
toJSONSchema,
|
|
2326
|
+
toJSONSchemaAdvanced,
|
|
2238
2327
|
validateForman,
|
|
2239
2328
|
validateFormanWithDomains
|
|
2240
2329
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -41,6 +41,8 @@ type FormanSchemaField = {
|
|
|
41
41
|
help?: string;
|
|
42
42
|
/** Sub-fields specification for collection or array types */
|
|
43
43
|
spec?: FormanSchemaField[] | FormanSchemaField;
|
|
44
|
+
/** JSON Schema for `json` typed fields */
|
|
45
|
+
schema?: JSONSchema7;
|
|
44
46
|
/** Hide field behind advanced toggle */
|
|
45
47
|
advanced?: boolean;
|
|
46
48
|
/** Human readable label for the field */
|
|
@@ -222,6 +224,43 @@ type FormanSchemaFieldState = {
|
|
|
222
224
|
nested?: Record<string, FormanSchemaFieldState>;
|
|
223
225
|
items?: Record<string, FormanSchemaFieldState>[];
|
|
224
226
|
};
|
|
227
|
+
/**
|
|
228
|
+
* Options for converting a Forman Schema to JSON Schema
|
|
229
|
+
*/
|
|
230
|
+
type FormanJsonSchemaOptions = {
|
|
231
|
+
/**
|
|
232
|
+
* Exclude fields marked `advanced: true` from the rendered schema. Defaults to `false`
|
|
233
|
+
* (advanced fields are included and stamped with `x-advanced: true`). When `true`,
|
|
234
|
+
* advanced fields are omitted; their dot-notation paths are reported on
|
|
235
|
+
* `toJSONSchemaAdvanced`'s `skippedPaths.advanced` so the caller can re-request them.
|
|
236
|
+
*/
|
|
237
|
+
excludeAdvancedFields?: boolean;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Result of converting a Forman Schema to JSON Schema
|
|
241
|
+
*/
|
|
242
|
+
type FormanJsonSchemaResult = {
|
|
243
|
+
/** The converted JSON Schema */
|
|
244
|
+
schema: JSONSchema7;
|
|
245
|
+
/** Paths to fields that were skipped during conversion. Present only when at least one field was skipped. */
|
|
246
|
+
skippedPaths?: {
|
|
247
|
+
/** Dot-notation paths of advanced fields that were skipped. Present only when at least one advanced field was skipped. */
|
|
248
|
+
advanced?: string[];
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Verdict fragment returned by an external validation callback (e.g. `validateJson`) that the
|
|
253
|
+
* library cannot perform itself. Spliced into the overall validation result: `errors`/`warnings`
|
|
254
|
+
* are stamped with the field's domain and path.
|
|
255
|
+
*/
|
|
256
|
+
type FormanExternalValidationResult = {
|
|
257
|
+
/** Whether the value is valid. A `false` verdict always fails validation, even with no messages. */
|
|
258
|
+
valid: boolean;
|
|
259
|
+
/** Error messages (cause validation to fail) */
|
|
260
|
+
errors?: string[];
|
|
261
|
+
/** Warning messages (do not affect validity) */
|
|
262
|
+
warnings?: string[];
|
|
263
|
+
};
|
|
225
264
|
type FormanValidationOptions = {
|
|
226
265
|
/** Unknown fields are not allowed when strict is true */
|
|
227
266
|
strict?: boolean;
|
|
@@ -231,6 +270,10 @@ type FormanValidationOptions = {
|
|
|
231
270
|
schemas?: boolean;
|
|
232
271
|
/** Remote resource resolver */
|
|
233
272
|
resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
|
|
273
|
+
/** Validator for `json` typed fields. Receives the field's JSON Schema and the value, and
|
|
274
|
+
* returns (or resolves to) a result fragment that is spliced into the overall validation
|
|
275
|
+
* result. When omitted, `json` fields with a `schema` pass without schema validation. */
|
|
276
|
+
validateJson?(schema: JSONSchema7, value: unknown): FormanExternalValidationResult | Promise<FormanExternalValidationResult>;
|
|
234
277
|
/** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
|
|
235
278
|
domainAliases?: Record<string, string>;
|
|
236
279
|
/** Whether to allow dynamic values (IML expressions, unresolved RPC options).
|
|
@@ -246,12 +289,43 @@ type FormanValidationOptions = {
|
|
|
246
289
|
*/
|
|
247
290
|
declare function toFormanSchema(field: JSONSchema7): FormanSchemaField;
|
|
248
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Converts a Forman Schema field to its JSON Schema equivalent and reports the paths of any
|
|
294
|
+
* fields that were skipped during conversion.
|
|
295
|
+
*
|
|
296
|
+
* **Advanced fields** (`advanced: true`) are included by default and stamped with
|
|
297
|
+
* `x-advanced: true` on the JSON Schema (the marker round-trips through `toFormanSchema`).
|
|
298
|
+
* Pass `{ excludeAdvancedFields: true }` to omit them — when excluded, the affected
|
|
299
|
+
* dot-notation paths are reported on `skippedPaths.advanced`. The filter applies to sub-fields
|
|
300
|
+
* of a collection (main form, nested-by-option, array-of-collection items, composite
|
|
301
|
+
* expansions, cross-domain buffered fields). It does NOT apply to the top-level field passed
|
|
302
|
+
* here, nor to the item type of an array whose `spec` is a single primitive field. Mark the
|
|
303
|
+
* parent as `advanced: true` to hide such structures.
|
|
304
|
+
*
|
|
305
|
+
* Known limitation: composite types (`udtspec`, `udttype`) are memoized in
|
|
306
|
+
* `definitions[type]`; advanced fields inside a composite template are recorded with the
|
|
307
|
+
* path of the FIRST usage only. See the comment near `compositeHandlers` in `src/forman.ts`.
|
|
308
|
+
*
|
|
309
|
+
* If you don't need `skippedPaths`, use {@link toJSONSchema} which returns just the schema.
|
|
310
|
+
*
|
|
311
|
+
* @param field The Forman Schema field to convert
|
|
312
|
+
* @param options Conversion options
|
|
313
|
+
* @returns The conversion result `{ schema, skippedPaths? }`. `skippedPaths` is omitted when nothing was skipped.
|
|
314
|
+
*/
|
|
315
|
+
declare function toJSONSchemaAdvanced(field: FormanSchemaField, options?: FormanJsonSchemaOptions): FormanJsonSchemaResult;
|
|
249
316
|
/**
|
|
250
317
|
* Converts a Forman Schema field to its JSON Schema equivalent.
|
|
318
|
+
*
|
|
319
|
+
* Advanced fields (`advanced: true`) are included by default and stamped with `x-advanced: true`.
|
|
320
|
+
* Pass `{ excludeAdvancedFields: true }` to omit them. If you need to know *which* advanced
|
|
321
|
+
* fields were dropped (e.g. to render a "show advanced" toggle), use {@link toJSONSchemaAdvanced}
|
|
322
|
+
* which returns `{ schema, skippedPaths? }`.
|
|
323
|
+
*
|
|
251
324
|
* @param field The Forman Schema field to convert
|
|
252
|
-
* @
|
|
325
|
+
* @param options Conversion options
|
|
326
|
+
* @returns The equivalent JSON Schema
|
|
253
327
|
*/
|
|
254
|
-
declare function toJSONSchema(field: FormanSchemaField): JSONSchema7;
|
|
328
|
+
declare function toJSONSchema(field: FormanSchemaField, options?: FormanJsonSchemaOptions): JSONSchema7;
|
|
255
329
|
/**
|
|
256
330
|
* Validates a Forman domains against schemas
|
|
257
331
|
* @param domains The domains to validate
|
|
@@ -279,4 +353,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
279
353
|
*/
|
|
280
354
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
|
|
281
355
|
|
|
282
|
-
export { type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
|
|
356
|
+
export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, toFormanSchema, toJSONSchema, toJSONSchemaAdvanced, validateForman, validateFormanWithDomains };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ type FormanSchemaField = {
|
|
|
41
41
|
help?: string;
|
|
42
42
|
/** Sub-fields specification for collection or array types */
|
|
43
43
|
spec?: FormanSchemaField[] | FormanSchemaField;
|
|
44
|
+
/** JSON Schema for `json` typed fields */
|
|
45
|
+
schema?: JSONSchema7;
|
|
44
46
|
/** Hide field behind advanced toggle */
|
|
45
47
|
advanced?: boolean;
|
|
46
48
|
/** Human readable label for the field */
|
|
@@ -222,6 +224,43 @@ type FormanSchemaFieldState = {
|
|
|
222
224
|
nested?: Record<string, FormanSchemaFieldState>;
|
|
223
225
|
items?: Record<string, FormanSchemaFieldState>[];
|
|
224
226
|
};
|
|
227
|
+
/**
|
|
228
|
+
* Options for converting a Forman Schema to JSON Schema
|
|
229
|
+
*/
|
|
230
|
+
type FormanJsonSchemaOptions = {
|
|
231
|
+
/**
|
|
232
|
+
* Exclude fields marked `advanced: true` from the rendered schema. Defaults to `false`
|
|
233
|
+
* (advanced fields are included and stamped with `x-advanced: true`). When `true`,
|
|
234
|
+
* advanced fields are omitted; their dot-notation paths are reported on
|
|
235
|
+
* `toJSONSchemaAdvanced`'s `skippedPaths.advanced` so the caller can re-request them.
|
|
236
|
+
*/
|
|
237
|
+
excludeAdvancedFields?: boolean;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Result of converting a Forman Schema to JSON Schema
|
|
241
|
+
*/
|
|
242
|
+
type FormanJsonSchemaResult = {
|
|
243
|
+
/** The converted JSON Schema */
|
|
244
|
+
schema: JSONSchema7;
|
|
245
|
+
/** Paths to fields that were skipped during conversion. Present only when at least one field was skipped. */
|
|
246
|
+
skippedPaths?: {
|
|
247
|
+
/** Dot-notation paths of advanced fields that were skipped. Present only when at least one advanced field was skipped. */
|
|
248
|
+
advanced?: string[];
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Verdict fragment returned by an external validation callback (e.g. `validateJson`) that the
|
|
253
|
+
* library cannot perform itself. Spliced into the overall validation result: `errors`/`warnings`
|
|
254
|
+
* are stamped with the field's domain and path.
|
|
255
|
+
*/
|
|
256
|
+
type FormanExternalValidationResult = {
|
|
257
|
+
/** Whether the value is valid. A `false` verdict always fails validation, even with no messages. */
|
|
258
|
+
valid: boolean;
|
|
259
|
+
/** Error messages (cause validation to fail) */
|
|
260
|
+
errors?: string[];
|
|
261
|
+
/** Warning messages (do not affect validity) */
|
|
262
|
+
warnings?: string[];
|
|
263
|
+
};
|
|
225
264
|
type FormanValidationOptions = {
|
|
226
265
|
/** Unknown fields are not allowed when strict is true */
|
|
227
266
|
strict?: boolean;
|
|
@@ -231,6 +270,10 @@ type FormanValidationOptions = {
|
|
|
231
270
|
schemas?: boolean;
|
|
232
271
|
/** Remote resource resolver */
|
|
233
272
|
resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
|
|
273
|
+
/** Validator for `json` typed fields. Receives the field's JSON Schema and the value, and
|
|
274
|
+
* returns (or resolves to) a result fragment that is spliced into the overall validation
|
|
275
|
+
* result. When omitted, `json` fields with a `schema` pass without schema validation. */
|
|
276
|
+
validateJson?(schema: JSONSchema7, value: unknown): FormanExternalValidationResult | Promise<FormanExternalValidationResult>;
|
|
234
277
|
/** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
|
|
235
278
|
domainAliases?: Record<string, string>;
|
|
236
279
|
/** Whether to allow dynamic values (IML expressions, unresolved RPC options).
|
|
@@ -246,12 +289,43 @@ type FormanValidationOptions = {
|
|
|
246
289
|
*/
|
|
247
290
|
declare function toFormanSchema(field: JSONSchema7): FormanSchemaField;
|
|
248
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Converts a Forman Schema field to its JSON Schema equivalent and reports the paths of any
|
|
294
|
+
* fields that were skipped during conversion.
|
|
295
|
+
*
|
|
296
|
+
* **Advanced fields** (`advanced: true`) are included by default and stamped with
|
|
297
|
+
* `x-advanced: true` on the JSON Schema (the marker round-trips through `toFormanSchema`).
|
|
298
|
+
* Pass `{ excludeAdvancedFields: true }` to omit them — when excluded, the affected
|
|
299
|
+
* dot-notation paths are reported on `skippedPaths.advanced`. The filter applies to sub-fields
|
|
300
|
+
* of a collection (main form, nested-by-option, array-of-collection items, composite
|
|
301
|
+
* expansions, cross-domain buffered fields). It does NOT apply to the top-level field passed
|
|
302
|
+
* here, nor to the item type of an array whose `spec` is a single primitive field. Mark the
|
|
303
|
+
* parent as `advanced: true` to hide such structures.
|
|
304
|
+
*
|
|
305
|
+
* Known limitation: composite types (`udtspec`, `udttype`) are memoized in
|
|
306
|
+
* `definitions[type]`; advanced fields inside a composite template are recorded with the
|
|
307
|
+
* path of the FIRST usage only. See the comment near `compositeHandlers` in `src/forman.ts`.
|
|
308
|
+
*
|
|
309
|
+
* If you don't need `skippedPaths`, use {@link toJSONSchema} which returns just the schema.
|
|
310
|
+
*
|
|
311
|
+
* @param field The Forman Schema field to convert
|
|
312
|
+
* @param options Conversion options
|
|
313
|
+
* @returns The conversion result `{ schema, skippedPaths? }`. `skippedPaths` is omitted when nothing was skipped.
|
|
314
|
+
*/
|
|
315
|
+
declare function toJSONSchemaAdvanced(field: FormanSchemaField, options?: FormanJsonSchemaOptions): FormanJsonSchemaResult;
|
|
249
316
|
/**
|
|
250
317
|
* Converts a Forman Schema field to its JSON Schema equivalent.
|
|
318
|
+
*
|
|
319
|
+
* Advanced fields (`advanced: true`) are included by default and stamped with `x-advanced: true`.
|
|
320
|
+
* Pass `{ excludeAdvancedFields: true }` to omit them. If you need to know *which* advanced
|
|
321
|
+
* fields were dropped (e.g. to render a "show advanced" toggle), use {@link toJSONSchemaAdvanced}
|
|
322
|
+
* which returns `{ schema, skippedPaths? }`.
|
|
323
|
+
*
|
|
251
324
|
* @param field The Forman Schema field to convert
|
|
252
|
-
* @
|
|
325
|
+
* @param options Conversion options
|
|
326
|
+
* @returns The equivalent JSON Schema
|
|
253
327
|
*/
|
|
254
|
-
declare function toJSONSchema(field: FormanSchemaField): JSONSchema7;
|
|
328
|
+
declare function toJSONSchema(field: FormanSchemaField, options?: FormanJsonSchemaOptions): JSONSchema7;
|
|
255
329
|
/**
|
|
256
330
|
* Validates a Forman domains against schemas
|
|
257
331
|
* @param domains The domains to validate
|
|
@@ -279,4 +353,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
279
353
|
*/
|
|
280
354
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
|
|
281
355
|
|
|
282
|
-
export { type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, toFormanSchema, toJSONSchema, validateForman, validateFormanWithDomains };
|
|
356
|
+
export { type FormanExternalValidationResult, type FormanJsonSchemaOptions, type FormanJsonSchemaResult, type FormanSchemaDirectoryOption, type FormanSchemaExtendedNested, type FormanSchemaExtendedOptions, type FormanSchemaField, type FormanSchemaFieldType, type FormanSchemaNested, type FormanSchemaOption, type FormanSchemaOptionGroup, type FormanSchemaPathExtendedOptions, type FormanSchemaRPCButton, type FormanSchemaValue, type FormanValidationOptions, type FormanValidationResult, toFormanSchema, toJSONSchema, toJSONSchemaAdvanced, validateForman, validateFormanWithDomains };
|
package/dist/index.js
CHANGED
|
@@ -531,7 +531,7 @@ var FORMAN_TYPE_MAP = {
|
|
|
531
531
|
boolean: "boolean",
|
|
532
532
|
checkbox: "boolean",
|
|
533
533
|
date: "string",
|
|
534
|
-
json: "
|
|
534
|
+
json: "object",
|
|
535
535
|
buffer: "string",
|
|
536
536
|
cert: "string",
|
|
537
537
|
color: "string",
|
|
@@ -578,13 +578,15 @@ function appendQueryString(path, domain, tail) {
|
|
|
578
578
|
const separator = path.includes("?") ? "&" : "?";
|
|
579
579
|
return `${path}${separator}${queryString}`;
|
|
580
580
|
}
|
|
581
|
-
function createDefaultContext() {
|
|
581
|
+
function createDefaultContext(options) {
|
|
582
582
|
return {
|
|
583
583
|
domain: "default",
|
|
584
584
|
tail: [],
|
|
585
585
|
path: [],
|
|
586
586
|
roots: {},
|
|
587
587
|
definitions: {},
|
|
588
|
+
excludeAdvancedFields: options?.excludeAdvancedFields ?? false,
|
|
589
|
+
skippedPaths: {},
|
|
588
590
|
addConditionalFields: () => {
|
|
589
591
|
throw new SchemaConversionError("Cannot serialize nested fields without parent field.");
|
|
590
592
|
}
|
|
@@ -639,6 +641,8 @@ function toJSONSchemaInternal(field, context) {
|
|
|
639
641
|
case "collection":
|
|
640
642
|
case "dynamicCollection":
|
|
641
643
|
return handleCollectionType(normalizedField, result, context);
|
|
644
|
+
case "json":
|
|
645
|
+
return handleJsonType(normalizedField, result);
|
|
642
646
|
case "array":
|
|
643
647
|
case "filestorage":
|
|
644
648
|
return handleArrayType(normalizedField, result, context);
|
|
@@ -666,6 +670,7 @@ function handleCollectionType(field, result, context) {
|
|
|
666
670
|
properties: {},
|
|
667
671
|
required: []
|
|
668
672
|
});
|
|
673
|
+
const collectionPath = field.name ? [...context.path, field.name] : context.path;
|
|
669
674
|
function addField(subField, tail) {
|
|
670
675
|
if (typeof subField === "string") {
|
|
671
676
|
const value = { $ref: appendQueryString(subField, context.domain, tail || context.tail) };
|
|
@@ -678,28 +683,41 @@ function handleCollectionType(field, result, context) {
|
|
|
678
683
|
}
|
|
679
684
|
if (!subField.name) return;
|
|
680
685
|
if (result.properties && Object.hasOwn(result.properties, subField.name)) return;
|
|
686
|
+
if (subField.advanced === true && context.excludeAdvancedFields) {
|
|
687
|
+
(context.skippedPaths.advanced ||= []).push([...collectionPath, subField.name].join("."));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
681
690
|
if (subField.required) {
|
|
682
691
|
result.required.push(subField.name);
|
|
683
692
|
}
|
|
693
|
+
const subSchema = toJSONSchemaInternal(subField, {
|
|
694
|
+
...context,
|
|
695
|
+
domain: field["x-domain-root"] || context.domain,
|
|
696
|
+
tail: tail || context.tail,
|
|
697
|
+
path: collectionPath,
|
|
698
|
+
addConditionalFields: (name, value, nested) => {
|
|
699
|
+
result.allOf ||= [];
|
|
700
|
+
result.allOf.push({
|
|
701
|
+
if: {
|
|
702
|
+
properties: {
|
|
703
|
+
[name]: { const: value }
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
then: typeof nested === "string" ? { $ref: nested } : nested
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
if (subField.advanced) {
|
|
711
|
+
Object.defineProperty(subSchema, "x-advanced", {
|
|
712
|
+
configurable: true,
|
|
713
|
+
enumerable: true,
|
|
714
|
+
writable: true,
|
|
715
|
+
value: true
|
|
716
|
+
});
|
|
717
|
+
}
|
|
684
718
|
Object.defineProperty(result.properties, subField.name, {
|
|
685
719
|
enumerable: true,
|
|
686
|
-
value:
|
|
687
|
-
...context,
|
|
688
|
-
domain: field["x-domain-root"] || context.domain,
|
|
689
|
-
tail: tail || context.tail,
|
|
690
|
-
path: [...context.path, field.name],
|
|
691
|
-
addConditionalFields: (name, value, nested) => {
|
|
692
|
-
result.allOf ||= [];
|
|
693
|
-
result.allOf.push({
|
|
694
|
-
if: {
|
|
695
|
-
properties: {
|
|
696
|
-
[name]: { const: value }
|
|
697
|
-
}
|
|
698
|
-
},
|
|
699
|
-
then: typeof nested === "string" ? { $ref: nested } : nested
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
})
|
|
720
|
+
value: subSchema
|
|
703
721
|
});
|
|
704
722
|
}
|
|
705
723
|
if (field["x-domain-root"]) {
|
|
@@ -981,6 +999,20 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
981
999
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
982
1000
|
return result;
|
|
983
1001
|
}
|
|
1002
|
+
function handleJsonType(field, result) {
|
|
1003
|
+
if (!isObject(field.schema)) {
|
|
1004
|
+
return result;
|
|
1005
|
+
}
|
|
1006
|
+
delete result.type;
|
|
1007
|
+
Object.assign(result, field.schema);
|
|
1008
|
+
Object.defineProperty(result, "x-json", {
|
|
1009
|
+
configurable: true,
|
|
1010
|
+
enumerable: true,
|
|
1011
|
+
writable: true,
|
|
1012
|
+
value: true
|
|
1013
|
+
});
|
|
1014
|
+
return result;
|
|
1015
|
+
}
|
|
984
1016
|
function handlePrimitiveType(field, result, context) {
|
|
985
1017
|
if (field.default !== "" && field.default != null) {
|
|
986
1018
|
result.default = field.default;
|
|
@@ -1100,7 +1132,8 @@ var SCHEMA_STRIP_KEYS = [
|
|
|
1100
1132
|
"tags",
|
|
1101
1133
|
"extension",
|
|
1102
1134
|
"codepage",
|
|
1103
|
-
"logic"
|
|
1135
|
+
"logic",
|
|
1136
|
+
"schema"
|
|
1104
1137
|
];
|
|
1105
1138
|
function clampFieldForSchema(field) {
|
|
1106
1139
|
const clamped = { ...field };
|
|
@@ -1145,7 +1178,7 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
1145
1178
|
boolean: "boolean",
|
|
1146
1179
|
checkbox: "boolean",
|
|
1147
1180
|
date: "string",
|
|
1148
|
-
json:
|
|
1181
|
+
json: void 0,
|
|
1149
1182
|
buffer: "string",
|
|
1150
1183
|
cert: "string",
|
|
1151
1184
|
color: "string",
|
|
@@ -1240,7 +1273,8 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1240
1273
|
...data,
|
|
1241
1274
|
...localData
|
|
1242
1275
|
});
|
|
1243
|
-
}
|
|
1276
|
+
},
|
|
1277
|
+
validateJson: options?.validateJson
|
|
1244
1278
|
}
|
|
1245
1279
|
);
|
|
1246
1280
|
errors.push(...result.errors);
|
|
@@ -1372,6 +1406,8 @@ async function validateFormanValue(value, field, context) {
|
|
|
1372
1406
|
return validateFormanValue(value, udtspecExpand({ ...normalizedField }), context);
|
|
1373
1407
|
}
|
|
1374
1408
|
switch (normalizedField.type) {
|
|
1409
|
+
case "json":
|
|
1410
|
+
return handleJsonType2(value, normalizedField, context);
|
|
1375
1411
|
case "collection":
|
|
1376
1412
|
return handleCollectionType2(value, normalizedField, context);
|
|
1377
1413
|
case "array":
|
|
@@ -1972,6 +2008,35 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
1972
2008
|
warnings
|
|
1973
2009
|
};
|
|
1974
2010
|
}
|
|
2011
|
+
async function handleJsonType2(value, field, context) {
|
|
2012
|
+
const errors = [];
|
|
2013
|
+
const warnings = [];
|
|
2014
|
+
if (!isObject(field.schema) || !context.validateJson) {
|
|
2015
|
+
return { valid: true, errors, warnings };
|
|
2016
|
+
}
|
|
2017
|
+
const path = context.path.join(".");
|
|
2018
|
+
let fragment;
|
|
2019
|
+
try {
|
|
2020
|
+
fragment = await context.validateJson(field.schema, value);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
const message = err instanceof Error ? err.message : "JSON schema validation failed unexpectedly.";
|
|
2023
|
+
return { valid: false, errors: [{ domain: context.domain, path, message }], warnings };
|
|
2024
|
+
}
|
|
2025
|
+
for (const message of fragment.errors ?? []) {
|
|
2026
|
+
errors.push({ domain: context.domain, path, message });
|
|
2027
|
+
}
|
|
2028
|
+
for (const message of fragment.warnings ?? []) {
|
|
2029
|
+
warnings.push({ domain: context.domain, path, message });
|
|
2030
|
+
}
|
|
2031
|
+
if (fragment.valid === false && errors.length === 0) {
|
|
2032
|
+
errors.push({ domain: context.domain, path, message: "JSON value failed schema validation." });
|
|
2033
|
+
}
|
|
2034
|
+
return {
|
|
2035
|
+
valid: errors.length === 0,
|
|
2036
|
+
errors,
|
|
2037
|
+
warnings
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
1975
2040
|
async function handlePrimitiveType2(value, field, context) {
|
|
1976
2041
|
const errors = [];
|
|
1977
2042
|
const warnings = [];
|
|
@@ -2045,6 +2110,18 @@ var JSON_PRIMITIVE_TYPE_MAP = {
|
|
|
2045
2110
|
boolean: "boolean"
|
|
2046
2111
|
};
|
|
2047
2112
|
function toFormanSchema(field) {
|
|
2113
|
+
const result = toFormanSchemaInternal(field);
|
|
2114
|
+
if (Object.getOwnPropertyDescriptor(field, "x-advanced")?.value === true) {
|
|
2115
|
+
result.advanced = true;
|
|
2116
|
+
}
|
|
2117
|
+
return result;
|
|
2118
|
+
}
|
|
2119
|
+
function toFormanSchemaInternal(field) {
|
|
2120
|
+
if (Object.getOwnPropertyDescriptor(field, "x-json")?.value === true) {
|
|
2121
|
+
const schema = { ...field };
|
|
2122
|
+
delete schema["x-json"];
|
|
2123
|
+
return { type: "json", schema };
|
|
2124
|
+
}
|
|
2048
2125
|
const compositeType = Object.getOwnPropertyDescriptor(field, "x-composite")?.value;
|
|
2049
2126
|
if (compositeType === "udttype") return udttypeCollapse(field);
|
|
2050
2127
|
if (compositeType === "udtspec") return udtspecCollapse(field);
|
|
@@ -2180,18 +2257,28 @@ function handleSearchDirective(formanField, directive) {
|
|
|
2180
2257
|
}
|
|
2181
2258
|
|
|
2182
2259
|
// src/index.ts
|
|
2183
|
-
function
|
|
2184
|
-
const context = createDefaultContext();
|
|
2185
|
-
const
|
|
2260
|
+
function toJSONSchemaAdvanced(field, options) {
|
|
2261
|
+
const context = createDefaultContext(options);
|
|
2262
|
+
const schema = toJSONSchemaInternal(field, context);
|
|
2186
2263
|
if (Object.keys(context.definitions ?? {}).length > 0) {
|
|
2187
|
-
Object.defineProperty(
|
|
2264
|
+
Object.defineProperty(schema, "definitions", {
|
|
2188
2265
|
configurable: true,
|
|
2189
2266
|
enumerable: true,
|
|
2190
2267
|
writable: true,
|
|
2191
2268
|
value: context.definitions
|
|
2192
2269
|
});
|
|
2193
2270
|
}
|
|
2194
|
-
|
|
2271
|
+
const skippedPaths = {};
|
|
2272
|
+
if (context.skippedPaths.advanced?.length) {
|
|
2273
|
+
skippedPaths.advanced = context.skippedPaths.advanced;
|
|
2274
|
+
}
|
|
2275
|
+
return {
|
|
2276
|
+
schema,
|
|
2277
|
+
...Object.keys(skippedPaths).length > 0 ? { skippedPaths } : {}
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
function toJSONSchema(field, options) {
|
|
2281
|
+
return toJSONSchemaAdvanced(field, options).schema;
|
|
2195
2282
|
}
|
|
2196
2283
|
function validateFormanWithDomains(domains, options) {
|
|
2197
2284
|
return validateFormanWithDomainsInternal(domains, options);
|
|
@@ -2205,6 +2292,7 @@ function validateForman(values, schema, options, restoreExtras) {
|
|
|
2205
2292
|
export {
|
|
2206
2293
|
toFormanSchema,
|
|
2207
2294
|
toJSONSchema,
|
|
2295
|
+
toJSONSchemaAdvanced,
|
|
2208
2296
|
validateForman,
|
|
2209
2297
|
validateFormanWithDomains
|
|
2210
2298
|
};
|