Skip to content

Always make all references absolute in nested bundled schemas #4683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ should change the heading of the (upcoming) version to include a major version b

-->

# 6.0.0-beta.11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 6.0.0-beta.11
# 6.0.0-beta.12


## rjsf/utils

- Always make all references absolute in nested bundled schemas


# 6.0.0-beta.11

## @rjsf/antd
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ export const UI_GLOBAL_OPTIONS_KEY = 'ui:globalOptions';

/** The JSON Schema version strings
*/
export const JSON_SCHEMA_DRAFT_2019_09 = 'https://json-schema.org/draft/2019-09/schema';
export const JSON_SCHEMA_DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
60 changes: 57 additions & 3 deletions packages/utils/src/findSchemaDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import jsonpointer from 'jsonpointer';
import omit from 'lodash/omit';

import { ID_KEY, JSON_SCHEMA_DRAFT_2020_12, REF_KEY, SCHEMA_KEY } from './constants';
import {
ALL_OF_KEY,
ID_KEY,
JSON_SCHEMA_DRAFT_2019_09,
JSON_SCHEMA_DRAFT_2020_12,
REF_KEY,
SCHEMA_KEY,
} from './constants';
import { GenericObjectType, RJSFSchema, StrictRJSFSchema } from './types';
import isObject from 'lodash/isObject';
import isEmpty from 'lodash/isEmpty';
Expand All @@ -20,7 +27,16 @@ function findEmbeddedSchemaRecursive<S extends StrictRJSFSchema = RJSFSchema>(sc
return schema;
}
for (const subSchema of Object.values(schema)) {
if (isObject(subSchema)) {
if (Array.isArray(subSchema)) {
for (const item of subSchema) {
if (isObject(item)) {
const result = findEmbeddedSchemaRecursive<S>(item as S, ref);
if (result !== undefined) {
return result as S;
}
}
}
} else if (isObject(subSchema)) {
const result = findEmbeddedSchemaRecursive<S>(subSchema as S, ref);
if (result !== undefined) {
return result as S;
Expand All @@ -30,6 +46,31 @@ function findEmbeddedSchemaRecursive<S extends StrictRJSFSchema = RJSFSchema>(sc
return undefined;
}

/** Parses a JSONSchema and makes all references absolute with respect to
* the `baseURI` argument
* @param schema - The schema to be processed
* @param baseURI - The base URI to be used for resolving relative references
*/
function makeAllReferencesAbsolute<S extends StrictRJSFSchema = RJSFSchema>(schema: S, baseURI: string): S {
const currentURI = get(schema, ID_KEY, baseURI);
// Make all other references absolute
if (REF_KEY in schema) {
schema = { ...schema, [REF_KEY]: UriResolver.resolve(currentURI, schema[REF_KEY]!) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are manipulating the root schema here or the lower-level schema as we are calling retrieveSchema()? Honestly, with a few changes to the createSchemaUtils() and Form in core, we could probably run this code over the rootSchema once and cache it in the schemaUtils object. And then use that schema within the Form by updating the getRegistry() function and update the schema in the state within Form to pick up the cached, updated rootSchema from the schemaUtils. Does that make sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I just updated the Form in core to always use the rootSchema in schemaUtils in this PR so all you have to do is modify the schemaUtils to cache the modified rootSchema having made references absolute.

}
// Look for references in nested subschemas
for (const [key, subSchema] of Object.entries(schema)) {
if (Array.isArray(subSchema)) {
schema = {
...schema,
[key]: subSchema.map((item) => (isObject(item) ? makeAllReferencesAbsolute(item as S, currentURI) : item)),
};
} else if (isObject(subSchema)) {
schema = { ...schema, [key]: makeAllReferencesAbsolute(subSchema as S, currentURI) };
}
}
return schema;
}

/** Splits out the value at the `key` in `object` from the `object`, returning an array that contains in the first
* location, the `object` minus the `key: value` and in the second location the `value`.
*
Expand Down Expand Up @@ -73,6 +114,9 @@ export function findSchemaDefinitionRecursive<S extends StrictRJSFSchema = RJSFS
current = findEmbeddedSchemaRecursive<S>(rootSchema, baseURI.replace(/\/$/, ''));
if (current !== undefined) {
current = jsonpointer.get(current, decodedRef);
if (current !== undefined) {
current = makeAllReferencesAbsolute(current, current[ID_KEY]!);
}
}
}
} else if (rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) {
Expand All @@ -84,6 +128,9 @@ export function findSchemaDefinitionRecursive<S extends StrictRJSFSchema = RJSFS
if (!isEmpty(refAnchor)) {
current = jsonpointer.get(current, decodeURIComponent(refAnchor.join('#')));
}
if (current !== undefined) {
current = makeAllReferencesAbsolute(current!, baseURI!);
}
}
}
if (current === undefined) {
Expand All @@ -103,7 +150,14 @@ export function findSchemaDefinitionRecursive<S extends StrictRJSFSchema = RJSFS
const [remaining, theRef] = splitKeyElementFromObject(REF_KEY, current);
const subSchema = findSchemaDefinitionRecursive<S>(theRef, rootSchema, [...recurseList, ref], baseURI);
if (Object.keys(remaining).length > 0) {
return { ...remaining, ...subSchema };
if (
rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2019_09 ||
rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12
) {
return { [ALL_OF_KEY]: [remaining, subSchema] } as S;
} else {
return { ...remaining, ...subSchema };
}
Comment on lines +153 to +160
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have different logic for different standard versions? Is the allOf option more correct (and could we use it for draft-07)?

}
return subSchema;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/schema/retrieveSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ export function stubExistingAdditionalProperties<
if (!isEmpty(matchingProperties)) {
schema.properties[key] = retrieveSchema<T, S, F>(
validator,
{ allOf: Object.values(matchingProperties) } as S,
{ [ALL_OF_KEY]: Object.values(matchingProperties) } as S,
rootSchema,
get(formData, [key]) as T,
experimental_customMergeAllOf,
Expand All @@ -445,7 +445,7 @@ export function stubExistingAdditionalProperties<
if (REF_KEY in schema.additionalProperties!) {
additionalProperties = retrieveSchema<T, S, F>(
validator,
{ $ref: get(schema.additionalProperties, [REF_KEY]) } as S,
{ [REF_KEY]: get(schema.additionalProperties, [REF_KEY]) } as S,
rootSchema,
formData as T,
experimental_customMergeAllOf,
Expand Down
Loading