Skip to content

Commit 0ecbeb6

Browse files
authored
fix: Fixed 3778 By Resolving all refs in oneOf/AnyOf options (rjsf-team#3786)
* fix: Fixed 3778 By Resolving all refs in oneOf/AnyOf options * remove console log * add test for sanitizeDataForNewSchema ref resolving * Update chanelog.md
1 parent 97f8d07 commit 0ecbeb6

File tree

5 files changed

+136
-10
lines changed

5 files changed

+136
-10
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ it according to semantic versioning. For example, if your PR adds a breaking cha
1515
should change the heading of the (upcoming) version to include a major version bump.
1616
1717
-->
18+
# 5.11.1
19+
20+
## @rjsf/utils
21+
22+
- Created new `resolveAllReferences()` function to resolve all references within a schema's properties and array items.
23+
- Updated `getClosestMatchingOption()` to use `resolveAllReferences()` for all oneOf/anyOf schemas
24+
- Updated `resolveAnyOrOneOfSchemas()` to use `resolveAllReferences()` for all oneOf/anyOf schemas
25+
1826
# 5.11.0
1927

2028
## @rjsf/core

packages/utils/src/schema/getClosestMatchingOption.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import reduce from 'lodash/reduce';
66
import times from 'lodash/times';
77

88
import getFirstMatchingOption from './getFirstMatchingOption';
9-
import retrieveSchema from './retrieveSchema';
9+
import retrieveSchema, { resolveAllReferences } from './retrieveSchema';
1010
import { ONE_OF_KEY, REF_KEY, JUNK_OPTION_ID, ANY_OF_KEY } from '../constants';
1111
import guessType from '../guessType';
1212
import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types';
@@ -145,10 +145,7 @@ export default function getClosestMatchingOption<
145145
): number {
146146
// First resolve any refs in the options
147147
const resolvedOptions = options.map((option) => {
148-
if (has(option, REF_KEY)) {
149-
return retrieveSchema<T, S, F>(validator, option, rootSchema, formData);
150-
}
151-
return option;
148+
return resolveAllReferences(option, rootSchema);
152149
});
153150
// Reduce the array of options down to a list of the indexes that are considered matching options
154151
const allValidIndexes = resolvedOptions.reduce((validList: number[], option, index: number) => {

packages/utils/src/schema/retrieveSchema.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import get from 'lodash/get';
22
import set from 'lodash/set';
33
import times from 'lodash/times';
4+
import forEach from 'lodash/forEach';
45
import mergeAllOf, { Options } from 'json-schema-merge-allof';
56

67
import {
@@ -12,6 +13,8 @@ import {
1213
IF_KEY,
1314
ONE_OF_KEY,
1415
REF_KEY,
16+
PROPERTIES_KEY,
17+
ITEMS_KEY,
1518
} from '../constants';
1619
import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition';
1720
import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
@@ -192,6 +195,39 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
192195
);
193196
}
194197

198+
/** Resolves all references within a schema's properties and array items.
199+
*
200+
* @param schema - The schema for which resolving all references is desired
201+
* @param rootSchema - The root schema that will be forwarded to all the APIs
202+
* @returns - given schema will all references resolved
203+
*/
204+
export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(schema: S, rootSchema: S): S {
205+
let resolvedSchema: S = schema;
206+
// resolve top level ref
207+
if (REF_KEY in resolvedSchema) {
208+
const { $ref, ...localSchema } = resolvedSchema;
209+
// Retrieve the referenced schema definition.
210+
const refSchema = findSchemaDefinition<S>($ref, rootSchema);
211+
resolvedSchema = { ...refSchema, ...localSchema };
212+
}
213+
214+
if (PROPERTIES_KEY in resolvedSchema) {
215+
forEach(resolvedSchema[PROPERTIES_KEY], (value, key) => {
216+
resolvedSchema[PROPERTIES_KEY]![key] = resolveAllReferences(value as S, rootSchema);
217+
});
218+
}
219+
220+
if (
221+
ITEMS_KEY in resolvedSchema &&
222+
!Array.isArray(resolvedSchema.items) &&
223+
typeof resolvedSchema.items !== 'boolean'
224+
) {
225+
resolvedSchema.items = resolveAllReferences(resolvedSchema.items as S, rootSchema);
226+
}
227+
228+
return resolvedSchema;
229+
}
230+
195231
/** Creates new 'properties' items for each key in the `formData`
196232
*
197233
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
@@ -333,11 +369,7 @@ export function resolveAnyOrOneOfSchemas<
333369
const formData = rawFormData === undefined && expandAllBranches ? ({} as T) : rawFormData;
334370
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
335371
anyOrOneOf = anyOrOneOf.map((s) => {
336-
if (REF_KEY in s) {
337-
// For this ref situation, don't expand all branches and just pick the first/only schema result
338-
return resolveReference<T, S, F>(validator, s, rootSchema, false, formData)[0];
339-
}
340-
return s;
372+
return resolveAllReferences(s, rootSchema);
341373
});
342374
// Call this to trigger the set of isValid() calls that the schema parser will need
343375
const option = getFirstMatchingOption<T, S, F>(validator, formData, anyOrOneOf, rootSchema, discriminator);

packages/utils/test/schema/retrieveSchemaTest.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,64 @@ export default function retrieveSchemaTest(testValidator: TestValidatorType) {
13071307
},
13081308
]);
13091309
});
1310+
it('resolves oneOf with multiple $refs', () => {
1311+
const schema: RJSFSchema = {
1312+
oneOf: [
1313+
{
1314+
type: 'object',
1315+
properties: {
1316+
field: {
1317+
$ref: '#/definitions/aObject',
1318+
},
1319+
},
1320+
},
1321+
{
1322+
type: 'array',
1323+
items: {
1324+
$ref: '#/definitions/bObject',
1325+
},
1326+
},
1327+
],
1328+
};
1329+
const rootSchema: RJSFSchema = {
1330+
definitions: {
1331+
aObject: {
1332+
properties: {
1333+
a: { enum: ['typeA'] },
1334+
b: { type: 'number' },
1335+
},
1336+
},
1337+
bObject: {
1338+
properties: {
1339+
a: { enum: ['typeB'] },
1340+
c: { type: 'boolean' },
1341+
},
1342+
},
1343+
},
1344+
};
1345+
expect(resolveAnyOrOneOfSchemas(testValidator, schema, rootSchema, true)).toEqual([
1346+
{
1347+
type: 'object',
1348+
properties: {
1349+
field: {
1350+
properties: {
1351+
a: { enum: ['typeA'] },
1352+
b: { type: 'number' },
1353+
},
1354+
},
1355+
},
1356+
},
1357+
{
1358+
type: 'array',
1359+
items: {
1360+
properties: {
1361+
a: { enum: ['typeB'] },
1362+
c: { type: 'boolean' },
1363+
},
1364+
},
1365+
},
1366+
]);
1367+
});
13101368
});
13111369
describe('resolveCondition()', () => {
13121370
it('returns both conditions with expandAll', () => {

packages/utils/test/schema/sanitizeDataForNewSchemaTest.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,37 @@ export default function sanitizeDataForNewSchemaTest(testValidator: TestValidato
258258
})
259259
).toEqual({});
260260
});
261+
it('returns empty formData after resolving schema refs', () => {
262+
const rootSchema: RJSFSchema = {
263+
definitions: {
264+
string_def: {
265+
type: 'string',
266+
},
267+
},
268+
};
269+
const oldSchema: RJSFSchema = {
270+
type: 'object',
271+
properties: {
272+
field: {
273+
$ref: '#/definitions/string_def',
274+
},
275+
oldField: {
276+
type: 'string',
277+
},
278+
},
279+
};
280+
const newSchema: RJSFSchema = {
281+
type: 'object',
282+
properties: {
283+
field: {
284+
$ref: '#/definitions/string_def',
285+
},
286+
},
287+
};
288+
expect(sanitizeDataForNewSchema(testValidator, rootSchema, newSchema, oldSchema, { oldField: 'test' })).toEqual(
289+
{}
290+
);
291+
});
261292
it('returns data when two arrays have same boolean items', () => {
262293
const oldSchema: RJSFSchema = {
263294
type: 'array',

0 commit comments

Comments
 (0)