diff --git a/CHANGELOG.md b/CHANGELOG.md index f587615d1c..b594f05f06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/validator-ajv8 - Updated the validator to use the `ErrorSchemaBuilder` in the `toErrorSchema()` function to simplify the implementation +- Updated the validator to properly map missing required field errors in the `ErrorSchema`, fixing [#3260](https://github.com/rjsf-team/react-jsonschema-form/issues/3260) ## Dev / docs / playground - Fixed the documentation for `ArrayFieldItemTemplate` as part of the fix for [#3253](https://github.com/rjsf-team/react-jsonschema-form/issues/3253) diff --git a/packages/core/test/Form_test.js b/packages/core/test/Form_test.js index b852ebd296..4e0b8ce310 100644 --- a/packages/core/test/Form_test.js +++ b/packages/core/test/Form_test.js @@ -2220,7 +2220,9 @@ describeRepeated("Form common", (createFormComponent) => { field1: { __errors: ["must be number"], }, - __errors: ["must have required property 'field2'"], + field2: { + __errors: ["must have required property 'field2'"], + }, }, }, "root_field1" diff --git a/packages/core/test/validate_test.js b/packages/core/test/validate_test.js index 155bf0c407..7034fa5178 100644 --- a/packages/core/test/validate_test.js +++ b/packages/core/test/validate_test.js @@ -618,7 +618,7 @@ describe("Validation", () => { message: "must have required property 'foo'", name: "required", params: { missingProperty: "foo" }, - property: "", + property: "foo", schemaPath: "#/required", stack: "must have required property 'foo'", }, @@ -943,7 +943,7 @@ describe("Validation", () => { message: "must have required property 'foo'", name: "required", params: { missingProperty: "foo" }, - property: "", + property: "foo", schemaPath: "#/required", stack: "must have required property 'foo'", }, diff --git a/packages/validator-ajv6/test/validator.test.ts b/packages/validator-ajv6/test/validator.test.ts index 12e8727d55..38ca1e9da2 100644 --- a/packages/validator-ajv6/test/validator.test.ts +++ b/packages/validator-ajv6/test/validator.test.ts @@ -224,6 +224,71 @@ describe("AJV6Validator", () => { ]); }); }); + describe("Validating required fields", () => { + let errors: RJSFValidationError[]; + let errorSchema: ErrorSchema; + describe("formData is not provided at top level", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }; + + const formData = { pass1: "a" }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual(".pass2 is a required property"); + }); + it("should return an errorSchema", () => { + expect(errorSchema.pass2!.__errors).toHaveLength(1); + expect(errorSchema.pass2!.__errors![0]).toEqual( + "is a required property" + ); + }); + }); + describe("formData is not provided for nested child", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + properties: { + nested: { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }, + }, + }; + + const formData = { nested: { pass1: "a" } }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + ".nested.pass2 is a required property" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.nested!.pass2!.__errors).toHaveLength(1); + expect(errorSchema.nested!.pass2!.__errors![0]).toEqual( + "is a required property" + ); + }); + }); + }); describe("No custom validate function, single additionalProperties value", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; diff --git a/packages/validator-ajv8/src/validator.ts b/packages/validator-ajv8/src/validator.ts index 7802a20c67..d4038e3c12 100644 --- a/packages/validator-ajv8/src/validator.ts +++ b/packages/validator-ajv8/src/validator.ts @@ -203,7 +203,14 @@ export default class AJV8Validator< ): RJSFValidationError[] { return errors.map((e: ErrorObject) => { const { instancePath, keyword, message, params, schemaPath } = e; - const property = instancePath.replace(/\//g, "."); + let property = instancePath.replace(/\//g, "."); + let stack = `${property} ${message}`.trim(); + if ("missingProperty" in params) { + property = property + ? `${property}.${params.missingProperty}` + : params.missingProperty; + stack = message!; + } // put data in expected format return { @@ -211,7 +218,7 @@ export default class AJV8Validator< property, message, params, // specific to ajv - stack: `${property} ${message}`.trim(), + stack, schemaPath, }; }); diff --git a/packages/validator-ajv8/test/validator.test.ts b/packages/validator-ajv8/test/validator.test.ts index 8b9a0fb2a6..b90f3b4bb1 100644 --- a/packages/validator-ajv8/test/validator.test.ts +++ b/packages/validator-ajv8/test/validator.test.ts @@ -254,6 +254,73 @@ describe("AJV8Validator", () => { ]); }); }); + describe("Validating required fields", () => { + let errors: RJSFValidationError[]; + let errorSchema: ErrorSchema; + describe("formData is not provided at top level", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }; + + const formData = { pass1: "a" }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + "must have required property 'pass2'" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.pass2!.__errors).toHaveLength(1); + expect(errorSchema.pass2!.__errors![0]).toEqual( + "must have required property 'pass2'" + ); + }); + }); + describe("formData is not provided for nested child", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + properties: { + nested: { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }, + }, + }; + + const formData = { nested: { pass1: "a" } }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + "must have required property 'pass2'" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.nested!.pass2!.__errors).toHaveLength(1); + expect(errorSchema.nested!.pass2!.__errors![0]).toEqual( + "must have required property 'pass2'" + ); + }); + }); + }); describe("No custom validate function, single additionalProperties value", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; @@ -695,6 +762,73 @@ describe("AJV8Validator", () => { ]); }); }); + describe("Validating required fields", () => { + let errors: RJSFValidationError[]; + let errorSchema: ErrorSchema; + describe("formData is not provided at top level", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }; + + const formData = { pass1: "a" }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + "must have required property 'pass2'" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.pass2!.__errors).toHaveLength(1); + expect(errorSchema.pass2!.__errors![0]).toEqual( + "must have required property 'pass2'" + ); + }); + }); + describe("formData is not provided for nested child", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + properties: { + nested: { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }, + }, + }; + + const formData = { nested: { pass1: "a" } }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + "must have required property 'pass2'" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.nested!.pass2!.__errors).toHaveLength(1); + expect(errorSchema.nested!.pass2!.__errors![0]).toEqual( + "must have required property 'pass2'" + ); + }); + }); + }); describe("No custom validate function, single additionalProperties value", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; @@ -1137,6 +1271,73 @@ describe("AJV8Validator", () => { ]); }); }); + describe("Validating required fields", () => { + let errors: RJSFValidationError[]; + let errorSchema: ErrorSchema; + describe("formData is not provided at top level", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }; + + const formData = { pass1: "a" }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + "must have required property 'pass2'" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.pass2!.__errors).toHaveLength(1); + expect(errorSchema.pass2!.__errors![0]).toEqual( + "must have required property 'pass2'" + ); + }); + }); + describe("formData is not provided for nested child", () => { + beforeAll(() => { + const schema: RJSFSchema = { + type: "object", + properties: { + nested: { + type: "object", + required: ["pass1", "pass2"], + properties: { + pass1: { type: "string" }, + pass2: { type: "string" }, + }, + }, + }, + }; + + const formData = { nested: { pass1: "a" } }; + const result = validator.validateFormData(formData, schema); + errors = result.errors; + errorSchema = result.errorSchema; + }); + it("should return an error list", () => { + expect(errors).toHaveLength(1); + expect(errors[0].stack).toEqual( + "must have required property 'pass2'" + ); + }); + it("should return an errorSchema", () => { + expect(errorSchema.nested!.pass2!.__errors).toHaveLength(1); + expect(errorSchema.nested!.pass2!.__errors![0]).toEqual( + "must have required property 'pass2'" + ); + }); + }); + }); describe("No custom validate function, single additionalProperties value", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema;