@nolwenture/formik-jsonschema-form 1.4.4 → 2.0.1

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 CHANGED
@@ -166,7 +166,7 @@ export function MyFormComponent() {
166
166
  return (
167
167
  <Formik
168
168
  initialValues={createInitialValues(schema)}
169
- validationSchema={createValidationSchema(schema)}
169
+ validationSchema={createValidationSchema({schema})}
170
170
  onSubmit={handleSubmit}
171
171
  >
172
172
  <Form schema={schema} uiSchema={uiSchema}></Form>
@@ -175,6 +175,170 @@ export function MyFormComponent() {
175
175
  }
176
176
  ```
177
177
 
178
+ ### Validation
179
+
180
+ The `createValidationSchema` function generates a Yup validation schema from your JSON Schema. It supports:
181
+
182
+ #### Supported JSON Schema Keywords
183
+
184
+ **String validation:**
185
+ - `minLength` - Minimum string length
186
+ - `maxLength` - Maximum string length
187
+ - `pattern` - Regular expression pattern
188
+
189
+ **Number validation:**
190
+ - `minimum` - Minimum value (inclusive)
191
+ - `maximum` - Maximum value (inclusive)
192
+ - `exclusiveMinimum` - Minimum value (exclusive)
193
+ - `exclusiveMaximum` - Maximum value (exclusive)
194
+ - `multipleOf` - Value must be a multiple of this number
195
+
196
+ **Array validation:**
197
+ - `minItems` - Minimum number of items
198
+ - `maxItems` - Maximum number of items
199
+ - `uniqueItems` - All items must be unique
200
+
201
+ **Equality validation:**
202
+ - `const` - Value must equal the specified constant
203
+ - `enum` - Value must be one of the specified values
204
+ - `not: { const: ... }` - Value must NOT equal the specified constant
205
+
206
+ **Required fields:**
207
+ - Fields listed in the `required` array are validated as required
208
+
209
+ #### Function Signature
210
+
211
+ ```tsx
212
+ createValidationSchema(
213
+ { schema, uiSchema }, // Schema and optional UI schema
214
+ { // Optional options object
215
+ localize, // Localization function i18next t compatible
216
+ definitions, // Use these definitions instead of definitions in schema
217
+ }
218
+ )
219
+ ```
220
+
221
+ #### Custom Error Messages
222
+
223
+ You can customize error messages using `ui:validation` in the uiSchema:
224
+
225
+ ```tsx
226
+ const uiSchema = {
227
+ username: {
228
+ "ui:validation": {
229
+ error: {
230
+ minLength: "Username must be at least 3 characters",
231
+ pattern: "Username can only contain letters and numbers",
232
+ required: "Please enter a username",
233
+ },
234
+ },
235
+ },
236
+ };
237
+ ```
238
+
239
+ #### Localization
240
+
241
+ Pass a `localize` function compatible with i18next to get localized error messages:
242
+
243
+ ```tsx
244
+ import { useTranslation } from 'react-i18next';
245
+
246
+ const { t } = useTranslation();
247
+ const validationSchema = createValidationSchema(
248
+ { schema, uiSchema },
249
+ { localize: t }
250
+ );
251
+ ```
252
+
253
+ The function will be called with keys like `validation.error.minLength`, `validation.error.required`, etc.
254
+
255
+ #### Error Message Priority
256
+
257
+ 1. **Custom UI message** - `uiSchema["ui:validation"].error[keyword]`
258
+ 2. **Localized message** - Result from `localize("validation.error.{keyword}", params)`
259
+ 3. **Default message** - Built-in English messages
260
+
261
+ #### Example
262
+
263
+ ```tsx
264
+ const schema = {
265
+ type: "object",
266
+ required: ["email", "age"],
267
+ properties: {
268
+ email: {
269
+ type: "string",
270
+ pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
271
+ },
272
+ age: {
273
+ type: "number",
274
+ minimum: 18,
275
+ maximum: 120,
276
+ },
277
+ tags: {
278
+ type: "array",
279
+ minItems: 1,
280
+ maxItems: 5,
281
+ uniqueItems: true,
282
+ items: { type: "string" },
283
+ },
284
+ status: {
285
+ type: "string",
286
+ const: "active",
287
+ },
288
+ },
289
+ };
290
+
291
+ const uiSchema = {
292
+ email: {
293
+ "ui:validation": {
294
+ error: {
295
+ pattern: "Please enter a valid email address",
296
+ },
297
+ },
298
+ },
299
+ };
300
+
301
+ const validationSchema = createValidationSchema({ schema, uiSchema });
302
+ ```
303
+
304
+ #### Password Confirmation (Cross-Field Validation)
305
+
306
+ Use `const: { $data: "1/fieldName" }` to validate that one field equals another. The `1/` prefix uses JSON Pointer syntax to reference a sibling field (go up 1 level):
307
+
308
+ ```tsx
309
+ const schema = {
310
+ type: "object",
311
+ required: ["password", "confirmPassword"],
312
+ properties: {
313
+ password: {
314
+ type: "string",
315
+ title: "Password",
316
+ minLength: 8,
317
+ },
318
+ confirmPassword: {
319
+ type: "string",
320
+ title: "Confirm password",
321
+ const: { $data: "1/password" }, // Must match password field
322
+ },
323
+ },
324
+ };
325
+
326
+ const uiSchema = {
327
+ confirmPassword: {
328
+ "ui:validation": {
329
+ error: {
330
+ const: "Passwords must match",
331
+ },
332
+ },
333
+ },
334
+ };
335
+
336
+ const validationSchema = createValidationSchema({ schema, uiSchema });
337
+ ```
338
+
339
+ The `$data` reference syntax follows the AJV validation library pattern with JSON Pointer paths. This is only supported for `const` keyword at the moment, but it can be extended to support other keywords in the future (e.g. minimum to compare endDate is after startDate).
340
+
341
+
178
342
  ### Using custom components
179
343
 
180
344
  Form components (such as wrappers) can be overridden with custom ones
@@ -37,6 +37,23 @@ exports.createValidationSchema = createValidationSchema;
37
37
  const Yup = __importStar(require("yup"));
38
38
  const resolveSchema_1 = require("./resolveSchema");
39
39
  const solveReferences_1 = require("./solveReferences");
40
+ const DEFAULT_ERROR_MESSAGES = {
41
+ required: "Required",
42
+ minLength: (params) => `Must be at least ${params.limit} characters`,
43
+ maxLength: (params) => `Must be at most ${params.limit} characters`,
44
+ pattern: "Invalid format",
45
+ minimum: (params) => `Must be at least ${params.min}`,
46
+ maximum: (params) => `Must be at most ${params.max}`,
47
+ exclusiveMinimum: (params) => `Must be more than ${params.min}`,
48
+ exclusiveMaximum: (params) => `Must be less than ${params.max}`,
49
+ multipleOf: (params) => `Must be a multiple of ${params.multipleOf}`,
50
+ minItems: (params) => `Must have at least ${params.min} items`,
51
+ maxItems: (params) => `Must have at most ${params.max} items`,
52
+ uniqueItems: "Items must be unique",
53
+ enum: "Invalid value",
54
+ const: "Invalid value",
55
+ notConst: "Invalid value",
56
+ };
40
57
  // Difficult to solve
41
58
  // @ts-expect-error
42
59
  Yup.addMethod(Yup.array, "mixedOf", function ({ itemSchemas, additionalItemSchema }) {
@@ -48,7 +65,7 @@ Yup.addMethod(Yup.array, "mixedOf", function ({ itemSchemas, additionalItemSchem
48
65
  const numOfAdditionalItems = itemSchemas.length > valueLength ? 0 : valueLength - itemSchemas.length;
49
66
  const additionalSchemas = additionalItemSchema
50
67
  ? Array(numOfAdditionalItems).fill(additionalItemSchema)
51
- : [];
68
+ : Array(numOfAdditionalItems).fill(Yup.mixed());
52
69
  return Yup.tuple([...itemSchemas, ...additionalSchemas]).typeError("Required");
53
70
  });
54
71
  });
@@ -156,22 +173,39 @@ function getYupType(schema) {
156
173
  throw new Error(`createValidationSchema: Unsupported schema type: ${type}`);
157
174
  }
158
175
  }
159
- function createValidationSchema(schema, definitions = {}, conditionsContext = {}, condition) {
176
+ function createValidationSchema(schemas, options) {
177
+ const { schema, uiSchema = {} } = schemas;
178
+ const { localize, definitions, conditionsContext = {}, condition } = options ?? {};
160
179
  if (!schema?.properties) {
161
180
  return Yup.object().shape({});
162
181
  }
163
- const schemaDefinitions = {
164
- ...definitions,
165
- ...(schema.definitions ?? {}),
182
+ const getErrorMessage = (key, fieldUiSchema, params) => {
183
+ const uiError = fieldUiSchema?.["ui:validation"]?.error?.[key];
184
+ if (uiError)
185
+ return uiError;
186
+ if (localize) {
187
+ return localize(`validation.error.${key}`, params);
188
+ }
189
+ const defaultMsg = DEFAULT_ERROR_MESSAGES[key];
190
+ return typeof defaultMsg === "function" ? defaultMsg(params) : defaultMsg;
166
191
  };
192
+ const schemaDefinitions = (definitions ?? schema.definitions ?? {});
167
193
  const requiredFields = schema.required;
168
194
  function getValue(givenSchema, key, required) {
169
195
  const { schema } = (0, solveReferences_1.solveReferences)(givenSchema, schemaDefinitions, {}, {});
170
196
  const isRequired = requiredFields?.includes(key) || required === true;
171
197
  let type = schema.type;
172
198
  let yupSchema;
199
+ const fieldUiSchema = uiSchema?.[key];
173
200
  if (schema.properties) {
174
- yupSchema = createValidationSchema(schema, schemaDefinitions, conditionsContext);
201
+ yupSchema = createValidationSchema({
202
+ schema,
203
+ uiSchema: fieldUiSchema
204
+ }, {
205
+ localize,
206
+ definitions: schemaDefinitions,
207
+ conditionsContext,
208
+ });
175
209
  type = type ?? "object";
176
210
  }
177
211
  else if (type === "array" && schema.anyOf) {
@@ -186,7 +220,9 @@ function createValidationSchema(schema, definitions = {}, conditionsContext = {}
186
220
  return Yup.mixed().oneOf(anyOf.map((item) => item.const));
187
221
  }));
188
222
  // In case the property is required, there should be at least one item in the array
189
- yupSchema = isRequired ? anyOfSchema.min(1, "Required") : anyOfSchema;
223
+ yupSchema = isRequired
224
+ ? anyOfSchema.min(1, getErrorMessage("required", fieldUiSchema))
225
+ : anyOfSchema;
190
226
  }
191
227
  }
192
228
  else if (type === "array") {
@@ -218,8 +254,99 @@ function createValidationSchema(schema, definitions = {}, conditionsContext = {}
218
254
  }
219
255
  if (type === "null")
220
256
  yupSchema.nullable();
257
+ // Apply JSON Schema validation keywords based on type
258
+ if (type === "string") {
259
+ let stringSchema = yupSchema;
260
+ if (schema.minLength !== undefined) {
261
+ stringSchema = stringSchema.min(schema.minLength, getErrorMessage("minLength", fieldUiSchema, { limit: schema.minLength }));
262
+ }
263
+ if (schema.maxLength !== undefined) {
264
+ stringSchema = stringSchema.max(schema.maxLength, getErrorMessage("maxLength", fieldUiSchema, { limit: schema.maxLength }));
265
+ }
266
+ if (schema.pattern !== undefined) {
267
+ stringSchema = stringSchema.matches(new RegExp(schema.pattern), getErrorMessage("pattern", fieldUiSchema));
268
+ }
269
+ yupSchema = stringSchema;
270
+ }
271
+ if (type === "number" || type === "integer") {
272
+ let numberSchema = yupSchema;
273
+ if (schema.minimum !== undefined) {
274
+ numberSchema = numberSchema.min(schema.minimum, getErrorMessage("minimum", fieldUiSchema, { min: schema.minimum }));
275
+ }
276
+ if (schema.maximum !== undefined) {
277
+ numberSchema = numberSchema.max(schema.maximum, getErrorMessage("maximum", fieldUiSchema, { max: schema.maximum }));
278
+ }
279
+ if (schema.exclusiveMinimum !== undefined) {
280
+ numberSchema = numberSchema.moreThan(schema.exclusiveMinimum, getErrorMessage("exclusiveMinimum", fieldUiSchema, { min: schema.exclusiveMinimum }));
281
+ }
282
+ if (schema.exclusiveMaximum !== undefined) {
283
+ numberSchema = numberSchema.lessThan(schema.exclusiveMaximum, getErrorMessage("exclusiveMaximum", fieldUiSchema, { max: schema.exclusiveMaximum }));
284
+ }
285
+ if (schema.multipleOf !== undefined) {
286
+ numberSchema = numberSchema.test("multipleOf", getErrorMessage("multipleOf", fieldUiSchema, { multipleOf: schema.multipleOf }), (value) => value === undefined ||
287
+ value === null ||
288
+ value % schema.multipleOf === 0);
289
+ }
290
+ yupSchema = numberSchema;
291
+ }
292
+ if (type === "array") {
293
+ const applyArrayTests = (s) => {
294
+ let result = s;
295
+ if (schema.minItems !== undefined) {
296
+ result = result.test("minItems", getErrorMessage("minItems", fieldUiSchema, { min: schema.minItems }), (value) => !value || value.length >= schema.minItems);
297
+ }
298
+ if (schema.maxItems !== undefined) {
299
+ result = result.test("maxItems", getErrorMessage("maxItems", fieldUiSchema, { max: schema.maxItems }), (value) => !value || value.length <= schema.maxItems);
300
+ }
301
+ if (schema.uniqueItems === true) {
302
+ result = result.test("uniqueItems", getErrorMessage("uniqueItems", fieldUiSchema), (value) => {
303
+ if (!value)
304
+ return true;
305
+ return new Set(value).size === value.length;
306
+ });
307
+ }
308
+ return result;
309
+ };
310
+ if (yupSchema.test) {
311
+ yupSchema = applyArrayTests(yupSchema);
312
+ }
313
+ else {
314
+ // It's likely a Lazy schema (from mixedOf). Wrap it to apply tests.
315
+ const originalLazy = yupSchema;
316
+ yupSchema = Yup.lazy((value, options) => {
317
+ return applyArrayTests(originalLazy.resolve(options));
318
+ });
319
+ }
320
+ }
321
+ const isOptionEnum = schema.enum !== undefined;
322
+ const isOptionConst = schema.const !== undefined;
323
+ if (isOptionEnum || isOptionConst) {
324
+ // Check for $data reference (cross-field validation)
325
+ const constValue = schema.const;
326
+ const isDataRef = typeof constValue === "object" && constValue?.$data;
327
+ if (isDataRef) {
328
+ // Cross-field reference: const: { $data: "1/otherFieldName" }
329
+ // JSON Pointer format: "1/fieldName" means go up 1 level and reference fieldName
330
+ const dataPath = constValue.$data;
331
+ const pathParts = dataPath.split("/");
332
+ const refFieldName = pathParts[pathParts.length - 1]; // Get the field name (last part)
333
+ yupSchema = yupSchema.oneOf([Yup.ref(refFieldName)], getErrorMessage("const", fieldUiSchema));
334
+ }
335
+ else {
336
+ // Standard const or enum validation
337
+ const allowedValues = isOptionConst ? [schema.const] : schema.enum;
338
+ const errorKey = isOptionConst ? "const" : "enum";
339
+ yupSchema = yupSchema.oneOf(allowedValues, getErrorMessage(errorKey, fieldUiSchema));
340
+ }
341
+ }
342
+ if (schema.not &&
343
+ typeof schema.not !== "boolean" &&
344
+ schema.not.const !== undefined) {
345
+ yupSchema = yupSchema.notOneOf([schema.not.const], getErrorMessage("notConst", fieldUiSchema));
346
+ }
347
+ // JSON Schema validation keywords
221
348
  if (isRequired && type !== "array") {
222
- yupSchema = yupSchema.required("Required");
349
+ yupSchema = yupSchema.required(getErrorMessage("required", fieldUiSchema));
223
350
  }
224
351
  if (!condition) {
225
352
  return yupSchema;
@@ -257,6 +384,14 @@ function createValidationSchema(schema, definitions = {}, conditionsContext = {}
257
384
  override ones in the base, including type, presence and nullability."
258
385
  Source: https://github.com/jquense/yup?tab=readme-ov-file#schemaconcatschema-schema-schema
259
386
  */
260
- return validationSchema.concat(createValidationSchema(solvedSchemas.schema, schemaDefinitions, conditionsContext, conditionalValidation.condition));
387
+ return validationSchema.concat(createValidationSchema({
388
+ schema: solvedSchemas.schema,
389
+ uiSchema
390
+ }, {
391
+ localize,
392
+ definitions: schemaDefinitions,
393
+ conditionsContext,
394
+ condition: conditionalValidation.condition
395
+ }));
261
396
  }, baseValidationSchema);
262
397
  }
@@ -1,6 +1,23 @@
1
1
  import * as Yup from "yup";
2
2
  import { resolveOneOfValue } from "./resolveSchema";
3
3
  import { solveReferences } from "./solveReferences";
4
+ const DEFAULT_ERROR_MESSAGES = {
5
+ required: "Required",
6
+ minLength: (params) => `Must be at least ${params.limit} characters`,
7
+ maxLength: (params) => `Must be at most ${params.limit} characters`,
8
+ pattern: "Invalid format",
9
+ minimum: (params) => `Must be at least ${params.min}`,
10
+ maximum: (params) => `Must be at most ${params.max}`,
11
+ exclusiveMinimum: (params) => `Must be more than ${params.min}`,
12
+ exclusiveMaximum: (params) => `Must be less than ${params.max}`,
13
+ multipleOf: (params) => `Must be a multiple of ${params.multipleOf}`,
14
+ minItems: (params) => `Must have at least ${params.min} items`,
15
+ maxItems: (params) => `Must have at most ${params.max} items`,
16
+ uniqueItems: "Items must be unique",
17
+ enum: "Invalid value",
18
+ const: "Invalid value",
19
+ notConst: "Invalid value",
20
+ };
4
21
  // Difficult to solve
5
22
  // @ts-expect-error
6
23
  Yup.addMethod(Yup.array, "mixedOf", function ({ itemSchemas, additionalItemSchema }) {
@@ -12,7 +29,7 @@ Yup.addMethod(Yup.array, "mixedOf", function ({ itemSchemas, additionalItemSchem
12
29
  const numOfAdditionalItems = itemSchemas.length > valueLength ? 0 : valueLength - itemSchemas.length;
13
30
  const additionalSchemas = additionalItemSchema
14
31
  ? Array(numOfAdditionalItems).fill(additionalItemSchema)
15
- : [];
32
+ : Array(numOfAdditionalItems).fill(Yup.mixed());
16
33
  return Yup.tuple([...itemSchemas, ...additionalSchemas]).typeError("Required");
17
34
  });
18
35
  });
@@ -120,22 +137,39 @@ function getYupType(schema) {
120
137
  throw new Error(`createValidationSchema: Unsupported schema type: ${type}`);
121
138
  }
122
139
  }
123
- export function createValidationSchema(schema, definitions = {}, conditionsContext = {}, condition) {
140
+ export function createValidationSchema(schemas, options) {
141
+ const { schema, uiSchema = {} } = schemas;
142
+ const { localize, definitions, conditionsContext = {}, condition } = options ?? {};
124
143
  if (!schema?.properties) {
125
144
  return Yup.object().shape({});
126
145
  }
127
- const schemaDefinitions = {
128
- ...definitions,
129
- ...(schema.definitions ?? {}),
146
+ const getErrorMessage = (key, fieldUiSchema, params) => {
147
+ const uiError = fieldUiSchema?.["ui:validation"]?.error?.[key];
148
+ if (uiError)
149
+ return uiError;
150
+ if (localize) {
151
+ return localize(`validation.error.${key}`, params);
152
+ }
153
+ const defaultMsg = DEFAULT_ERROR_MESSAGES[key];
154
+ return typeof defaultMsg === "function" ? defaultMsg(params) : defaultMsg;
130
155
  };
156
+ const schemaDefinitions = (definitions ?? schema.definitions ?? {});
131
157
  const requiredFields = schema.required;
132
158
  function getValue(givenSchema, key, required) {
133
159
  const { schema } = solveReferences(givenSchema, schemaDefinitions, {}, {});
134
160
  const isRequired = requiredFields?.includes(key) || required === true;
135
161
  let type = schema.type;
136
162
  let yupSchema;
163
+ const fieldUiSchema = uiSchema?.[key];
137
164
  if (schema.properties) {
138
- yupSchema = createValidationSchema(schema, schemaDefinitions, conditionsContext);
165
+ yupSchema = createValidationSchema({
166
+ schema,
167
+ uiSchema: fieldUiSchema
168
+ }, {
169
+ localize,
170
+ definitions: schemaDefinitions,
171
+ conditionsContext,
172
+ });
139
173
  type = type ?? "object";
140
174
  }
141
175
  else if (type === "array" && schema.anyOf) {
@@ -150,7 +184,9 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
150
184
  return Yup.mixed().oneOf(anyOf.map((item) => item.const));
151
185
  }));
152
186
  // In case the property is required, there should be at least one item in the array
153
- yupSchema = isRequired ? anyOfSchema.min(1, "Required") : anyOfSchema;
187
+ yupSchema = isRequired
188
+ ? anyOfSchema.min(1, getErrorMessage("required", fieldUiSchema))
189
+ : anyOfSchema;
154
190
  }
155
191
  }
156
192
  else if (type === "array") {
@@ -182,8 +218,99 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
182
218
  }
183
219
  if (type === "null")
184
220
  yupSchema.nullable();
221
+ // Apply JSON Schema validation keywords based on type
222
+ if (type === "string") {
223
+ let stringSchema = yupSchema;
224
+ if (schema.minLength !== undefined) {
225
+ stringSchema = stringSchema.min(schema.minLength, getErrorMessage("minLength", fieldUiSchema, { limit: schema.minLength }));
226
+ }
227
+ if (schema.maxLength !== undefined) {
228
+ stringSchema = stringSchema.max(schema.maxLength, getErrorMessage("maxLength", fieldUiSchema, { limit: schema.maxLength }));
229
+ }
230
+ if (schema.pattern !== undefined) {
231
+ stringSchema = stringSchema.matches(new RegExp(schema.pattern), getErrorMessage("pattern", fieldUiSchema));
232
+ }
233
+ yupSchema = stringSchema;
234
+ }
235
+ if (type === "number" || type === "integer") {
236
+ let numberSchema = yupSchema;
237
+ if (schema.minimum !== undefined) {
238
+ numberSchema = numberSchema.min(schema.minimum, getErrorMessage("minimum", fieldUiSchema, { min: schema.minimum }));
239
+ }
240
+ if (schema.maximum !== undefined) {
241
+ numberSchema = numberSchema.max(schema.maximum, getErrorMessage("maximum", fieldUiSchema, { max: schema.maximum }));
242
+ }
243
+ if (schema.exclusiveMinimum !== undefined) {
244
+ numberSchema = numberSchema.moreThan(schema.exclusiveMinimum, getErrorMessage("exclusiveMinimum", fieldUiSchema, { min: schema.exclusiveMinimum }));
245
+ }
246
+ if (schema.exclusiveMaximum !== undefined) {
247
+ numberSchema = numberSchema.lessThan(schema.exclusiveMaximum, getErrorMessage("exclusiveMaximum", fieldUiSchema, { max: schema.exclusiveMaximum }));
248
+ }
249
+ if (schema.multipleOf !== undefined) {
250
+ numberSchema = numberSchema.test("multipleOf", getErrorMessage("multipleOf", fieldUiSchema, { multipleOf: schema.multipleOf }), (value) => value === undefined ||
251
+ value === null ||
252
+ value % schema.multipleOf === 0);
253
+ }
254
+ yupSchema = numberSchema;
255
+ }
256
+ if (type === "array") {
257
+ const applyArrayTests = (s) => {
258
+ let result = s;
259
+ if (schema.minItems !== undefined) {
260
+ result = result.test("minItems", getErrorMessage("minItems", fieldUiSchema, { min: schema.minItems }), (value) => !value || value.length >= schema.minItems);
261
+ }
262
+ if (schema.maxItems !== undefined) {
263
+ result = result.test("maxItems", getErrorMessage("maxItems", fieldUiSchema, { max: schema.maxItems }), (value) => !value || value.length <= schema.maxItems);
264
+ }
265
+ if (schema.uniqueItems === true) {
266
+ result = result.test("uniqueItems", getErrorMessage("uniqueItems", fieldUiSchema), (value) => {
267
+ if (!value)
268
+ return true;
269
+ return new Set(value).size === value.length;
270
+ });
271
+ }
272
+ return result;
273
+ };
274
+ if (yupSchema.test) {
275
+ yupSchema = applyArrayTests(yupSchema);
276
+ }
277
+ else {
278
+ // It's likely a Lazy schema (from mixedOf). Wrap it to apply tests.
279
+ const originalLazy = yupSchema;
280
+ yupSchema = Yup.lazy((value, options) => {
281
+ return applyArrayTests(originalLazy.resolve(options));
282
+ });
283
+ }
284
+ }
285
+ const isOptionEnum = schema.enum !== undefined;
286
+ const isOptionConst = schema.const !== undefined;
287
+ if (isOptionEnum || isOptionConst) {
288
+ // Check for $data reference (cross-field validation)
289
+ const constValue = schema.const;
290
+ const isDataRef = typeof constValue === "object" && constValue?.$data;
291
+ if (isDataRef) {
292
+ // Cross-field reference: const: { $data: "1/otherFieldName" }
293
+ // JSON Pointer format: "1/fieldName" means go up 1 level and reference fieldName
294
+ const dataPath = constValue.$data;
295
+ const pathParts = dataPath.split("/");
296
+ const refFieldName = pathParts[pathParts.length - 1]; // Get the field name (last part)
297
+ yupSchema = yupSchema.oneOf([Yup.ref(refFieldName)], getErrorMessage("const", fieldUiSchema));
298
+ }
299
+ else {
300
+ // Standard const or enum validation
301
+ const allowedValues = isOptionConst ? [schema.const] : schema.enum;
302
+ const errorKey = isOptionConst ? "const" : "enum";
303
+ yupSchema = yupSchema.oneOf(allowedValues, getErrorMessage(errorKey, fieldUiSchema));
304
+ }
305
+ }
306
+ if (schema.not &&
307
+ typeof schema.not !== "boolean" &&
308
+ schema.not.const !== undefined) {
309
+ yupSchema = yupSchema.notOneOf([schema.not.const], getErrorMessage("notConst", fieldUiSchema));
310
+ }
311
+ // JSON Schema validation keywords
185
312
  if (isRequired && type !== "array") {
186
- yupSchema = yupSchema.required("Required");
313
+ yupSchema = yupSchema.required(getErrorMessage("required", fieldUiSchema));
187
314
  }
188
315
  if (!condition) {
189
316
  return yupSchema;
@@ -221,6 +348,14 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
221
348
  override ones in the base, including type, presence and nullability."
222
349
  Source: https://github.com/jquense/yup?tab=readme-ov-file#schemaconcatschema-schema-schema
223
350
  */
224
- return validationSchema.concat(createValidationSchema(solvedSchemas.schema, schemaDefinitions, conditionsContext, conditionalValidation.condition));
351
+ return validationSchema.concat(createValidationSchema({
352
+ schema: solvedSchemas.schema,
353
+ uiSchema
354
+ }, {
355
+ localize,
356
+ definitions: schemaDefinitions,
357
+ conditionsContext,
358
+ condition: conditionalValidation.condition
359
+ }));
225
360
  }, baseValidationSchema);
226
361
  }
@@ -1,5 +1,7 @@
1
1
  import * as Yup from "yup";
2
2
  import { Schema } from "../common/types";
3
+ import { UiSchema } from "../common/types";
4
+ export type LocalizeType = (key: string, params?: Record<string, any>) => string;
3
5
  type Condition = {
4
6
  dependentKeys: string[];
5
7
  conditionFn: (...value: any[]) => boolean;
@@ -17,5 +19,13 @@ type ConditionsContext = {
17
19
  condition: Condition;
18
20
  yupSchema: Yup.Schema<any, any, any, "">;
19
21
  };
20
- export declare function createValidationSchema(schema: Schema, definitions?: Schema, conditionsContext?: Record<string, ConditionsContext[]>, condition?: Condition): Yup.ObjectSchema<{}, Yup.AnyObject, {}, "">;
22
+ export declare function createValidationSchema(schemas: {
23
+ schema: Schema;
24
+ uiSchema?: UiSchema;
25
+ }, options?: {
26
+ localize?: LocalizeType;
27
+ definitions?: Schema;
28
+ conditionsContext?: Record<string, ConditionsContext[]>;
29
+ condition?: Condition;
30
+ }): Yup.ObjectSchema<{}, Yup.AnyObject, {}, "">;
21
31
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "1.4.4",
3
+ "version": "2.0.1",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "anttivikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [