-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
schemaToYup.ts
152 lines (132 loc) · 4.64 KB
/
schemaToYup.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import { JSONSchema7 } from "json-schema";
import * as yup from "yup";
import { WidgetConfigMap } from "core/form/types";
import { isDefined } from "utils/common";
/**
* Returns yup.schema for validation
*
* This method builds yup schema based on jsonSchema ${@link JSONSchema7} and widgetConfig ${@link WidgetConfigMap}.
* Every property is walked through recursively in case it is condition | object | array.
*
* uiConfig is used to select currently selected oneOf conditions to build proper schema
* As uiConfig widget paths are .dot based (key1.innerModule1.innerModule2) propertyKey is provided recursively
* @param jsonSchema
* @param uiConfig uiConfig of widget currently selected in form
* @param parentSchema used in recursive schema building as required fields can be described in parentSchema
* @param propertyKey used in recursive schema building for building path for uiConfig
* @param propertyPath constructs path of property
*/
export const buildYupFormForJsonSchema = (
jsonSchema: JSONSchema7,
uiConfig?: WidgetConfigMap,
parentSchema?: JSONSchema7,
propertyKey?: string,
propertyPath: string | undefined = propertyKey
): yup.AnySchema => {
let schema:
| yup.NumberSchema
| yup.StringSchema
| yup.AnyObjectSchema
| yup.ArraySchema<yup.AnySchema>
| yup.BooleanSchema
| null = null;
if (jsonSchema.oneOf && uiConfig && propertyPath) {
let selectedSchema = jsonSchema.oneOf.find(
(condition) => typeof condition !== "boolean" && uiConfig[propertyPath]?.selectedItem === condition.title
);
// Select first oneOf path if no item selected
selectedSchema = selectedSchema ?? jsonSchema.oneOf[0];
if (selectedSchema && typeof selectedSchema !== "boolean") {
return buildYupFormForJsonSchema(
{ type: jsonSchema.type, ...selectedSchema },
uiConfig,
jsonSchema,
propertyKey,
propertyPath
);
}
}
switch (jsonSchema.type) {
case "string":
schema = yup
.string()
.transform((val) => String(val))
.trim();
if (jsonSchema?.pattern !== undefined) {
schema = schema.matches(new RegExp(jsonSchema.pattern), "form.pattern.error");
}
break;
case "boolean":
schema = yup.boolean();
break;
case "integer":
schema = yup.number().transform((value) => (isNaN(value) ? undefined : value));
if (jsonSchema?.minimum !== undefined) {
schema = schema.min(jsonSchema?.minimum);
}
if (jsonSchema?.maximum !== undefined) {
schema = schema.max(jsonSchema?.maximum);
}
break;
case "array":
if (typeof jsonSchema.items === "object" && !Array.isArray(jsonSchema.items)) {
schema = yup
.array()
.required("you need this!")
.of(
buildYupFormForJsonSchema(
jsonSchema.items,
uiConfig,
jsonSchema,
propertyKey,
propertyPath ? `${propertyPath}.${propertyKey}` : propertyKey
)
);
}
break;
case "object":
let objectSchema = yup.object();
const keyEntries = Object.entries(jsonSchema.properties || {}).map(([propertyKey, condition]) => [
propertyKey,
typeof condition !== "boolean"
? buildYupFormForJsonSchema(
condition,
uiConfig,
jsonSchema,
propertyKey,
propertyPath ? `${propertyPath}.${propertyKey}` : propertyKey
)
: yup.mixed(),
]);
if (keyEntries.length) {
objectSchema = objectSchema.shape(Object.fromEntries(keyEntries));
} else {
objectSchema = objectSchema.default({});
}
schema = objectSchema;
}
if (schema) {
const hasDefault = isDefined(jsonSchema.default);
if (hasDefault) {
// @ts-expect-error can't infer correct type here so lets just use default from json_schema
schema = schema.default(jsonSchema.default);
}
if (!hasDefault && jsonSchema.const) {
// @ts-expect-error can't infer correct type here so lets just use default from json_schema
schema = schema.oneOf([jsonSchema.const]).default(jsonSchema.const);
}
if (jsonSchema.enum) {
// @ts-expect-error as enum is array we are going to use it as oneOf for yup
schema = schema.oneOf(jsonSchema.enum);
}
const isRequired =
!hasDefault &&
parentSchema &&
Array.isArray(parentSchema?.required) &&
parentSchema.required.find((item) => item === propertyKey);
if (schema && isRequired) {
schema = schema.required("form.empty.error");
}
}
return schema || yup.mixed();
};