Skip to content

Commit 6a28075

Browse files
committed
clone value object to validate
merge without change ref of value object after validated
1 parent bf11c76 commit 6a28075

File tree

4 files changed

+187
-8
lines changed

4 files changed

+187
-8
lines changed

lib/rules/multi.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module.exports = function({ schema, messages }, path, context) {
1919
`);
2020

2121
const rule = this.getRuleFromSchema(schema.rules[i]);
22-
src.push(this.compileRule(rule, context, path, "var tmpVal = context.fn[%%INDEX%%](value, field, parent, errors, context);", "tmpVal"));
22+
src.push(this.compileRule(rule, context, path, "var tmpVal = context.fn[%%INDEX%%](context.validator.clone(value), field, parent, errors, context);", "tmpVal"));
2323
src.push(`
2424
if (errors.length == errBefore) {
2525
hasValid = true;
@@ -32,6 +32,7 @@ module.exports = function({ schema, messages }, path, context) {
3232
src.push(`
3333
if (hasValid) {
3434
errors.length = prevErrLen;
35+
context.validator.removeChildrenAndMerge(value, newVal);
3536
}
3637
3738
return newVal;

lib/validator.js

+54-4
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class Validator {
106106
// We should set default-value when value is undefined or null, not skip! (Except when null is allowed)
107107
skipUndefinedValue = false;
108108
if (rule.schema.nullable !== true) skipNullValue = false;
109-
109+
110110
let defaultValue;
111111
if (typeof rule.schema.default === "function") {
112112
if (!context.customs[rule.index]) context.customs[rule.index] = {};
@@ -120,7 +120,7 @@ class Validator {
120120
value = ${defaultValue};
121121
${resVar} = value;
122122
`;
123-
123+
124124
} else {
125125
handleNoValue = this.makeError({ type: "required", actual: "value", messages: rule.messages });
126126
}
@@ -134,6 +134,56 @@ class Validator {
134134
return src.join("\n");
135135
}
136136

137+
/**
138+
* Simple clone value
139+
*
140+
* @param {any} value
141+
*/
142+
clone(value) {
143+
if (value == null || typeof value !== "object") {
144+
// dont need to clone
145+
return value;
146+
}
147+
148+
if (Array.isArray(value)) {
149+
return value.map(val => this.clone(val));
150+
} else {
151+
return Object.keys(value).reduce((obj, key) => {
152+
obj[key] = this.clone(value[key]);
153+
return obj;
154+
}, {});
155+
}
156+
}
157+
158+
/**
159+
* replace children content without change parent reference
160+
*
161+
* @param {any} target - target need to keep ref
162+
* @param {any} vals - keep values that will merge to ref
163+
*/
164+
removeChildrenAndMerge(target, vals) {
165+
if (target == null || typeof target !== "object") {
166+
// dont need to change
167+
return;
168+
}
169+
170+
if (vals == null || typeof vals !== "object") {
171+
// unexpected, dont change
172+
return;
173+
}
174+
175+
if (Array.isArray(target) && Array.isArray(vals)) {
176+
target.splice(0, target.length, ...vals);
177+
} else {
178+
// clear children
179+
Object.keys(target).forEach(key => {
180+
delete target[key];
181+
});
182+
// merge
183+
Object.assign(target, vals);
184+
}
185+
}
186+
137187
/**
138188
* Compile a schema
139189
*
@@ -322,7 +372,7 @@ class Validator {
322372

323373
return rule;
324374
}
325-
375+
326376
/**
327377
* Parse rule from shorthand string
328378
* @param {String} str shorthand string
@@ -397,7 +447,7 @@ class Validator {
397447
* @param {String} opts.path
398448
* @param {Object} opts.schema
399449
* @param {Object} opts.context
400-
* @param {Object} opts.messages
450+
* @param {Object} opts.messages
401451
*/
402452
makeCustomValidator({ vName = "value", fnName = "custom", ruleIndex, path, schema, context, messages }) {
403453
const ruleVName = "rule" + ruleIndex;

test/typescript/validator.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ describe('TypeScript Definitions', () => {
120120
customs: expect.any(Object),
121121
rules: expect.any(Array),
122122
fn: expect.any(Array),
123-
index: 2
123+
index: 2,
124+
validator: expect.any(Validator)
124125
};
125126

126127
expect(validFn).toHaveBeenCalledTimes(1);

test/validator.spec.js

+129-2
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ describe("Test add", () => {
158158
customs: expect.any(Object),
159159
rules: expect.any(Array),
160160
fn: expect.any(Array),
161-
index: 2
161+
index: 2,
162+
validator: expect.any(Validator)
162163
};
163164

164165

@@ -496,7 +497,7 @@ describe("Test custom validation v1", () => {
496497
fn(value);
497498
if (value % 2 !== 0) return [{ type: "evenNumber", actual: value }];
498499
}
499-
},
500+
},
500501
b: {
501502
type: "number",
502503
custom(value){
@@ -696,3 +697,129 @@ describe("Test addMessage" , () => {
696697
expect(v.messages.string).toBe("C");
697698
});
698699

700+
describe("Test clone (simple)" , () => {
701+
const v = new Validator();
702+
it("should matches expected array", () => {
703+
const actual = [
704+
[
705+
1, 2, {
706+
a: 3,
707+
b:"4",
708+
c: [
709+
{
710+
d: 5,
711+
e: "6",
712+
f: false
713+
}
714+
]
715+
}
716+
]
717+
];
718+
const expected = [
719+
[
720+
1, 2, {
721+
a: 3,
722+
b:"4",
723+
c: [
724+
{
725+
d: 5,
726+
e: "6",
727+
f: false
728+
}
729+
]
730+
}
731+
]
732+
];
733+
expect(v.clone(actual)).toMatchObject(expected);
734+
});
735+
it("should matches expected object", () => {
736+
const actual = {
737+
a: "1",
738+
b: 2,
739+
c: true,
740+
d: [
741+
{
742+
e: "3",
743+
f: 4,
744+
g: {
745+
h: "5",
746+
i: false,
747+
j: [
748+
{
749+
k: "7",
750+
l: 8
751+
}
752+
]
753+
}
754+
}
755+
]
756+
};
757+
const expected = {
758+
a: "1",
759+
b: 2,
760+
c: true,
761+
d: [
762+
{
763+
e: "3",
764+
f: 4,
765+
g: {
766+
h: "5",
767+
i: false,
768+
j: [
769+
{
770+
k: "7",
771+
l: 8
772+
}
773+
]
774+
}
775+
}
776+
]
777+
};
778+
expect(v.clone(actual)).toMatchObject(expected);
779+
});
780+
});
781+
782+
describe("Test replaceAndMerge" , () => {
783+
const v = new Validator();
784+
it("should work on array", () => {
785+
const target = [1,2,3,4];
786+
const ref = target; // keep reference
787+
const vals = [
788+
[
789+
1, 2, {
790+
a: 3,
791+
b:"4",
792+
c: [
793+
{
794+
d: 5,
795+
e: "6",
796+
f: false
797+
}
798+
]
799+
}
800+
]
801+
];
802+
v.removeChildrenAndMerge(target, vals);
803+
expect(ref).toBe(target);
804+
expect(target).toMatchObject(vals);
805+
});
806+
it("should work on object", () => {
807+
const target = {a: "1"};
808+
const ref = target; // keep reference
809+
const vals = {
810+
a: 3,
811+
b:"4",
812+
c: [
813+
{
814+
d: 5,
815+
e: "6",
816+
f: false
817+
}
818+
]
819+
};
820+
v.removeChildrenAndMerge(target, vals);
821+
expect(ref).toBe(target);
822+
expect(target).toMatchObject(vals);
823+
});
824+
});
825+

0 commit comments

Comments
 (0)