Skip to content

Commit

Permalink
fix: Fixed 3778 By Resolving all refs in oneOf/AnyOf options (#3786)
Browse files Browse the repository at this point in the history
* fix: Fixed 3778 By Resolving all refs in oneOf/AnyOf options

* remove console log

* add test for sanitizeDataForNewSchema ref resolving

* Update chanelog.md
  • Loading branch information
cwendtxealth committed Jul 24, 2023
1 parent 97f8d07 commit 0ecbeb6
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 10 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ 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.
-->
# 5.11.1

## @rjsf/utils

- Created new `resolveAllReferences()` function to resolve all references within a schema's properties and array items.
- Updated `getClosestMatchingOption()` to use `resolveAllReferences()` for all oneOf/anyOf schemas
- Updated `resolveAnyOrOneOfSchemas()` to use `resolveAllReferences()` for all oneOf/anyOf schemas

# 5.11.0

## @rjsf/core
Expand Down
7 changes: 2 additions & 5 deletions packages/utils/src/schema/getClosestMatchingOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import reduce from 'lodash/reduce';
import times from 'lodash/times';

import getFirstMatchingOption from './getFirstMatchingOption';
import retrieveSchema from './retrieveSchema';
import retrieveSchema, { resolveAllReferences } from './retrieveSchema';
import { ONE_OF_KEY, REF_KEY, JUNK_OPTION_ID, ANY_OF_KEY } from '../constants';
import guessType from '../guessType';
import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types';
Expand Down Expand Up @@ -145,10 +145,7 @@ export default function getClosestMatchingOption<
): number {
// First resolve any refs in the options
const resolvedOptions = options.map((option) => {
if (has(option, REF_KEY)) {
return retrieveSchema<T, S, F>(validator, option, rootSchema, formData);
}
return option;
return resolveAllReferences(option, rootSchema);
});
// Reduce the array of options down to a list of the indexes that are considered matching options
const allValidIndexes = resolvedOptions.reduce((validList: number[], option, index: number) => {
Expand Down
42 changes: 37 additions & 5 deletions packages/utils/src/schema/retrieveSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import get from 'lodash/get';
import set from 'lodash/set';
import times from 'lodash/times';
import forEach from 'lodash/forEach';
import mergeAllOf, { Options } from 'json-schema-merge-allof';

import {
Expand All @@ -12,6 +13,8 @@ import {
IF_KEY,
ONE_OF_KEY,
REF_KEY,
PROPERTIES_KEY,
ITEMS_KEY,
} from '../constants';
import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition';
import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
Expand Down Expand Up @@ -192,6 +195,39 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
);
}

/** Resolves all references within a schema's properties and array items.
*
* @param schema - The schema for which resolving all references is desired
* @param rootSchema - The root schema that will be forwarded to all the APIs
* @returns - given schema will all references resolved
*/
export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(schema: S, rootSchema: S): S {
let resolvedSchema: S = schema;
// resolve top level ref
if (REF_KEY in resolvedSchema) {
const { $ref, ...localSchema } = resolvedSchema;
// Retrieve the referenced schema definition.
const refSchema = findSchemaDefinition<S>($ref, rootSchema);
resolvedSchema = { ...refSchema, ...localSchema };
}

if (PROPERTIES_KEY in resolvedSchema) {
forEach(resolvedSchema[PROPERTIES_KEY], (value, key) => {
resolvedSchema[PROPERTIES_KEY]![key] = resolveAllReferences(value as S, rootSchema);
});
}

if (
ITEMS_KEY in resolvedSchema &&
!Array.isArray(resolvedSchema.items) &&
typeof resolvedSchema.items !== 'boolean'
) {
resolvedSchema.items = resolveAllReferences(resolvedSchema.items as S, rootSchema);
}

return resolvedSchema;
}

/** Creates new 'properties' items for each key in the `formData`
*
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
Expand Down Expand Up @@ -333,11 +369,7 @@ export function resolveAnyOrOneOfSchemas<
const formData = rawFormData === undefined && expandAllBranches ? ({} as T) : rawFormData;
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
anyOrOneOf = anyOrOneOf.map((s) => {
if (REF_KEY in s) {
// For this ref situation, don't expand all branches and just pick the first/only schema result
return resolveReference<T, S, F>(validator, s, rootSchema, false, formData)[0];
}
return s;
return resolveAllReferences(s, rootSchema);
});
// Call this to trigger the set of isValid() calls that the schema parser will need
const option = getFirstMatchingOption<T, S, F>(validator, formData, anyOrOneOf, rootSchema, discriminator);
Expand Down
58 changes: 58 additions & 0 deletions packages/utils/test/schema/retrieveSchemaTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,64 @@ export default function retrieveSchemaTest(testValidator: TestValidatorType) {
},
]);
});
it('resolves oneOf with multiple $refs', () => {
const schema: RJSFSchema = {
oneOf: [
{
type: 'object',
properties: {
field: {
$ref: '#/definitions/aObject',
},
},
},
{
type: 'array',
items: {
$ref: '#/definitions/bObject',
},
},
],
};
const rootSchema: RJSFSchema = {
definitions: {
aObject: {
properties: {
a: { enum: ['typeA'] },
b: { type: 'number' },
},
},
bObject: {
properties: {
a: { enum: ['typeB'] },
c: { type: 'boolean' },
},
},
},
};
expect(resolveAnyOrOneOfSchemas(testValidator, schema, rootSchema, true)).toEqual([
{
type: 'object',
properties: {
field: {
properties: {
a: { enum: ['typeA'] },
b: { type: 'number' },
},
},
},
},
{
type: 'array',
items: {
properties: {
a: { enum: ['typeB'] },
c: { type: 'boolean' },
},
},
},
]);
});
});
describe('resolveCondition()', () => {
it('returns both conditions with expandAll', () => {
Expand Down
31 changes: 31 additions & 0 deletions packages/utils/test/schema/sanitizeDataForNewSchemaTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,37 @@ export default function sanitizeDataForNewSchemaTest(testValidator: TestValidato
})
).toEqual({});
});
it('returns empty formData after resolving schema refs', () => {
const rootSchema: RJSFSchema = {
definitions: {
string_def: {
type: 'string',
},
},
};
const oldSchema: RJSFSchema = {
type: 'object',
properties: {
field: {
$ref: '#/definitions/string_def',
},
oldField: {
type: 'string',
},
},
};
const newSchema: RJSFSchema = {
type: 'object',
properties: {
field: {
$ref: '#/definitions/string_def',
},
},
};
expect(sanitizeDataForNewSchema(testValidator, rootSchema, newSchema, oldSchema, { oldField: 'test' })).toEqual(
{}
);
});
it('returns data when two arrays have same boolean items', () => {
const oldSchema: RJSFSchema = {
type: 'array',
Expand Down

0 comments on commit 0ecbeb6

Please sign in to comment.