@makehq/forman-schema 1.14.0 → 1.16.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 +51 -1
- package/dist/index.cjs +63 -7
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +63 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,6 +80,56 @@ const jsonSchemaField = {
|
|
|
80
80
|
const formanSchema = toFormanSchema(jsonSchemaField);
|
|
81
81
|
```
|
|
82
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
|
+
|
|
83
133
|
### Validation
|
|
84
134
|
|
|
85
135
|
Validate Forman values against a Forman Schema. Two entry points are available:
|
|
@@ -217,7 +267,7 @@ const result = await validateFormanWithDomains(
|
|
|
217
267
|
- hidden → string
|
|
218
268
|
- hook → number
|
|
219
269
|
- integer → number
|
|
220
|
-
- json →
|
|
270
|
+
- json → object (or its `schema` echoed verbatim when provided — see [JSON fields](#json-fields-type-json))
|
|
221
271
|
- keychain → number
|
|
222
272
|
- number → number
|
|
223
273
|
- path → string
|
package/dist/index.cjs
CHANGED
|
@@ -216,7 +216,7 @@ function buildRestoreStructure(items) {
|
|
|
216
216
|
}
|
|
217
217
|
current = current[key].items;
|
|
218
218
|
} else {
|
|
219
|
-
if (!current[key].nested) {
|
|
219
|
+
if (!current[key].nested || typeof current[key].nested !== "object" || Array.isArray(current[key].nested)) {
|
|
220
220
|
current[key].nested = {};
|
|
221
221
|
}
|
|
222
222
|
current = current[key].nested;
|
|
@@ -561,7 +561,7 @@ var FORMAN_TYPE_MAP = {
|
|
|
561
561
|
boolean: "boolean",
|
|
562
562
|
checkbox: "boolean",
|
|
563
563
|
date: "string",
|
|
564
|
-
json: "
|
|
564
|
+
json: "object",
|
|
565
565
|
buffer: "string",
|
|
566
566
|
cert: "string",
|
|
567
567
|
color: "string",
|
|
@@ -671,6 +671,8 @@ function toJSONSchemaInternal(field, context) {
|
|
|
671
671
|
case "collection":
|
|
672
672
|
case "dynamicCollection":
|
|
673
673
|
return handleCollectionType(normalizedField, result, context);
|
|
674
|
+
case "json":
|
|
675
|
+
return handleJsonType(normalizedField, result);
|
|
674
676
|
case "array":
|
|
675
677
|
case "filestorage":
|
|
676
678
|
return handleArrayType(normalizedField, result, context);
|
|
@@ -1027,6 +1029,20 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
1027
1029
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
1028
1030
|
return result;
|
|
1029
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
|
+
}
|
|
1030
1046
|
function handlePrimitiveType(field, result, context) {
|
|
1031
1047
|
if (field.default !== "" && field.default != null) {
|
|
1032
1048
|
result.default = field.default;
|
|
@@ -1146,7 +1162,8 @@ var SCHEMA_STRIP_KEYS = [
|
|
|
1146
1162
|
"tags",
|
|
1147
1163
|
"extension",
|
|
1148
1164
|
"codepage",
|
|
1149
|
-
"logic"
|
|
1165
|
+
"logic",
|
|
1166
|
+
"schema"
|
|
1150
1167
|
];
|
|
1151
1168
|
function clampFieldForSchema(field) {
|
|
1152
1169
|
const clamped = { ...field };
|
|
@@ -1191,7 +1208,7 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
1191
1208
|
boolean: "boolean",
|
|
1192
1209
|
checkbox: "boolean",
|
|
1193
1210
|
date: "string",
|
|
1194
|
-
json:
|
|
1211
|
+
json: void 0,
|
|
1195
1212
|
buffer: "string",
|
|
1196
1213
|
cert: "string",
|
|
1197
1214
|
color: "string",
|
|
@@ -1286,7 +1303,8 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1286
1303
|
...data,
|
|
1287
1304
|
...localData
|
|
1288
1305
|
});
|
|
1289
|
-
}
|
|
1306
|
+
},
|
|
1307
|
+
validateJson: options?.validateJson
|
|
1290
1308
|
}
|
|
1291
1309
|
);
|
|
1292
1310
|
errors.push(...result.errors);
|
|
@@ -1418,6 +1436,8 @@ async function validateFormanValue(value, field, context) {
|
|
|
1418
1436
|
return validateFormanValue(value, udtspecExpand({ ...normalizedField }), context);
|
|
1419
1437
|
}
|
|
1420
1438
|
switch (normalizedField.type) {
|
|
1439
|
+
case "json":
|
|
1440
|
+
return handleJsonType2(value, normalizedField, context);
|
|
1421
1441
|
case "collection":
|
|
1422
1442
|
return handleCollectionType2(value, normalizedField, context);
|
|
1423
1443
|
case "array":
|
|
@@ -1897,7 +1917,8 @@ async function handleSelectType(value, field, context) {
|
|
|
1897
1917
|
path: context.path,
|
|
1898
1918
|
state: {
|
|
1899
1919
|
mode: "chose",
|
|
1900
|
-
label: placeholder.label
|
|
1920
|
+
label: placeholder.label,
|
|
1921
|
+
...placeholder.nested ? { nested: placeholder.nested } : {}
|
|
1901
1922
|
}
|
|
1902
1923
|
});
|
|
1903
1924
|
}
|
|
@@ -1935,7 +1956,8 @@ async function handleSelectType(value, field, context) {
|
|
|
1935
1956
|
path: context.path,
|
|
1936
1957
|
state: {
|
|
1937
1958
|
mode: isReferenceType(field.type) ? void 0 : "chose",
|
|
1938
|
-
label: item.label
|
|
1959
|
+
label: item.label,
|
|
1960
|
+
...item.nested ? { nested: item.nested } : {}
|
|
1939
1961
|
}
|
|
1940
1962
|
});
|
|
1941
1963
|
}
|
|
@@ -2018,6 +2040,35 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
2018
2040
|
warnings
|
|
2019
2041
|
};
|
|
2020
2042
|
}
|
|
2043
|
+
async function handleJsonType2(value, field, context) {
|
|
2044
|
+
const errors = [];
|
|
2045
|
+
const warnings = [];
|
|
2046
|
+
if (!isObject(field.schema) || !context.validateJson) {
|
|
2047
|
+
return { valid: true, errors, warnings };
|
|
2048
|
+
}
|
|
2049
|
+
const path = context.path.join(".");
|
|
2050
|
+
let fragment;
|
|
2051
|
+
try {
|
|
2052
|
+
fragment = await context.validateJson(field.schema, value);
|
|
2053
|
+
} catch (err) {
|
|
2054
|
+
const message = err instanceof Error ? err.message : "JSON schema validation failed unexpectedly.";
|
|
2055
|
+
return { valid: false, errors: [{ domain: context.domain, path, message }], warnings };
|
|
2056
|
+
}
|
|
2057
|
+
for (const message of fragment.errors ?? []) {
|
|
2058
|
+
errors.push({ domain: context.domain, path, message });
|
|
2059
|
+
}
|
|
2060
|
+
for (const message of fragment.warnings ?? []) {
|
|
2061
|
+
warnings.push({ domain: context.domain, path, message });
|
|
2062
|
+
}
|
|
2063
|
+
if (fragment.valid === false && errors.length === 0) {
|
|
2064
|
+
errors.push({ domain: context.domain, path, message: "JSON value failed schema validation." });
|
|
2065
|
+
}
|
|
2066
|
+
return {
|
|
2067
|
+
valid: errors.length === 0,
|
|
2068
|
+
errors,
|
|
2069
|
+
warnings
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2021
2072
|
async function handlePrimitiveType2(value, field, context) {
|
|
2022
2073
|
const errors = [];
|
|
2023
2074
|
const warnings = [];
|
|
@@ -2098,6 +2149,11 @@ function toFormanSchema(field) {
|
|
|
2098
2149
|
return result;
|
|
2099
2150
|
}
|
|
2100
2151
|
function toFormanSchemaInternal(field) {
|
|
2152
|
+
if (Object.getOwnPropertyDescriptor(field, "x-json")?.value === true) {
|
|
2153
|
+
const schema = { ...field };
|
|
2154
|
+
delete schema["x-json"];
|
|
2155
|
+
return { type: "json", schema };
|
|
2156
|
+
}
|
|
2101
2157
|
const compositeType = Object.getOwnPropertyDescriptor(field, "x-composite")?.value;
|
|
2102
2158
|
if (compositeType === "udttype") return udttypeCollapse(field);
|
|
2103
2159
|
if (compositeType === "udtspec") return udtspecCollapse(field);
|
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 */
|
|
@@ -219,7 +221,12 @@ type FormanSchemaFieldState = {
|
|
|
219
221
|
path?: Array<string>;
|
|
220
222
|
data?: Record<string, unknown>;
|
|
221
223
|
extra?: Record<string, unknown>;
|
|
222
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Child field states (record, built from field paths) or, on `chose` states of
|
|
226
|
+
* select-like fields, the chosen option's nested field specification — the UI
|
|
227
|
+
* persists that spec in `metadata.restore` to render the dependent fields.
|
|
228
|
+
*/
|
|
229
|
+
nested?: Record<string, FormanSchemaFieldState> | FormanSchemaNested;
|
|
223
230
|
items?: Record<string, FormanSchemaFieldState>[];
|
|
224
231
|
};
|
|
225
232
|
/**
|
|
@@ -246,6 +253,19 @@ type FormanJsonSchemaResult = {
|
|
|
246
253
|
advanced?: string[];
|
|
247
254
|
};
|
|
248
255
|
};
|
|
256
|
+
/**
|
|
257
|
+
* Verdict fragment returned by an external validation callback (e.g. `validateJson`) that the
|
|
258
|
+
* library cannot perform itself. Spliced into the overall validation result: `errors`/`warnings`
|
|
259
|
+
* are stamped with the field's domain and path.
|
|
260
|
+
*/
|
|
261
|
+
type FormanExternalValidationResult = {
|
|
262
|
+
/** Whether the value is valid. A `false` verdict always fails validation, even with no messages. */
|
|
263
|
+
valid: boolean;
|
|
264
|
+
/** Error messages (cause validation to fail) */
|
|
265
|
+
errors?: string[];
|
|
266
|
+
/** Warning messages (do not affect validity) */
|
|
267
|
+
warnings?: string[];
|
|
268
|
+
};
|
|
249
269
|
type FormanValidationOptions = {
|
|
250
270
|
/** Unknown fields are not allowed when strict is true */
|
|
251
271
|
strict?: boolean;
|
|
@@ -255,6 +275,10 @@ type FormanValidationOptions = {
|
|
|
255
275
|
schemas?: boolean;
|
|
256
276
|
/** Remote resource resolver */
|
|
257
277
|
resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
|
|
278
|
+
/** Validator for `json` typed fields. Receives the field's JSON Schema and the value, and
|
|
279
|
+
* returns (or resolves to) a result fragment that is spliced into the overall validation
|
|
280
|
+
* result. When omitted, `json` fields with a `schema` pass without schema validation. */
|
|
281
|
+
validateJson?(schema: JSONSchema7, value: unknown): FormanExternalValidationResult | Promise<FormanExternalValidationResult>;
|
|
258
282
|
/** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
|
|
259
283
|
domainAliases?: Record<string, string>;
|
|
260
284
|
/** Whether to allow dynamic values (IML expressions, unresolved RPC options).
|
|
@@ -334,4 +358,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
334
358
|
*/
|
|
335
359
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
|
|
336
360
|
|
|
337
|
-
export { 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 };
|
|
361
|
+
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 */
|
|
@@ -219,7 +221,12 @@ type FormanSchemaFieldState = {
|
|
|
219
221
|
path?: Array<string>;
|
|
220
222
|
data?: Record<string, unknown>;
|
|
221
223
|
extra?: Record<string, unknown>;
|
|
222
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Child field states (record, built from field paths) or, on `chose` states of
|
|
226
|
+
* select-like fields, the chosen option's nested field specification — the UI
|
|
227
|
+
* persists that spec in `metadata.restore` to render the dependent fields.
|
|
228
|
+
*/
|
|
229
|
+
nested?: Record<string, FormanSchemaFieldState> | FormanSchemaNested;
|
|
223
230
|
items?: Record<string, FormanSchemaFieldState>[];
|
|
224
231
|
};
|
|
225
232
|
/**
|
|
@@ -246,6 +253,19 @@ type FormanJsonSchemaResult = {
|
|
|
246
253
|
advanced?: string[];
|
|
247
254
|
};
|
|
248
255
|
};
|
|
256
|
+
/**
|
|
257
|
+
* Verdict fragment returned by an external validation callback (e.g. `validateJson`) that the
|
|
258
|
+
* library cannot perform itself. Spliced into the overall validation result: `errors`/`warnings`
|
|
259
|
+
* are stamped with the field's domain and path.
|
|
260
|
+
*/
|
|
261
|
+
type FormanExternalValidationResult = {
|
|
262
|
+
/** Whether the value is valid. A `false` verdict always fails validation, even with no messages. */
|
|
263
|
+
valid: boolean;
|
|
264
|
+
/** Error messages (cause validation to fail) */
|
|
265
|
+
errors?: string[];
|
|
266
|
+
/** Warning messages (do not affect validity) */
|
|
267
|
+
warnings?: string[];
|
|
268
|
+
};
|
|
249
269
|
type FormanValidationOptions = {
|
|
250
270
|
/** Unknown fields are not allowed when strict is true */
|
|
251
271
|
strict?: boolean;
|
|
@@ -255,6 +275,10 @@ type FormanValidationOptions = {
|
|
|
255
275
|
schemas?: boolean;
|
|
256
276
|
/** Remote resource resolver */
|
|
257
277
|
resolveRemote?(path: string, data: Record<string, unknown>): Promise<unknown>;
|
|
278
|
+
/** Validator for `json` typed fields. Receives the field's JSON Schema and the value, and
|
|
279
|
+
* returns (or resolves to) a result fragment that is spliced into the overall validation
|
|
280
|
+
* result. When omitted, `json` fields with a `schema` pass without schema validation. */
|
|
281
|
+
validateJson?(schema: JSONSchema7, value: unknown): FormanExternalValidationResult | Promise<FormanExternalValidationResult>;
|
|
258
282
|
/** Maps domain names used in nested.domain to actual domain keys passed to validateFormanWithDomains */
|
|
259
283
|
domainAliases?: Record<string, string>;
|
|
260
284
|
/** Whether to allow dynamic values (IML expressions, unresolved RPC options).
|
|
@@ -334,4 +358,4 @@ declare function validateFormanWithDomains(domains: Record<string, {
|
|
|
334
358
|
*/
|
|
335
359
|
declare function validateForman(values: Record<string, unknown>, schema: FormanSchemaField[], options?: FormanValidationOptions, restoreExtras?: Record<string, Record<string, unknown>>): Promise<FormanValidationResult>;
|
|
336
360
|
|
|
337
|
-
export { 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 };
|
|
361
|
+
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
|
@@ -186,7 +186,7 @@ function buildRestoreStructure(items) {
|
|
|
186
186
|
}
|
|
187
187
|
current = current[key].items;
|
|
188
188
|
} else {
|
|
189
|
-
if (!current[key].nested) {
|
|
189
|
+
if (!current[key].nested || typeof current[key].nested !== "object" || Array.isArray(current[key].nested)) {
|
|
190
190
|
current[key].nested = {};
|
|
191
191
|
}
|
|
192
192
|
current = current[key].nested;
|
|
@@ -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",
|
|
@@ -641,6 +641,8 @@ function toJSONSchemaInternal(field, context) {
|
|
|
641
641
|
case "collection":
|
|
642
642
|
case "dynamicCollection":
|
|
643
643
|
return handleCollectionType(normalizedField, result, context);
|
|
644
|
+
case "json":
|
|
645
|
+
return handleJsonType(normalizedField, result);
|
|
644
646
|
case "array":
|
|
645
647
|
case "filestorage":
|
|
646
648
|
return handleArrayType(normalizedField, result, context);
|
|
@@ -997,6 +999,20 @@ function handleSelectOrPathType(field, result, context) {
|
|
|
997
999
|
if (field.rpc) result = processRpcDirective(field, result, context);
|
|
998
1000
|
return result;
|
|
999
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
|
+
}
|
|
1000
1016
|
function handlePrimitiveType(field, result, context) {
|
|
1001
1017
|
if (field.default !== "" && field.default != null) {
|
|
1002
1018
|
result.default = field.default;
|
|
@@ -1116,7 +1132,8 @@ var SCHEMA_STRIP_KEYS = [
|
|
|
1116
1132
|
"tags",
|
|
1117
1133
|
"extension",
|
|
1118
1134
|
"codepage",
|
|
1119
|
-
"logic"
|
|
1135
|
+
"logic",
|
|
1136
|
+
"schema"
|
|
1120
1137
|
];
|
|
1121
1138
|
function clampFieldForSchema(field) {
|
|
1122
1139
|
const clamped = { ...field };
|
|
@@ -1161,7 +1178,7 @@ var FORMAN_TYPE_MAP2 = {
|
|
|
1161
1178
|
boolean: "boolean",
|
|
1162
1179
|
checkbox: "boolean",
|
|
1163
1180
|
date: "string",
|
|
1164
|
-
json:
|
|
1181
|
+
json: void 0,
|
|
1165
1182
|
buffer: "string",
|
|
1166
1183
|
cert: "string",
|
|
1167
1184
|
color: "string",
|
|
@@ -1256,7 +1273,8 @@ async function validateFormanWithDomainsInternal(domains, options) {
|
|
|
1256
1273
|
...data,
|
|
1257
1274
|
...localData
|
|
1258
1275
|
});
|
|
1259
|
-
}
|
|
1276
|
+
},
|
|
1277
|
+
validateJson: options?.validateJson
|
|
1260
1278
|
}
|
|
1261
1279
|
);
|
|
1262
1280
|
errors.push(...result.errors);
|
|
@@ -1388,6 +1406,8 @@ async function validateFormanValue(value, field, context) {
|
|
|
1388
1406
|
return validateFormanValue(value, udtspecExpand({ ...normalizedField }), context);
|
|
1389
1407
|
}
|
|
1390
1408
|
switch (normalizedField.type) {
|
|
1409
|
+
case "json":
|
|
1410
|
+
return handleJsonType2(value, normalizedField, context);
|
|
1391
1411
|
case "collection":
|
|
1392
1412
|
return handleCollectionType2(value, normalizedField, context);
|
|
1393
1413
|
case "array":
|
|
@@ -1867,7 +1887,8 @@ async function handleSelectType(value, field, context) {
|
|
|
1867
1887
|
path: context.path,
|
|
1868
1888
|
state: {
|
|
1869
1889
|
mode: "chose",
|
|
1870
|
-
label: placeholder.label
|
|
1890
|
+
label: placeholder.label,
|
|
1891
|
+
...placeholder.nested ? { nested: placeholder.nested } : {}
|
|
1871
1892
|
}
|
|
1872
1893
|
});
|
|
1873
1894
|
}
|
|
@@ -1905,7 +1926,8 @@ async function handleSelectType(value, field, context) {
|
|
|
1905
1926
|
path: context.path,
|
|
1906
1927
|
state: {
|
|
1907
1928
|
mode: isReferenceType(field.type) ? void 0 : "chose",
|
|
1908
|
-
label: item.label
|
|
1929
|
+
label: item.label,
|
|
1930
|
+
...item.nested ? { nested: item.nested } : {}
|
|
1909
1931
|
}
|
|
1910
1932
|
});
|
|
1911
1933
|
}
|
|
@@ -1988,6 +2010,35 @@ async function handleNestedFields(nested, value, field, context) {
|
|
|
1988
2010
|
warnings
|
|
1989
2011
|
};
|
|
1990
2012
|
}
|
|
2013
|
+
async function handleJsonType2(value, field, context) {
|
|
2014
|
+
const errors = [];
|
|
2015
|
+
const warnings = [];
|
|
2016
|
+
if (!isObject(field.schema) || !context.validateJson) {
|
|
2017
|
+
return { valid: true, errors, warnings };
|
|
2018
|
+
}
|
|
2019
|
+
const path = context.path.join(".");
|
|
2020
|
+
let fragment;
|
|
2021
|
+
try {
|
|
2022
|
+
fragment = await context.validateJson(field.schema, value);
|
|
2023
|
+
} catch (err) {
|
|
2024
|
+
const message = err instanceof Error ? err.message : "JSON schema validation failed unexpectedly.";
|
|
2025
|
+
return { valid: false, errors: [{ domain: context.domain, path, message }], warnings };
|
|
2026
|
+
}
|
|
2027
|
+
for (const message of fragment.errors ?? []) {
|
|
2028
|
+
errors.push({ domain: context.domain, path, message });
|
|
2029
|
+
}
|
|
2030
|
+
for (const message of fragment.warnings ?? []) {
|
|
2031
|
+
warnings.push({ domain: context.domain, path, message });
|
|
2032
|
+
}
|
|
2033
|
+
if (fragment.valid === false && errors.length === 0) {
|
|
2034
|
+
errors.push({ domain: context.domain, path, message: "JSON value failed schema validation." });
|
|
2035
|
+
}
|
|
2036
|
+
return {
|
|
2037
|
+
valid: errors.length === 0,
|
|
2038
|
+
errors,
|
|
2039
|
+
warnings
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
1991
2042
|
async function handlePrimitiveType2(value, field, context) {
|
|
1992
2043
|
const errors = [];
|
|
1993
2044
|
const warnings = [];
|
|
@@ -2068,6 +2119,11 @@ function toFormanSchema(field) {
|
|
|
2068
2119
|
return result;
|
|
2069
2120
|
}
|
|
2070
2121
|
function toFormanSchemaInternal(field) {
|
|
2122
|
+
if (Object.getOwnPropertyDescriptor(field, "x-json")?.value === true) {
|
|
2123
|
+
const schema = { ...field };
|
|
2124
|
+
delete schema["x-json"];
|
|
2125
|
+
return { type: "json", schema };
|
|
2126
|
+
}
|
|
2071
2127
|
const compositeType = Object.getOwnPropertyDescriptor(field, "x-composite")?.value;
|
|
2072
2128
|
if (compositeType === "udttype") return udttypeCollapse(field);
|
|
2073
2129
|
if (compositeType === "udtspec") return udtspecCollapse(field);
|