Skip to content

Commit 4359bd1

Browse files
authored
Fix issue where ui:title in anyOf/oneOf is not shown in error messages (#4398)
* Fixed issue where ui:title in anyOf/oneOf is not shown in error messages. Fixes #4368. * Use replace() instead of replaceAll() to support Node v14.
1 parent 433f99b commit 4359bd1

File tree

3 files changed

+254
-1
lines changed

3 files changed

+254
-1
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ should change the heading of the (upcoming) version to include a major version b
2222

2323
- Fix default value population when switching between options in `MultiSchemaField` [#4375](https://github.com/rjsf-team/react-jsonschema-form/pull/4375). Fixes [#4367](https://github.com/rjsf-team/react-jsonschema-form/issues/4367)
2424

25+
## @rjsf/validator-ajv8
26+
27+
- Fixed issue where `ui:title` in anyOf/oneOf is not shown in error messages. Fixes [#4368](https://github.com/rjsf-team/react-jsonschema-form/issues/4368)
28+
2529
# 5.23.1
2630

2731
## @rjsf/chakra-ui

packages/validator-ajv8/src/processRawValidationErrors.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ export function transformRJSFValidationErrors<
4040
if ('missingProperty' in params) {
4141
property = property ? `${property}.${params.missingProperty}` : params.missingProperty;
4242
const currentProperty: string = params.missingProperty;
43-
const uiSchemaTitle = getUiOptions(get(uiSchema, `${property.replace(/^\./, '')}`)).title;
43+
let uiSchemaTitle = getUiOptions(get(uiSchema, `${property.replace(/^\./, '')}`)).title;
44+
if (uiSchemaTitle === undefined) {
45+
const uiSchemaPath = schemaPath
46+
.replace(/\/properties\//g, '/')
47+
.split('/')
48+
.slice(1, -1)
49+
.concat([currentProperty]);
50+
uiSchemaTitle = getUiOptions(get(uiSchema, uiSchemaPath)).title;
51+
}
4452

4553
if (uiSchemaTitle) {
4654
message = message.replace(`'${currentProperty}'`, `'${uiSchemaTitle}'`);

packages/validator-ajv8/test/validator.test.ts

+241
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,247 @@ describe('AJV8Validator', () => {
13441344
expect(errorSchema.numberOfChildren!.__errors![0]).toEqual('must match pattern "\\d+"');
13451345
});
13461346
});
1347+
describe('title is in validation message when it is in the uiSchema ui:title field with anyOf', () => {
1348+
beforeAll(() => {
1349+
const schema: RJSFSchema = {
1350+
anyOf: [
1351+
{
1352+
type: 'object',
1353+
required: ['firstName', 'lastName'],
1354+
properties: {
1355+
firstName: { type: 'string', title: 'First Name' },
1356+
lastName: { type: 'string', title: 'Last Name' },
1357+
},
1358+
},
1359+
],
1360+
};
1361+
const uiSchema: UiSchema = {
1362+
anyOf: [
1363+
{
1364+
firstName: {
1365+
'ui:title': 'uiSchema First Name',
1366+
},
1367+
lastName: {
1368+
'ui:title': 'uiSchema Last Name',
1369+
},
1370+
},
1371+
],
1372+
};
1373+
1374+
const formData = { firstName: 'a' };
1375+
const result = validator.validateFormData(formData, schema, undefined, undefined, uiSchema);
1376+
errors = result.errors;
1377+
errorSchema = result.errorSchema;
1378+
});
1379+
it('should return an error list', () => {
1380+
expect(errors).toHaveLength(2);
1381+
1382+
const expected = ["must have required property 'uiSchema Last Name'", 'must match a schema in anyOf'];
1383+
1384+
const messages = errors.map((e) => e.message);
1385+
expect(messages).toEqual(expected);
1386+
1387+
const stack = errors.map((e) => e.stack);
1388+
expect(stack).toEqual(expected);
1389+
});
1390+
it('should return an errorSchema', () => {
1391+
expect(errorSchema.lastName!.__errors).toHaveLength(1);
1392+
expect(errorSchema.lastName!.__errors![0]).toEqual("must have required property 'uiSchema Last Name'");
1393+
1394+
expect(errorSchema.__errors).toHaveLength(1);
1395+
expect(errorSchema.__errors![0]).toEqual('must match a schema in anyOf');
1396+
});
1397+
});
1398+
describe('title is in validation message when it is in the uiSchema ui:title field with oneOf', () => {
1399+
beforeAll(() => {
1400+
const schema: RJSFSchema = {
1401+
oneOf: [
1402+
{
1403+
type: 'object',
1404+
required: ['firstName', 'lastName'],
1405+
properties: {
1406+
firstName: { type: 'string', title: 'First Name' },
1407+
lastName: { type: 'string', title: 'Last Name' },
1408+
},
1409+
},
1410+
],
1411+
};
1412+
const uiSchema: UiSchema = {
1413+
oneOf: [
1414+
{
1415+
firstName: {
1416+
'ui:title': 'uiSchema First Name',
1417+
},
1418+
lastName: {
1419+
'ui:title': 'uiSchema Last Name',
1420+
},
1421+
},
1422+
],
1423+
};
1424+
1425+
const formData = { firstName: 'a' };
1426+
const result = validator.validateFormData(formData, schema, undefined, undefined, uiSchema);
1427+
errors = result.errors;
1428+
errorSchema = result.errorSchema;
1429+
});
1430+
it('should return an error list', () => {
1431+
expect(errors).toHaveLength(2);
1432+
1433+
const expected = [
1434+
"must have required property 'uiSchema Last Name'",
1435+
'must match exactly one schema in oneOf',
1436+
];
1437+
1438+
const messages = errors.map((e) => e.message);
1439+
expect(messages).toEqual(expected);
1440+
1441+
const stack = errors.map((e) => e.stack);
1442+
expect(stack).toEqual(expected);
1443+
});
1444+
it('should return an errorSchema', () => {
1445+
expect(errorSchema.lastName!.__errors).toHaveLength(1);
1446+
expect(errorSchema.lastName!.__errors![0]).toEqual("must have required property 'uiSchema Last Name'");
1447+
1448+
expect(errorSchema.__errors).toHaveLength(1);
1449+
expect(errorSchema.__errors![0]).toEqual('must match exactly one schema in oneOf');
1450+
});
1451+
});
1452+
describe('ui:title is in validation message when it is defined in referrer', () => {
1453+
beforeAll(() => {
1454+
const schema: RJSFSchema = {
1455+
definitions: {
1456+
address: {
1457+
type: 'object',
1458+
properties: {
1459+
streetAddress: { type: 'string' },
1460+
city: { type: 'string' },
1461+
state: { type: 'string' },
1462+
},
1463+
required: ['streetAddress', 'city', 'state'],
1464+
},
1465+
},
1466+
type: 'object',
1467+
required: ['billingAddress', 'shippingAddress'],
1468+
properties: {
1469+
billingAddress: {
1470+
$ref: '#/definitions/address',
1471+
},
1472+
shippingAddress: {
1473+
$ref: '#/definitions/address',
1474+
},
1475+
},
1476+
};
1477+
const uiSchema: UiSchema = {
1478+
billingAddress: {
1479+
'ui:title': 'uiSchema Billing Address',
1480+
},
1481+
shippingAddress: {
1482+
city: {
1483+
'ui:title': 'uiSchema City',
1484+
},
1485+
},
1486+
};
1487+
1488+
const formData = { shippingAddress: { streetAddress: 'El Camino Real', state: 'California' } };
1489+
const result = validator.validateFormData(formData, schema, undefined, undefined, uiSchema);
1490+
errors = result.errors;
1491+
errorSchema = result.errorSchema;
1492+
});
1493+
it('should return an error list', () => {
1494+
expect(errors).toHaveLength(2);
1495+
1496+
const expected = [
1497+
"must have required property 'uiSchema Billing Address'",
1498+
"must have required property 'uiSchema City'",
1499+
];
1500+
1501+
const messages = errors.map((e) => e.message);
1502+
expect(messages).toEqual(expected);
1503+
1504+
const stack = errors.map((e) => e.stack);
1505+
expect(stack).toEqual(expected);
1506+
});
1507+
it('should return an errorSchema', () => {
1508+
expect(errorSchema.billingAddress!.__errors).toHaveLength(1);
1509+
expect(errorSchema.billingAddress!.__errors![0]).toEqual(
1510+
"must have required property 'uiSchema Billing Address'"
1511+
);
1512+
1513+
expect(errorSchema.shippingAddress!.city!.__errors).toHaveLength(1);
1514+
expect(errorSchema.shippingAddress!.city!.__errors![0]).toEqual(
1515+
"must have required property 'uiSchema City'"
1516+
);
1517+
});
1518+
});
1519+
describe('ui:title is in validation message when it is defined in definitions', () => {
1520+
beforeAll(() => {
1521+
const schema: RJSFSchema = {
1522+
definitions: {
1523+
address: {
1524+
type: 'object',
1525+
properties: {
1526+
streetAddress: { type: 'string' },
1527+
city: { type: 'string' },
1528+
state: { type: 'string' },
1529+
},
1530+
required: ['streetAddress', 'city', 'state'],
1531+
},
1532+
},
1533+
type: 'object',
1534+
required: ['billingAddress', 'shippingAddress'],
1535+
properties: {
1536+
billingAddress: {
1537+
$ref: '#/definitions/address',
1538+
},
1539+
shippingAddress: {
1540+
$ref: '#/definitions/address',
1541+
},
1542+
},
1543+
};
1544+
const uiSchema: UiSchema = {
1545+
definitions: {
1546+
address: {
1547+
city: {
1548+
'ui:title': 'uiSchema City',
1549+
},
1550+
},
1551+
},
1552+
billingAddress: {
1553+
'ui:title': 'uiSchema Billing Address',
1554+
},
1555+
};
1556+
1557+
const formData = { shippingAddress: { streetAddress: 'El Camino Real', state: 'California' } };
1558+
const result = validator.validateFormData(formData, schema, undefined, undefined, uiSchema);
1559+
errors = result.errors;
1560+
errorSchema = result.errorSchema;
1561+
});
1562+
it('should return an error list', () => {
1563+
expect(errors).toHaveLength(2);
1564+
1565+
const expected = [
1566+
"must have required property 'uiSchema Billing Address'",
1567+
"must have required property 'uiSchema City'",
1568+
];
1569+
1570+
const messages = errors.map((e) => e.message);
1571+
expect(messages).toEqual(expected);
1572+
1573+
const stack = errors.map((e) => e.stack);
1574+
expect(stack).toEqual(expected);
1575+
});
1576+
it('should return an errorSchema', () => {
1577+
expect(errorSchema.billingAddress!.__errors).toHaveLength(1);
1578+
expect(errorSchema.billingAddress!.__errors![0]).toEqual(
1579+
"must have required property 'uiSchema Billing Address'"
1580+
);
1581+
1582+
expect(errorSchema.shippingAddress!.city!.__errors).toHaveLength(1);
1583+
expect(errorSchema.shippingAddress!.city!.__errors![0]).toEqual(
1584+
"must have required property 'uiSchema City'"
1585+
);
1586+
});
1587+
});
13471588
describe('uiSchema title in validation when defined in nested field', () => {
13481589
beforeAll(() => {
13491590
const schema: RJSFSchema = {

0 commit comments

Comments
 (0)