@nolwenture/formik-jsonschema-form 0.23.3 → 0.24.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.
@@ -166,7 +166,14 @@ const ifThenElseExampleSchema = {
166
166
  const allOfExample = {
167
167
  type: "object",
168
168
  properties: {
169
- customerType: { type: "string" },
169
+ customerType: {
170
+ type: "string",
171
+ oneOf: [
172
+ { const: "BUSINESS", title: "Business" },
173
+ { const: "INDIVIDUAL", title: "Individual" },
174
+ { const: "UNKNOWN", title: "Unknown" },
175
+ ],
176
+ },
170
177
  },
171
178
  allOf: [
172
179
  {
@@ -234,6 +241,58 @@ const allOfExample = {
234
241
  },
235
242
  },
236
243
  };
244
+ const anyOfExample = (required = []) => ({
245
+ type: "object",
246
+ title: "",
247
+ required,
248
+ properties: {
249
+ warningSymbols: {
250
+ type: "array",
251
+ anyOf: [
252
+ {
253
+ const: "GHS01",
254
+ title: "Räjähtävä",
255
+ },
256
+ {
257
+ const: "GHS02",
258
+ title: "Syttyvä",
259
+ },
260
+ {
261
+ const: "GHS03",
262
+ title: "Hapettava",
263
+ },
264
+ {
265
+ const: "GHS04",
266
+ title: "Paineen alaiset kaasut",
267
+ },
268
+ {
269
+ const: "GHS05",
270
+ title: "Syövyttävä",
271
+ },
272
+ {
273
+ const: "GHS06",
274
+ title: "Välittömästi myrkyllinen",
275
+ },
276
+ {
277
+ const: 5678,
278
+ title: "Haitallinen / ärsyttävä / herkistävä / vaarallinen otsonikerrokselle",
279
+ },
280
+ {
281
+ const: 6789,
282
+ title: "Vakava terveysvaara",
283
+ },
284
+ {
285
+ const: 7890,
286
+ title: "Ympäristölle vaarallinen",
287
+ },
288
+ ],
289
+ items: {
290
+ type: "string",
291
+ },
292
+ uniqueItems: true,
293
+ },
294
+ },
295
+ });
237
296
  const nestedRequiredConditionalExampleSchema = {
238
297
  type: "object",
239
298
  properties: {
@@ -268,7 +327,8 @@ describe("Create Validation Schema", () => {
268
327
  var _a;
269
328
  const yupSchema = createValidationSchema(schema, definitions);
270
329
  try {
271
- yupSchema.validateSync(data, { abortEarly: false });
330
+ // Cannot be strict, since forms always use string based values
331
+ yupSchema.validateSync(data, { abortEarly: false, strict: false });
272
332
  }
273
333
  catch (err) {
274
334
  const error = err;
@@ -336,7 +396,7 @@ describe("Create Validation Schema", () => {
336
396
  age: 17,
337
397
  drinksCoffee: false,
338
398
  simpleArray: "Hello :)",
339
- }, { strict: true })).rejects.toThrow('simpleArray must be a `tuple` type, but the final value was: `"Hello :)"`.');
399
+ }, { strict: true })).rejects.toThrow("Required");
340
400
  }));
341
401
  describe("Conditionals", () => {
342
402
  it("generates validation for dependencies without oneOf", () => {
@@ -571,7 +631,7 @@ describe("Create Validation Schema", () => {
571
631
  });
572
632
  const faultyItem1 = listErrors(arraySchema, {
573
633
  arrayOfObjects: [
574
- true,
634
+ ["array"],
575
635
  123.123,
576
636
  456,
577
637
  ["array"],
@@ -580,12 +640,12 @@ describe("Create Validation Schema", () => {
580
640
  ],
581
641
  });
582
642
  expect(faultyItem1).toEqual([
583
- "arrayOfObjects[0]: must be a `string` type, but the final value was: `true`.",
643
+ 'arrayOfObjects[0]: must be a `string` type, but the final value was: `[\n "\\"array\\""\n]`.',
584
644
  ]);
585
645
  const faultyItem2 = listErrors(arraySchema, {
586
646
  arrayOfObjects: [
587
647
  "string",
588
- false,
648
+ "not a number",
589
649
  456,
590
650
  ["array"],
591
651
  false,
@@ -593,20 +653,20 @@ describe("Create Validation Schema", () => {
593
653
  ],
594
654
  });
595
655
  expect(faultyItem2).toEqual([
596
- "arrayOfObjects[1]: must be a `number` type, but the final value was: `false`.",
656
+ 'arrayOfObjects[1]: must be a `number` type, but the final value was: `NaN` (cast from the value `"not a number"`).',
597
657
  ]);
598
658
  const faultyItem3 = listErrors(arraySchema, {
599
659
  arrayOfObjects: [
600
660
  "string",
601
661
  123.123,
602
- "incorrect",
662
+ false,
603
663
  ["array"],
604
664
  false,
605
665
  { name: "John", age: 27 },
606
666
  ],
607
667
  });
608
668
  expect(faultyItem3).toEqual([
609
- 'arrayOfObjects[2]: must be a `number` type, but the final value was: `"incorrect"`.',
669
+ "arrayOfObjects[2]: must be a `number` type, but the final value was: `NaN` (cast from the value `false`).",
610
670
  ]);
611
671
  const faultyItem4 = listErrors(arraySchema, {
612
672
  arrayOfObjects: [
@@ -618,9 +678,7 @@ describe("Create Validation Schema", () => {
618
678
  { name: "John", age: 27 },
619
679
  ],
620
680
  });
621
- expect(faultyItem4).toEqual([
622
- 'arrayOfObjects[3]: must be a `tuple` type, but the final value was: `{\n "id": "123"\n}`.',
623
- ]);
681
+ expect(faultyItem4).toEqual(["arrayOfObjects[3]: Required"]);
624
682
  const faultyItem5 = listErrors(arraySchema, {
625
683
  arrayOfObjects: [
626
684
  "string",
@@ -667,9 +725,9 @@ describe("Create Validation Schema", () => {
667
725
  "arrayOfObjects[0]: Required",
668
726
  "arrayOfObjects[1]: Required",
669
727
  "arrayOfObjects[2]: Required",
670
- "arrayOfObjects[3]: tuple value has too few items, expected a length of 1 but got 0 for value: `[]`",
728
+ "arrayOfObjects[3]: Required",
671
729
  "arrayOfObjects[4]: Required",
672
- "arrayOfObjects[5]: Required",
730
+ "arrayOfObjects[5].name: Required",
673
731
  ]);
674
732
  const correct = listErrors(arraySchema, {
675
733
  arrayOfObjects: [
@@ -693,19 +751,19 @@ describe("Create Validation Schema", () => {
693
751
  ],
694
752
  });
695
753
  const faulty = listErrors(arraySchema, {
696
- arrayOfObjects: ["name", undefined, undefined, false],
754
+ arrayOfObjects: ["name", undefined, undefined, {}],
697
755
  });
698
756
  expect(faulty).toEqual([
699
757
  "arrayOfObjects[1]: Required",
700
758
  "arrayOfObjects[2]: Required",
701
- "arrayOfObjects[3]: must be a `number` type, but the final value was: `false`.",
759
+ "arrayOfObjects[3]: must be a `number` type, but the final value was: `NaN` (cast from the value `{}`).",
702
760
  ]);
703
761
  const coprrecorrect = listErrors(arraySchema, {
704
762
  arrayOfObjects: ["name", 123, true, 123.141241],
705
763
  });
706
764
  expect(coprrecorrect).toEqual(undefined);
707
765
  }));
708
- it.only("requires fields with array items and additionalItems with definitions", () => __awaiter(void 0, void 0, void 0, function* () {
766
+ it("requires fields with array items and additionalItems with definitions", () => __awaiter(void 0, void 0, void 0, function* () {
709
767
  const arraySchema = {
710
768
  title: "TITLE",
711
769
  properties: {
@@ -750,12 +808,142 @@ describe("Create Validation Schema", () => {
750
808
  ]);
751
809
  const correct = listErrors(arraySchema, {
752
810
  arrayOfDefinitions: [
753
- { commodityId: "1", insuredValue: "123" },
754
- { commodityId: "2", insuredValue: "456" },
755
- { commodityId: "3", insuredValue: "789" },
811
+ { commodityId: "1", insuredValue: 123 },
812
+ { commodityId: "2", insuredValue: 456 },
813
+ { commodityId: "3", insuredValue: 789 },
756
814
  ],
757
815
  }, definitions);
758
816
  expect(correct).toEqual(undefined);
759
817
  }));
818
+ it("validates anyOf correctly", () => __awaiter(void 0, void 0, void 0, function* () {
819
+ const optionalInput = listErrors(anyOfExample(), { warningSymbols: [] });
820
+ expect(optionalInput).toEqual(undefined);
821
+ const requiredInput = listErrors(anyOfExample(["warningSymbols"]), {
822
+ warningSymbols: [],
823
+ });
824
+ expect(requiredInput).toEqual(["warningSymbols: Required"]);
825
+ const requiredInput2 = listErrors(anyOfExample(["warningSymbols"]), {
826
+ warningSymbols: [6789],
827
+ });
828
+ expect(requiredInput2).toEqual(undefined);
829
+ const wrongInput = listErrors(anyOfExample(), {
830
+ // false and 1234 are stringified
831
+ warningSymbols: [false, {}, [], 1234, "invalid value"],
832
+ });
833
+ expect(wrongInput).toEqual([
834
+ "warningSymbols[0]: must be one of the following values: GHS01, GHS02, GHS03, GHS04, GHS05, GHS06, 5678, 6789, 7890",
835
+ "warningSymbols[1]: must be one of the following values: GHS01, GHS02, GHS03, GHS04, GHS05, GHS06, 5678, 6789, 7890",
836
+ "warningSymbols[2]: must be one of the following values: GHS01, GHS02, GHS03, GHS04, GHS05, GHS06, 5678, 6789, 7890",
837
+ "warningSymbols[3]: must be one of the following values: GHS01, GHS02, GHS03, GHS04, GHS05, GHS06, 5678, 6789, 7890",
838
+ "warningSymbols[4]: must be one of the following values: GHS01, GHS02, GHS03, GHS04, GHS05, GHS06, 5678, 6789, 7890",
839
+ ]);
840
+ const validInput = listErrors(anyOfExample(), {
841
+ warningSymbols: ["GHS01", "GHS05", 7890],
842
+ });
843
+ expect(validInput).toEqual(undefined);
844
+ }));
845
+ });
846
+ describe("Infinite loop validation", () => {
847
+ const definitions = {
848
+ Organization: {
849
+ type: "array",
850
+ additionalItems: {
851
+ type: "object",
852
+ properties: {
853
+ name: { title: "Name", type: "string" },
854
+ subOrganizations: {
855
+ $ref: "#/definitions/Organization",
856
+ },
857
+ },
858
+ },
859
+ },
860
+ };
861
+ const treeSchema = {
862
+ title: "Form editor",
863
+ type: "object",
864
+ properties: {
865
+ organization: {
866
+ type: "object",
867
+ properties: {
868
+ name: { title: "Title", type: "string" },
869
+ subOrganizations: {
870
+ $ref: "#/definitions/Organization",
871
+ },
872
+ },
873
+ },
874
+ },
875
+ };
876
+ it("does not throw RangeError: Maximum call stack size exceeded", () => __awaiter(void 0, void 0, void 0, function* () {
877
+ const validInput1 = listErrors(treeSchema, {
878
+ organization: {
879
+ name: "Org A",
880
+ subOrganizations: [],
881
+ },
882
+ }, definitions);
883
+ expect(validInput1).toEqual(undefined);
884
+ const validInput2 = listErrors(treeSchema, {
885
+ organization: {
886
+ name: "Org A",
887
+ subOrganizations: [
888
+ {
889
+ name: "Sub A1",
890
+ subOrganizations: [
891
+ {
892
+ name: "Sub A2",
893
+ subOrganizations: [
894
+ {
895
+ name: "Sub A3",
896
+ subOrganizations: [
897
+ {
898
+ name: "Sub A4",
899
+ subOrganizations: [
900
+ {
901
+ name: "Sub A5",
902
+ subOrganizations: [
903
+ {
904
+ name: "Sub A6",
905
+ subOrganizations: [
906
+ {
907
+ name: "Sub A7",
908
+ subOrganizations: [
909
+ {
910
+ name: "Sub A8",
911
+ subOrganizations: [],
912
+ },
913
+ ],
914
+ },
915
+ ],
916
+ },
917
+ ],
918
+ },
919
+ ],
920
+ },
921
+ ],
922
+ },
923
+ ],
924
+ },
925
+ ],
926
+ },
927
+ {
928
+ name: "Sub B1",
929
+ subOrganizations: [
930
+ {
931
+ name: "Sub B2",
932
+ subOrganizations: [],
933
+ },
934
+ ],
935
+ },
936
+ ],
937
+ },
938
+ }, definitions);
939
+ expect(validInput2).toEqual(undefined);
940
+ const invalidInput = listErrors(treeSchema, {
941
+ organization: {
942
+ name: "Org A",
943
+ subOrganizations: 123,
944
+ },
945
+ }, definitions);
946
+ expect(invalidInput).toEqual(["organization.subOrganizations: Required"]);
947
+ }));
760
948
  });
761
949
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ import { hasReferences } from "../hasReferences";
2
+ describe("hasReferences", () => {
3
+ it("returns false if no references are found", () => {
4
+ const schema = {
5
+ title: "Deep object",
6
+ properties: {
7
+ value: { type: "number" },
8
+ level1: {
9
+ title: "Level1",
10
+ properties: {
11
+ value: { type: "number" },
12
+ level2: {
13
+ title: "Level2",
14
+ properties: {
15
+ value: { type: "number" },
16
+ level3: {
17
+ title: "Level3",
18
+ properties: {
19
+ value: { type: "number" },
20
+ level4: {
21
+ title: "Level4",
22
+ properties: {
23
+ value: { type: "number" },
24
+ },
25
+ },
26
+ },
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ },
33
+ };
34
+ expect(hasReferences(schema)).toEqual(false);
35
+ });
36
+ it("returns true if any references are found", () => {
37
+ const schema = {
38
+ title: "Deep object",
39
+ properties: {
40
+ value: { type: "number" },
41
+ level1: {
42
+ title: "Level1",
43
+ properties: {
44
+ value: { type: "number" },
45
+ level2: {
46
+ title: "Level2",
47
+ properties: {
48
+ value: { type: "number" },
49
+ level3: {
50
+ title: "Level3",
51
+ properties: {
52
+ value: { type: "number" },
53
+ level4: {
54
+ title: "Level4",
55
+ properties: {
56
+ value: { $ref: "#/definitions/organization" },
57
+ },
58
+ },
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ },
65
+ },
66
+ };
67
+ expect(hasReferences(schema)).toEqual(true);
68
+ const schema2 = {
69
+ title: "Deep object",
70
+ properties: {
71
+ value: { type: "number" },
72
+ level1: {
73
+ title: "Level1",
74
+ properties: {
75
+ value: { type: "number" },
76
+ level2: {
77
+ title: "Level2",
78
+ properties: {
79
+ value: { $ref: "#/definitions/organization" },
80
+ level3: {
81
+ title: "Level3",
82
+ properties: {
83
+ value: { type: "number" },
84
+ level4: {
85
+ title: "Level4",
86
+ properties: {
87
+ value: { type: "number" },
88
+ },
89
+ },
90
+ },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ },
96
+ },
97
+ };
98
+ expect(hasReferences(schema2)).toEqual(true);
99
+ });
100
+ });
@@ -140,20 +140,40 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
140
140
  function getValue(givenSchema, key, required) {
141
141
  var _a, _b;
142
142
  const { schema } = solveReferences(givenSchema, schemaDefinitions, {}, {});
143
+ const isRequired = (requiredFields === null || requiredFields === void 0 ? void 0 : requiredFields.includes(key)) || required === true;
143
144
  let type = schema.type;
144
145
  let yupSchema;
145
146
  if (schema.properties) {
146
147
  yupSchema = createValidationSchema(schema, schemaDefinitions, conditionsContext);
147
148
  type = type !== null && type !== void 0 ? type : "object";
148
149
  }
150
+ else if (type === "array" && schema.anyOf) {
151
+ const anyOf = schema.anyOf;
152
+ // anyOf is optional, therefore we cannot change it to array.mixedOf() aka tuple
153
+ const anyOfSchema = Yup.array().of(Yup.lazy(() => {
154
+ // value has to match with one of the anyOf values in the schema
155
+ return Yup.mixed().oneOf(anyOf.map((item) => item.const));
156
+ }));
157
+ // In case the property is required, there should be at least one item in the array
158
+ yupSchema = isRequired ? anyOfSchema.min(1, "Required") : anyOfSchema;
159
+ }
149
160
  else if (type === "array") {
161
+ const additionalItemSchema = (() => {
162
+ const additionalItems = schema.additionalItems;
163
+ if (!additionalItems) {
164
+ return undefined;
165
+ }
166
+ // Yup.lazy() enables validation for infinitely nested schemas
167
+ // aka references that refer to themselves or in circular references
168
+ return Yup.lazy(() => {
169
+ return getValue(schema === null || schema === void 0 ? void 0 : schema.additionalItems, "#ignore-this", true);
170
+ });
171
+ })();
150
172
  const schemaItems = (_a = schema === null || schema === void 0 ? void 0 : schema.items) !== null && _a !== void 0 ? _a : [];
151
173
  const items = Array.isArray(schemaItems) ? schemaItems : [schemaItems];
152
174
  yupSchema = Yup.array().mixedOf({
153
175
  itemSchemas: items.map((item) => getValue(item, "#ignore-this", true)),
154
- additionalItemSchema: (schema === null || schema === void 0 ? void 0 : schema.additionalItems)
155
- ? getValue(schema === null || schema === void 0 ? void 0 : schema.additionalItems, "#ignore-this", true)
156
- : undefined,
176
+ additionalItemSchema,
157
177
  });
158
178
  }
159
179
  else {
@@ -161,7 +181,6 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
161
181
  }
162
182
  if (type === "null")
163
183
  yupSchema.nullable();
164
- const isRequired = (requiredFields === null || requiredFields === void 0 ? void 0 : requiredFields.includes(key)) || required === true;
165
184
  if (isRequired && type !== "array") {
166
185
  yupSchema = yupSchema.required("Required");
167
186
  }
@@ -0,0 +1,2 @@
1
+ import { Schema } from "../types";
2
+ export declare function hasReferences(schema: Schema): boolean;
@@ -0,0 +1,12 @@
1
+ export function hasReferences(schema) {
2
+ const properties = schema === null || schema === void 0 ? void 0 : schema.properties;
3
+ if (!properties)
4
+ return false;
5
+ return Object.keys(properties).some((key) => {
6
+ const value = properties[key];
7
+ if (value === null || value === void 0 ? void 0 : value.properties) {
8
+ return hasReferences(value);
9
+ }
10
+ return Object.keys(value).includes("$ref");
11
+ });
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "0.23.3",
3
+ "version": "0.24.0",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "Antti Vikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [