diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b41ff3c81..29bc735bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> - # 6.0.0-beta.14 ## @rjsf/core @@ -26,6 +25,7 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/utils - Updated `UiSchema` type to support dynamic array item UI schemas - the `items` property can now be either a `UiSchema` object or a function that returns a `UiSchema` ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706)) +- Added `title` property to `RJSFValidationError` [PR](https://github.com/rjsf-team/react-jsonschema-form/pull/4700) ## @rjsf/chakra-ui @@ -56,6 +56,10 @@ should change the heading of the (upcoming) version to include a major version b - Added comprehensive documentation for dynamic UI schema feature with TypeScript examples ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706)) - Updated array documentation to reference the new dynamic UI schema capabilities ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706)) +## @rjsf/validator-ajv8 + +- Updated `transformRJSFValidationErrors()` to include the `title` property of a field with error fixing #4504 with [PR](https://github.com/rjsf-team/react-jsonschema-form/pull/4700) + # 6.0.0-beta.13 ## @rjsf/shadcn diff --git a/packages/core/test/ArrayField.test.jsx b/packages/core/test/ArrayField.test.jsx index 86b87166ce..db67dea862 100644 --- a/packages/core/test/ArrayField.test.jsx +++ b/packages/core/test/ArrayField.test.jsx @@ -1074,6 +1074,7 @@ describe('ArrayField', () => { property: '.1', schemaPath: '#/items/type', stack: '.1 must be integer', + title: '', }, ], formData: [1, null, 3], @@ -1090,6 +1091,7 @@ describe('ArrayField', () => { property: '.1', schemaPath: '#/items/type', stack: '.1 must be integer', + title: '', }, ]); }); @@ -1291,6 +1293,7 @@ describe('ArrayField', () => { property: '.multipleChoicesList', schemaPath: '#/properties/multipleChoicesList/minItems', stack: '.multipleChoicesList must NOT have fewer than 3 items', + title: '', }, ]); }); diff --git a/packages/core/test/Form.test.jsx b/packages/core/test/Form.test.jsx index 42623dc5fb..2c047642b0 100644 --- a/packages/core/test/Form.test.jsx +++ b/packages/core/test/Form.test.jsx @@ -1780,6 +1780,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.shipping_address.street_address', schemaPath: '#/properties/shipping_address/required', stack: "must have required property 'street_address'", + title: '', }, { message: "must have required property 'city'", @@ -1788,6 +1789,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.shipping_address.city', schemaPath: '#/properties/shipping_address/required', stack: "must have required property 'city'", + title: '', }, { message: "must have required property 'state'", @@ -1796,6 +1798,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.shipping_address.state', schemaPath: '#/properties/shipping_address/required', stack: "must have required property 'state'", + title: '', }, ]); }); @@ -1852,6 +1855,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.albums', schemaPath: '#/properties/albums/minItems', stack: "'Album Titles' must NOT have fewer than 3 items", + title: 'Album Titles', }, ]); }); @@ -2061,6 +2065,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/type', stack: 'must be number', + title: '', }, ]); }); @@ -2544,6 +2549,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/minLength', stack: 'must NOT have fewer than 8 characters', + title: '', }, ]); sinon.assert.calledOnce(onError); @@ -2606,6 +2612,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/minLength', stack: 'must NOT have fewer than 8 characters', + title: '', }, ]); }); @@ -2642,6 +2649,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/minLength', stack: 'must NOT have fewer than 8 characters', + title: '', }, { message: 'must match pattern "d+"', @@ -2650,6 +2658,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/pattern', stack: 'must match pattern "d+"', + title: '', }, ]); }); @@ -2702,6 +2711,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.level1.level2', schemaPath: '#/properties/level1/properties/level2/minLength', stack: '.level1.level2 must NOT have fewer than 8 characters', + title: '', }, ]); }); @@ -2742,6 +2752,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.1', schemaPath: '#/items/minLength', stack: '.1 must NOT have fewer than 4 characters', + title: '', }, ]); }); @@ -2798,6 +2809,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.level1.1', schemaPath: '#/properties/level1/items/minLength', stack: '.level1.1 must NOT have fewer than 4 characters', + title: '', }, { message: 'must NOT have fewer than 4 characters', @@ -2806,6 +2818,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.level1.3', schemaPath: '#/properties/level1/items/minLength', stack: '.level1.3 must NOT have fewer than 4 characters', + title: '', }, ]); }); @@ -2871,6 +2884,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.outer.0.1', schemaPath: '#/properties/outer/items/items/minLength', stack: '.outer.0.1 must NOT have fewer than 4 characters', + title: '', }, { message: 'must NOT have fewer than 4 characters', @@ -2879,6 +2893,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.outer.1.0', schemaPath: '#/properties/outer/items/items/minLength', stack: '.outer.1.0 must NOT have fewer than 4 characters', + title: '', }, ]); sinon.assert.calledOnce(focusSpy); @@ -2933,6 +2948,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.1.foo', schemaPath: '#/items/properties/foo/minLength', stack: '.1.foo must NOT have fewer than 4 characters', + title: '', }, ]); }); @@ -3558,6 +3574,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '.areaCode', schemaPath: '#/properties/areaCode/format', stack: '.areaCode must match format "area-code"', + title: '', }, ]); // We use setTimeout with a delay of 0ms to allow all asynchronous operations to complete in the React component. @@ -3606,6 +3623,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/minLength', stack: 'must NOT have fewer than 8 characters', + title: '', }, { message: 'must match pattern "d+"', @@ -3614,6 +3632,7 @@ describeRepeated('Form common', (createFormComponent) => { property: '', schemaPath: '#/pattern', stack: 'must match pattern "d+"', + title: '', }, ]); }); @@ -4648,6 +4667,7 @@ describe('Form omitExtraData and liveOmit', () => { }, schemaPath: '#/required', stack: "must have required property 'foo'", + title: '', }, ]); diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index 6424aba07c..596d83025a 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -714,6 +714,7 @@ describe('ObjectField', () => { property: '', schemaPath: '#/additionalProperties', stack: 'must NOT have additional properties', + title: '', }, ]); }); diff --git a/packages/core/test/StringField.test.jsx b/packages/core/test/StringField.test.jsx index 20c7bc2028..1d5cacc1e7 100644 --- a/packages/core/test/StringField.test.jsx +++ b/packages/core/test/StringField.test.jsx @@ -808,6 +808,7 @@ describe('StringField', () => { property: '', schemaPath: '#/type', stack: 'must be string', + title: '', }, ], }); @@ -843,6 +844,7 @@ describe('StringField', () => { property: '', schemaPath: '#/type', stack: 'must be string', + title: '', }, ], }); @@ -1010,6 +1012,7 @@ describe('StringField', () => { property: '', schemaPath: '#/format', stack: 'must match format "date"', + title: '', }, ], }); @@ -1141,6 +1144,7 @@ describe('StringField', () => { property: '', schemaPath: '#/format', stack: 'must match format "time"', + title: '', }, ], }); @@ -1948,6 +1952,7 @@ describe('StringField', () => { property: '', schemaPath: '#/format', stack: 'must match format "email"', + title: '', }, ], }); @@ -2088,6 +2093,7 @@ describe('StringField', () => { property: '', schemaPath: '#/format', stack: 'must match format "uri"', + title: '', }, ], }); @@ -2209,6 +2215,7 @@ describe('StringField', () => { property: '', schemaPath: '#/format', stack: 'must match format "color"', + title: '', }, ], }); diff --git a/packages/core/test/validate.test.js b/packages/core/test/validate.test.js index 9b168ade6a..3f51e0ed12 100644 --- a/packages/core/test/validate.test.js +++ b/packages/core/test/validate.test.js @@ -50,6 +50,7 @@ describe('Validation', () => { property: 'foo', schemaPath: '#/required', stack: "must have required property 'foo'", + title: '', }, ]); }); @@ -102,6 +103,7 @@ describe('Validation', () => { property: '.foo', schemaPath: '#/properties/foo/minLength', stack: '.foo must NOT have fewer than 10 characters', + title: '', }, ]); }); @@ -249,6 +251,7 @@ describe('Validation', () => { property: '.pass2', schemaPath: '#/properties/pass2/minLength', stack: '.pass2 must NOT have fewer than 3 characters', + title: '', }, { property: '.pass2', @@ -372,6 +375,7 @@ describe('Validation', () => { property: 'foo', schemaPath: '#/required', stack: "must have required property 'foo'", + title: '', }, ]); }); @@ -467,6 +471,7 @@ describe('Validation', () => { property: '.datasetId', schemaPath: '#/properties/datasetId/pattern', stack: '.datasetId must match pattern "\\d+"', + title: '', }, ]); onError.resetHistory(); diff --git a/packages/docs/docs/usage/validation.md b/packages/docs/docs/usage/validation.md index 969be491cf..a251eaa812 100644 --- a/packages/docs/docs/usage/validation.md +++ b/packages/docs/docs/usage/validation.md @@ -382,6 +382,7 @@ Each element in the `errors` list passed to `transformErrors` is a `RJSFValidati - `property`: optional string in Javascript property accessor notation to the data path of the field with the error. For example, `.name` or `.first-name`. - `schemaPath`: optional JSON pointer to the schema of the keyword that failed validation. For example, `#/fields/firstName/required`. (Note: this may sometimes be wrong due to a [bug in ajv](https://github.com/ajv-validator/ajv/issues/512)). - `stack`: full error name, for example ".name is a required property". +- `title`: optional string containing title property for the field with error. ## Error List Display diff --git a/packages/playground/src/samples/validation.ts b/packages/playground/src/samples/validation.ts index adfd06b020..8f87096c4a 100644 --- a/packages/playground/src/samples/validation.ts +++ b/packages/playground/src/samples/validation.ts @@ -15,6 +15,11 @@ const transformErrors: ErrorTransformer = (errors) => { message: 'You need to be 18 because of some legal thing', }); } + if (error.name === 'required') { + return Object.assign({}, error, { + message: `${error.title} is a required field`, + }); + } return error; }); }; @@ -25,7 +30,12 @@ const validation: Sample = { description: 'This form defines custom validation rules checking that the two passwords match. There is also a custom validation message when submitting an age < 18, which can only be seen if HTML5 validation is turned off.', type: 'object', + required: ['firstName'], properties: { + firstName: { + title: 'First Name', + type: 'string', + }, pass1: { title: 'Password', type: 'string', diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index b4e9138f94..81bbef25be 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -212,6 +212,8 @@ export type RJSFValidationError = { schemaPath?: string; /** Full error name, for example ".name is a required property" */ stack: string; + /** The title property for the failing field*/ + title?: string; }; /** The type that describes an error in a field */ diff --git a/packages/validator-ajv8/src/processRawValidationErrors.ts b/packages/validator-ajv8/src/processRawValidationErrors.ts index 38b2c41225..9906f8e43a 100644 --- a/packages/validator-ajv8/src/processRawValidationErrors.ts +++ b/packages/validator-ajv8/src/processRawValidationErrors.ts @@ -39,6 +39,7 @@ export function transformRJSFValidationErrors< let { message = '' } = rest; let property = instancePath.replace(/\//g, '.'); let stack = `${property} ${message}`.trim(); + let uiTitle = ''; const rawPropertyNames: string[] = [ ...(params.deps?.split(', ') || []), params.missingProperty, @@ -61,10 +62,12 @@ export function transformRJSFValidationErrors< } if (uiSchemaTitle) { message = message.replace(`'${currentProperty}'`, `'${uiSchemaTitle}'`); + uiTitle = uiSchemaTitle; } else { const parentSchemaTitle = get(parentSchema, [PROPERTIES_KEY, currentProperty, 'title']); if (parentSchemaTitle) { message = message.replace(`'${currentProperty}'`, `'${parentSchemaTitle}'`); + uiTitle = parentSchemaTitle; } } }); @@ -75,11 +78,13 @@ export function transformRJSFValidationErrors< if (uiSchemaTitle) { stack = `'${uiSchemaTitle}' ${message}`.trim(); + uiTitle = uiSchemaTitle; } else { const parentSchemaTitle = parentSchema?.title; if (parentSchemaTitle) { stack = `'${parentSchemaTitle}' ${message}`.trim(); + uiTitle = parentSchemaTitle; } } } @@ -97,6 +102,7 @@ export function transformRJSFValidationErrors< params, // specific to ajv stack, schemaPath, + title: uiTitle, }; }); }