Skip to content

Commit

Permalink
Merge pull request #58 from croz-ltd/feature_upgradeYup_v1.20
Browse files Browse the repository at this point in the history
Upgrade yup to version 1.4.0
  • Loading branch information
jtomic-croz authored Apr 4, 2024
2 parents 8dd3c3c + d2fe264 commit f728d1f
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-experts-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@croz/nrich-form-configuration-core": major
---

Upgrade yup to v1.4.0, add custom deep merge function for merging yup schemas
3 changes: 2 additions & 1 deletion libs/form-configuration/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "CROZ",
"bugs": "https://github.com/croz-ltd/nrich-frontend/issues",
"dependencies": {
"yup": "^0.32.11",
"yup": "^1.4.0",
"zustand": "^4.4.7"
},
"devDependencies": {
Expand All @@ -18,6 +18,7 @@
"eslint": "^8.2.0",
"eslint-config-nrich": "*",
"jest": "^28.1.0",
"lodash": "^4.17.21",
"msw": "^0.48.1",
"react": "^18.1.0",
"react-dom": "^18.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*
*/

import _mergeWith from "lodash/mergeWith";
import * as yup from "yup";

import { ConstrainedPropertyClientValidatorConfiguration, ConstrainedPropertyConfiguration, ValidatorConverter } from "../api";
Expand All @@ -35,8 +36,7 @@ export class FormConfigurationValidationConverter {
},
{
supports: (configuration) => ["Size", "Length"].includes(configuration.name),
convert: (configuration, validator) => validator.min(configuration.argumentMap.min, configuration.errorMessage)
.max(configuration.argumentMap.max, configuration.errorMessage),
convert: (configuration, validator) => validator.min(configuration.argumentMap.min, configuration.errorMessage).max(configuration.argumentMap.max, configuration.errorMessage),
},
{
supports: (configuration) => ["Pattern"].includes(configuration.name),
Expand Down Expand Up @@ -84,14 +84,15 @@ export class FormConfigurationValidationConverter {
return;
}

const validator = property.validatorList.reduce((previousValidator, validatorConfiguration) => this.applyConverter(validatorConfiguration, previousValidator), yupValidation().nullable());
const validator = property.validatorList
.reduce((previousValidator, validatorConfiguration) => this.applyConverter(validatorConfiguration, previousValidator), yupValidation().default(undefined).nullable());
const [propertyName, restOfPathList] = FormConfigurationValidationConverter.convertPath(property.path);

if (restOfPathList.length > 0) {
const currentPathSchema = [...restOfPathList].reverse()
.reduce((currentShape, path) => ({ [path]: yup.object().shape(currentShape) }), { [propertyName]: validator });
.reduce((currentShape, path) => ({ [path]: yup.object().shape(currentShape).default(undefined).nullable() }), { [propertyName]: validator });

schema = schema.concat(yup.object().shape(currentPathSchema));
schema = this.mergeSchemas(schema, yup.object().shape(currentPathSchema));
}
else {
const currentPropertySchema = yup.object().shape({ [propertyName]: validator });
Expand All @@ -103,6 +104,53 @@ export class FormConfigurationValidationConverter {
return schema;
}

// Function to recursively merge two Yup schemas
mergeSchemas(schema1: yup.ObjectSchema<any>, schema2: yup.ObjectSchema<any>) {
// Recursive helper function to merge two schema objects
const mergeObjects = (obj1, obj2) => {
const merged = { ...obj1 };

Object.keys(obj2).forEach((key) => {
if (Object.prototype.hasOwnProperty.call(merged, key)) {
// If both properties are objects, merge recursively
if (obj1[key].type === "object" && obj2[key].type === "object") {
merged[key] = this.mergeSchemas(obj1[key], obj2[key]);
merged[key].spec = _mergeWith(obj1[key].spec, obj2[key].spec, (field1, field2) => (typeof field1 === "boolean" ? field1 && field2 : field1 ?? field2));
}
else if (obj1[key].type === "array" && obj2[key].type === "array") {
if (obj1[key].innerType.type === "object" && obj2[key].innerType.type === "object") {
merged[key] = yup.array().of(this.mergeSchemas(obj1[key].innerType, obj2[key].innerType));
}
else {
merged[key] = yup.array().of(obj2[key].innerType);
}
}
else {
merged[key] = obj2[key];
}
}
else {
// Otherwise, add the property to the merged object
merged[key] = obj2[key];
}
});

return merged;
};

// Extract the fields of schema1
const fields1 = schema1.fields;

// Extract the fields of schema2
const fields2 = schema2.fields;

// Merge the fields recursively
const mergedFields = mergeObjects(fields1, fields2);

// Create a new merged schema
return yup.object().shape(mergedFields);
}

private applyConverter(validatorConfiguration: ConstrainedPropertyClientValidatorConfiguration, validator: any): any {
const converter = this.resolveConverter(validatorConfiguration);
let resolvedValidator = validator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*
*/

import * as yup from "yup";

import { FormConfigurationValidationConverter } from "../../src/converter";
import {
createComplexValidationList, createCustomValidationList, createNestedValidationList, createSimpleNullableValidationList, createSimpleValidationList, invalidValidationConfiguration,
Expand Down Expand Up @@ -130,3 +132,142 @@ describe("@croz/nrich-form-configuration-core/FormConfigurationValidationConvert
expect(result.isValidSync({ username: null })).toBe(true);
});
});

it.each([
[
{
schema1: yup.object().shape({ firstName: yup.string().required() }),
schema2: yup.object().shape({ lastName: yup.string() }),
},
{
expectedResult: yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string(),
}),
},
],

[
{
schema1: yup.object().shape({ firstName: yup.string().required() }),
schema2: yup.object().shape({}),
},
{
expectedResult: yup.object().shape({ firstName: yup.string().required() }),
},
],

[
{
schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.string().required() }) }) }),
schema2: yup.object().shape({ id: yup.number() }),
},
{
expectedResult: yup.object().shape({ id: yup.number(), user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.string().required() }) }) }),
},
],

[
{
schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.string().required() }) }) }),
schema2: yup.object().shape({ user: yup.object().shape({ address: yup.object().shape({ city: yup.string().required() }) }) }),
},
{
expectedResult: yup.object().shape({
user: yup.object().shape({
username: yup.string(),
address: yup.object().shape({
street: yup.string().required(),
city: yup.string().required(),
}),
}),
}),
},
],

[
{
schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.object().shape({ streetName: yup.string() }) }) }) }),
schema2: yup.object().shape({ user: yup.object().shape({ address: yup.object().shape({ city: yup.string().required() }) }) }),
},
{
expectedResult: yup.object().shape({
user: yup.object().shape({
username: yup.string(),
address: yup.object().shape({
street: yup.object().shape({
streetName: yup.string(),
}),
city: yup.string().required(),
}),
}),
}),
},
],

[
{
schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string() }) }),
schema2: yup.object().shape({ user: yup.object().shape({ address: yup.object().shape({ city: yup.string().required() }).default(undefined).nullable() }) }),
},
{
expectedResult: yup.object().shape({
user: yup.object().shape({
username: yup.string(),
address: yup.object().shape({
city: yup.string().required(),
}).default(undefined).nullable(),
}),
}),
},
],

[
{
schema1: yup.object().shape({ firstName: yup.string().required() }),
schema2: yup.object().shape({ firstName: yup.string() }),
},
{ expectedResult: yup.object().shape({ firstName: yup.string() }) },
],

[
{
schema1: yup.object().shape({ todos: yup.array().of(yup.string()) }),
schema2: yup.object().shape({ todos: yup.array().of(yup.number()) }),
},
{ expectedResult: yup.object().shape({ todos: yup.array().of(yup.number()) }) },
],

[
{
schema1: yup.object().shape({ todos: yup.array().of(yup.string()) }),
schema2: yup.object().shape({ todos: yup.array().of(yup.object().shape({ name: yup.string() })) }),
},
{ expectedResult: yup.object().shape({ todos: yup.array().of(yup.object().shape({ name: yup.string() })) }) },
],

[
{
schema1: yup.object().shape({ todos: yup.array().of(yup.object().shape({ name: yup.string() })) }),
schema2: yup.object().shape({ todos: yup.array().of(yup.string()) }),
},
{ expectedResult: yup.object().shape({ todos: yup.array().of(yup.string()) }) },
],

[
{
schema1: yup.object().shape({ test: yup.object().shape({ prop: yup.string().required() }).required() }),
schema2: yup.object().shape({ test: yup.object().shape({ prop2: yup.string() }).optional() }),
},
{ expectedResult: yup.object().shape({ test: yup.object().shape({ prop: yup.string().required(), prop2: yup.string() }).required() }) },
],
])("should merge schemas %p correctly, and get result %p", (schemas, expectedResult) => {
// given
const converter = new FormConfigurationValidationConverter();

// when
const mergedSchema = converter.mergeSchemas(schemas.schema1, schemas.schema2);

// then
expect(mergedSchema.describe()).toEqual(expectedResult.expectedResult.describe());
});
58 changes: 21 additions & 37 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ __metadata:
languageName: node
linkType: hard

"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
version: 7.20.1
resolution: "@babel/runtime@npm:7.20.1"
dependencies:
Expand Down Expand Up @@ -703,12 +703,13 @@ __metadata:
eslint: ^8.2.0
eslint-config-nrich: "*"
jest: ^28.1.0
lodash: ^4.17.21
msw: ^0.48.1
react: ^18.1.0
react-dom: ^18.1.0
tsup: ^6.5.0
whatwg-fetch: ^3.6.2
yup: ^0.32.11
yup: ^1.4.0
zustand: ^4.4.7
peerDependencies:
react: ">=16.8.0"
Expand Down Expand Up @@ -1867,13 +1868,6 @@ __metadata:
languageName: node
linkType: hard

"@types/lodash@npm:^4.14.175":
version: 4.14.189
resolution: "@types/lodash@npm:4.14.189"
checksum: 096d1e0954794fb76bd6a0ea443067f02004b53e3c59ac6e3fed88817a1468fc2e6614abdf44341f5e0fdff5f9a3c94a934cc9ff7126e07efbcd94355a21fe45
languageName: node
linkType: hard

"@types/minimist@npm:^1.2.0":
version: 1.2.2
resolution: "@types/minimist@npm:1.2.2"
Expand Down Expand Up @@ -6222,13 +6216,6 @@ __metadata:
languageName: node
linkType: hard

"lodash-es@npm:^4.17.21":
version: 4.17.21
resolution: "lodash-es@npm:4.17.21"
checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2
languageName: node
linkType: hard

"lodash.memoize@npm:4.x":
version: 4.1.2
resolution: "lodash.memoize@npm:4.1.2"
Expand Down Expand Up @@ -6667,13 +6654,6 @@ __metadata:
languageName: node
linkType: hard

"nanoclone@npm:^0.2.1":
version: 0.2.1
resolution: "nanoclone@npm:0.2.1"
checksum: 96b2954e22f70561f41e20d69856266c65583c2a441dae108f1dc71b716785d2c8038dac5f1d5e92b117aed3825f526b53139e2e5d6e6db8a77cfa35b3b8bf40
languageName: node
linkType: hard

"natural-compare-lite@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare-lite@npm:1.4.0"
Expand Down Expand Up @@ -7284,10 +7264,10 @@ __metadata:
languageName: node
linkType: hard

"property-expr@npm:^2.0.4":
version: 2.0.5
resolution: "property-expr@npm:2.0.5"
checksum: 4ebe82ce45aaf1527e96e2ab84d75d25217167ec3ff6378cf83a84fb4abc746e7c65768a79d275881602ae82f168f9a6dfaa7f5e331d0fcc83d692770bcce5f1
"property-expr@npm:^2.0.5":
version: 2.0.6
resolution: "property-expr@npm:2.0.6"
checksum: 89977f4bb230736c1876f460dd7ca9328034502fd92e738deb40516d16564b850c0bbc4e052c3df88b5b8cd58e51c93b46a94bea049a3f23f4a022c038864cab
languageName: node
linkType: hard

Expand Down Expand Up @@ -8288,6 +8268,13 @@ __metadata:
languageName: node
linkType: hard

"tiny-case@npm:^1.0.3":
version: 1.0.3
resolution: "tiny-case@npm:1.0.3"
checksum: 3f7a30c39d5b0e1bc097b0b271bec14eb5b836093db034f35a0de26c14422380b50dc12bfd37498cf35b192f5df06f28a710712c87ead68872a9e37ad6f6049d
languageName: node
linkType: hard

"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"
Expand Down Expand Up @@ -9221,18 +9208,15 @@ __metadata:
languageName: node
linkType: hard

"yup@npm:^0.32.11":
version: 0.32.11
resolution: "yup@npm:0.32.11"
"yup@npm:^1.4.0":
version: 1.4.0
resolution: "yup@npm:1.4.0"
dependencies:
"@babel/runtime": ^7.15.4
"@types/lodash": ^4.14.175
lodash: ^4.17.21
lodash-es: ^4.17.21
nanoclone: ^0.2.1
property-expr: ^2.0.4
property-expr: ^2.0.5
tiny-case: ^1.0.3
toposort: ^2.0.2
checksum: 43a16786b47cc910fed4891cebdd89df6d6e31702e9462e8f969c73eac88551ce750732608012201ea6b93802c8847cb0aa27b5d57370640f4ecf30f9f97d4b0
type-fest: ^2.19.0
checksum: 20a2ee0c1e891979ca16b34805b3a3be9ab4bea6ea3d2f9005b998b4dc992d0e4d7b53e5f4d8d9423420046630fb44fdf0ecf7e83bc34dd83392bca046c5229d
languageName: node
linkType: hard

Expand Down

0 comments on commit f728d1f

Please sign in to comment.