Skip to content

Commit 54ed4ed

Browse files
committed
Fix for AJV $data reference in const property in schema treated as default/const value.
1 parent fbb53e9 commit 54ed4ed

File tree

5 files changed

+169
-3
lines changed

5 files changed

+169
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19+
# 5.24.0
20+
21+
- Fix for AJV [$data](https://ajv.js.org/guide/combining-schemas.html#data-reference) reference in const property in schema treated as default/const value. The issue is mentioned in [#4361](https://github.com/rjsf-team/react-jsonschema-form/issues/4361).
22+
1923
# 5.23.2
2024

2125
## @rjsf/core

packages/core/test/ObjectField.test.jsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,49 @@ describe('ObjectField', () => {
227227
});
228228
});
229229

230+
it('should validate AJV $data reference ', () => {
231+
const schema = {
232+
type: 'object',
233+
properties: {
234+
email: {
235+
type: 'string',
236+
title: 'E-mail',
237+
format: 'email',
238+
},
239+
emailConfirm: {
240+
type: 'string',
241+
const: {
242+
$data: '/email',
243+
},
244+
title: 'Confirm e-mail',
245+
format: 'email',
246+
},
247+
},
248+
};
249+
const { node, rerender } = createFormComponent({
250+
schema,
251+
formData: {
252+
253+
emailConfirm: '[email protected]',
254+
},
255+
liveValidate: true,
256+
});
257+
258+
const errorMessages = node.querySelectorAll('#root_emailConfirm__error');
259+
expect(errorMessages).to.have.length(1);
260+
261+
rerender({
262+
schema,
263+
formData: {
264+
265+
emailConfirm: '[email protected]',
266+
},
267+
liveValidate: true,
268+
});
269+
270+
expect(node.querySelectorAll('#root_foo__error')).to.have.length(0);
271+
});
272+
230273
it('Check that when formData changes, the form should re-validate', () => {
231274
const { node, rerender } = createFormComponent({
232275
schema,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { CONST_KEY, getSchemaType, isObject } from './';
2+
import { RJSFSchema, StrictRJSFSchema } from './types';
3+
import { JSONSchema7Type } from 'json-schema';
4+
import isString from 'lodash/isString';
5+
6+
/**
7+
* Checks if the schema const property value is an AJV $data reference
8+
* and the current schema is not an object or array
9+
*
10+
* @param schema - The schema to check if the const is an AJV $data reference
11+
* @returns - true if the schema const property value is an AJV $data reference otherwise false.
12+
*/
13+
export default function constIsAjvDataReference<S extends StrictRJSFSchema = RJSFSchema>(schema: S): boolean {
14+
const schemaConst = schema[CONST_KEY] as JSONSchema7Type & { $data: string };
15+
const schemaType = getSchemaType<S>(schema);
16+
return isObject(schemaConst) && isString(schemaConst?.$data) && schemaType !== 'object' && schemaType !== 'array';
17+
}

packages/utils/src/schema/getDefaultFormState.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
import isMultiSelect from './isMultiSelect';
3333
import retrieveSchema, { resolveDependencies } from './retrieveSchema';
3434
import { JSONSchema7Object } from 'json-schema';
35+
import constIsAjvDataReference from '../constIsAjvDataReference';
3536

3637
const PRIMITIVE_TYPES = ['string', 'number', 'integer', 'boolean', 'null'];
3738

@@ -203,8 +204,12 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
203204
let experimental_dfsb_to_compute = experimental_defaultFormStateBehavior;
204205
let updatedRecurseList = _recurseList;
205206

206-
if (schema[CONST_KEY] && experimental_defaultFormStateBehavior?.constAsDefaults !== 'never') {
207-
defaults = schema.const as unknown as T;
207+
if (
208+
schema[CONST_KEY] &&
209+
experimental_defaultFormStateBehavior?.constAsDefaults !== 'never' &&
210+
!constIsAjvDataReference(schema)
211+
) {
212+
defaults = schema[CONST_KEY] as unknown as T;
208213
} else if (isObject(defaults) && isObject(schema.default)) {
209214
// For object defaults, only override parent defaults that are defined in
210215
// schema.default.
@@ -357,7 +362,8 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
357362
const hasParentConst = isObject(parentConst) && (parentConst as JSONSchema7Object)[key] !== undefined;
358363
const hasConst =
359364
((isObject(propertySchema) && CONST_KEY in propertySchema) || hasParentConst) &&
360-
experimental_defaultFormStateBehavior?.constAsDefaults !== 'never';
365+
experimental_defaultFormStateBehavior?.constAsDefaults !== 'never' &&
366+
!constIsAjvDataReference(propertySchema);
361367
// Compute the defaults for this node, with the parent defaults we might
362368
// have from a previous run: defaults[key].
363369
const computedDefault = computeDefaults<T, S, F>(validator, propertySchema, {

packages/utils/test/schema/getDefaultFormStateTest.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,6 +1750,102 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType
17501750
expect(getArrayDefaults(testValidator, schema)).toStrictEqual([]);
17511751
});
17521752
});
1753+
describe('AJV $data reference in const property in schema should not be treated as default/const value', () => {
1754+
let schema: RJSFSchema;
1755+
it('test nested object with $data in the schema', () => {
1756+
schema = {
1757+
type: 'object',
1758+
properties: {
1759+
email: {
1760+
type: 'string',
1761+
title: 'E-mail',
1762+
format: 'email',
1763+
},
1764+
emailConfirm: {
1765+
type: 'string',
1766+
const: {
1767+
$data: '/email',
1768+
},
1769+
title: 'Confirm e-mail',
1770+
format: 'email',
1771+
},
1772+
nestedObject: {
1773+
type: 'object',
1774+
properties: {
1775+
nestedEmail: {
1776+
type: 'string',
1777+
title: 'E-mail',
1778+
format: 'email',
1779+
},
1780+
nestedEmailConfirm: {
1781+
type: 'string',
1782+
title: 'Confirm e-mail',
1783+
const: {
1784+
$data: '/nestedObject/nestedEmail',
1785+
},
1786+
format: 'email',
1787+
},
1788+
},
1789+
},
1790+
nestedObjectConfirm: {
1791+
type: 'object',
1792+
properties: {
1793+
nestedEmailConfirm: {
1794+
type: 'string',
1795+
title: 'Confirm e-mail',
1796+
const: {
1797+
$data: '/nestedObject/nestedEmail',
1798+
},
1799+
format: 'email',
1800+
},
1801+
},
1802+
},
1803+
arrayConfirm: {
1804+
type: 'array',
1805+
items: {
1806+
type: 'string',
1807+
title: 'Confirm e-mail',
1808+
const: {
1809+
$data: '/nestedObject/nestedEmail',
1810+
},
1811+
format: 'email',
1812+
},
1813+
},
1814+
},
1815+
};
1816+
expect(
1817+
computeDefaults(testValidator, schema, {
1818+
rootSchema: schema,
1819+
})
1820+
).toEqual({
1821+
arrayConfirm: [],
1822+
});
1823+
});
1824+
it('test nested object with $data in the schema and emptyObjectFields set to populateRequiredDefaults', () => {
1825+
expect(
1826+
computeDefaults(testValidator, schema, {
1827+
rootSchema: schema,
1828+
experimental_defaultFormStateBehavior: { emptyObjectFields: 'populateRequiredDefaults' },
1829+
})
1830+
).toEqual({});
1831+
});
1832+
it('test nested object with $data in the schema and emptyObjectFields set to skipEmptyDefaults', () => {
1833+
expect(
1834+
computeDefaults(testValidator, schema, {
1835+
rootSchema: schema,
1836+
experimental_defaultFormStateBehavior: { emptyObjectFields: 'skipEmptyDefaults' },
1837+
})
1838+
).toEqual({});
1839+
});
1840+
it('test nested object with $data in the schema and emptyObjectFields set to skipDefaults', () => {
1841+
expect(
1842+
computeDefaults(testValidator, schema, {
1843+
rootSchema: schema,
1844+
experimental_defaultFormStateBehavior: { emptyObjectFields: 'skipDefaults' },
1845+
})
1846+
).toEqual({});
1847+
});
1848+
});
17531849
describe('default form state behavior: ignore min items unless required', () => {
17541850
it('should return empty data for an optional array property with minItems', () => {
17551851
const schema: RJSFSchema = {

0 commit comments

Comments
 (0)