diff --git a/packages/core/src/validate.js b/packages/core/src/validate.js index 73046208df..ffb0b8173b 100644 --- a/packages/core/src/validate.js +++ b/packages/core/src/validate.js @@ -80,21 +80,22 @@ function toErrorSchema(errors) { }, {}); } -export function toErrorList(errorSchema, fieldName = "root") { - // XXX: We should transform fieldName as a full field path string. +export function toErrorList(errorSchema, fieldPath = []) { let errorList = []; if ("__errors" in errorSchema) { errorList = errorList.concat( errorSchema.__errors.map(stack => { + const property = "." + fieldPath.join("."); return { - stack: `${fieldName}: ${stack}`, + property, + stack: `${property} ${stack}`, }; }) ); } return Object.keys(errorSchema).reduce((acc, key) => { if (key !== "__errors") { - acc = acc.concat(toErrorList(errorSchema[key], key)); + acc = acc.concat(toErrorList(errorSchema[key], [...fieldPath, key])); } return acc; }, errorList); @@ -252,10 +253,10 @@ export default function validateFormData( const errorHandler = customValidate(formData, createErrorHandler(formData)); const userErrorSchema = unwrapErrorHandler(errorHandler); const newErrorSchema = mergeObjects(errorSchema, userErrorSchema, true); - // XXX: The errors list produced is not fully compliant with the format - // exposed by the jsonschema lib, which contains full field paths and other - // properties. - const newErrors = toErrorList(newErrorSchema); + + // Append the user's errors to the current error list + // Maintain the original error list to keep its AJV-provided detail properties. + const newErrors = [].concat(errors, toErrorList(userErrorSchema)); return { errors: newErrors, diff --git a/packages/core/test/validate_test.js b/packages/core/test/validate_test.js index badfac8362..e8c6c291a0 100644 --- a/packages/core/test/validate_test.js +++ b/packages/core/test/validate_test.js @@ -229,6 +229,7 @@ describe("Validation", () => { properties: { pass1: { type: "string" }, pass2: { type: "string" }, + numberWithMinimum: { type: "number", minimum: 5 }, }, }; @@ -239,20 +240,23 @@ describe("Validation", () => { } return errors; }; - const formData = { pass1: "a", pass2: "b" }; + const formData = { pass1: "a", pass2: "b", numberWithMinimum: 2 }; const result = validateFormData(formData, schema, validate); errors = result.errors; errorSchema = result.errorSchema; }); it("should return an error list", () => { - expect(errors).to.have.length.of(1); - expect(errors[0].stack).eql("pass2: passwords don't match."); + expect(errors).to.have.length.of(2); + expect(errors[0].stack).eql(".numberWithMinimum should be >= 5"); + expect(errors[1].stack).eql(".pass2 passwords don't match."); }); it("should return an errorSchema", () => { expect(errorSchema.pass2.__errors).to.have.length.of(1); expect(errorSchema.pass2.__errors[0]).eql("passwords don't match."); + expect(errorSchema.numberWithMinimum.__errors).to.have.length.of(1); + expect(errorSchema.numberWithMinimum.__errors[0]).eql("should be >= 5"); }); }); @@ -335,11 +339,11 @@ describe("Validation", () => { }, }) ).eql([ - { stack: "root: err1" }, - { stack: "root: err2" }, - { stack: "b: err3" }, - { stack: "b: err4" }, - { stack: "c: err5" }, + { property: ".", stack: ". err1" }, + { property: ".", stack: ". err2" }, + { property: ".a.b", stack: ".a.b err3" }, + { property: ".a.b", stack: ".a.b err4" }, + { property: ".c", stack: ".c err5" }, ]); }); }); @@ -502,7 +506,7 @@ describe("Validation", () => { submitForm(node); sinon.assert.calledWithMatch(onError.lastCall, [ - { stack: "root: Invalid" }, + { property: ".", stack: ". Invalid" }, ]); }); @@ -529,7 +533,7 @@ describe("Validation", () => { sinon.assert.calledWithMatch(onChange.lastCall, { errorSchema: { __errors: ["Invalid"] }, - errors: [{ stack: "root: Invalid" }], + errors: [{ property: ".", stack: ". Invalid" }], formData: "1234", }); }); @@ -611,8 +615,18 @@ describe("Validation", () => { }); submitForm(node); sinon.assert.calledWithMatch(onError.lastCall, [ - { stack: "pass2: should NOT be shorter than 3 characters" }, - { stack: "pass2: Passwords don't match" }, + { + message: "should NOT be shorter than 3 characters", + name: "minLength", + params: { limit: 3 }, + property: ".pass2", + schemaPath: "#/properties/pass2/minLength", + stack: ".pass2 should NOT be shorter than 3 characters", + }, + { + property: ".pass2", + stack: ".pass2 Passwords don't match", + }, ]); }); @@ -650,7 +664,7 @@ describe("Validation", () => { submitForm(node); sinon.assert.calledWithMatch(onError.lastCall, [ - { stack: "pass2: Passwords don't match" }, + { property: ".0.pass2", stack: ".0.pass2 Passwords don't match" }, ]); }); @@ -678,7 +692,7 @@ describe("Validation", () => { }); submitForm(node); sinon.assert.calledWithMatch(onError.lastCall, [ - { stack: "root: Forbidden value: bbb" }, + { property: ".", stack: ". Forbidden value: bbb" }, ]); }); });