From f8e31fb7e3cc13f7dcb381df959bd1d63d32b7ef Mon Sep 17 00:00:00 2001 From: GlassOfWhiskey Date: Sat, 14 Jun 2025 19:23:36 +0200 Subject: [PATCH 1/2] Make all references absolute This commit adjusts all internal references of a bundled schema to be absolute, allowing subschemas to be correctly resolved. --- CHANGELOG.md | 7 + packages/utils/src/constants.ts | 1 + packages/utils/src/findSchemaDefinition.ts | 60 ++++++- packages/utils/src/schema/retrieveSchema.ts | 4 +- .../utils/test/findSchemaDefinition.test.ts | 169 +++++++++++++----- 5 files changed, 190 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f64b8d014..425a17b8b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,13 @@ should change the heading of the (upcoming) version to include a major version b - Updated `SchemaUtils` and `createSchemaUtils()` to add a new `getRootSchema()` function +# 6.0.0-beta.11 + +## rjsf/utils + +- Always make all references absolute in nested bundled schemas + + # 6.0.0-beta.11 ## @rjsf/antd diff --git a/packages/utils/src/constants.ts b/packages/utils/src/constants.ts index e379f0c372..4867358df5 100644 --- a/packages/utils/src/constants.ts +++ b/packages/utils/src/constants.ts @@ -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'; diff --git a/packages/utils/src/findSchemaDefinition.ts b/packages/utils/src/findSchemaDefinition.ts index 09abc7e215..df88011153 100644 --- a/packages/utils/src/findSchemaDefinition.ts +++ b/packages/utils/src/findSchemaDefinition.ts @@ -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'; @@ -20,7 +27,16 @@ function findEmbeddedSchemaRecursive(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(item as S, ref); + if (result !== undefined) { + return result as S; + } + } + } + } else if (isObject(subSchema)) { const result = findEmbeddedSchemaRecursive(subSchema as S, ref); if (result !== undefined) { return result as S; @@ -30,6 +46,31 @@ function findEmbeddedSchemaRecursive(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(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]!) }; + } + // 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`. * @@ -73,6 +114,9 @@ export function findSchemaDefinitionRecursive(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) { @@ -84,6 +128,9 @@ export function findSchemaDefinitionRecursive(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 }; + } } return subSchema; } diff --git a/packages/utils/src/schema/retrieveSchema.ts b/packages/utils/src/schema/retrieveSchema.ts index cb6d58d9d7..807d87b63b 100644 --- a/packages/utils/src/schema/retrieveSchema.ts +++ b/packages/utils/src/schema/retrieveSchema.ts @@ -430,7 +430,7 @@ export function stubExistingAdditionalProperties< if (!isEmpty(matchingProperties)) { schema.properties[key] = retrieveSchema( validator, - { allOf: Object.values(matchingProperties) } as S, + { [ALL_OF_KEY]: Object.values(matchingProperties) } as S, rootSchema, get(formData, [key]) as T, experimental_customMergeAllOf, @@ -445,7 +445,7 @@ export function stubExistingAdditionalProperties< if (REF_KEY in schema.additionalProperties!) { additionalProperties = retrieveSchema( validator, - { $ref: get(schema.additionalProperties, [REF_KEY]) } as S, + { [REF_KEY]: get(schema.additionalProperties, [REF_KEY]) } as S, rootSchema, formData as T, experimental_customMergeAllOf, diff --git a/packages/utils/test/findSchemaDefinition.test.ts b/packages/utils/test/findSchemaDefinition.test.ts index 0cf130b9cb..9828f63044 100644 --- a/packages/utils/test/findSchemaDefinition.test.ts +++ b/packages/utils/test/findSchemaDefinition.test.ts @@ -46,34 +46,87 @@ const schema: RJSFSchema = { }, }; -const bundledSchema: RJSFSchema = { +const internalSchema: RJSFSchema = { $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://example.com/bundled.ref.json', type: 'object', - $id: 'https://example.com/bundled.schema.json', $defs: { - bundledSchema: { - $schema: 'https://json-schema.org/draft/2020-12/schema', - $id: 'https://example.com/bundled.ref.json', - type: 'object', - $defs: { - string: { - type: 'string', + colors: { + type: 'string', + enum: ['red', 'green', 'blue'], + }, + string: { + type: 'string', + }, + circularRef: { + $ref: '/bundled.schema.json/#/$defs/circularRef', + }, + undefinedRef: { + $ref: '#/$defs/undefined', + }, + }, + properties: { + num: { + type: 'integer', + }, + string: { + $ref: '#/$defs/string', + }, + allOf: { + allOf: [ + { + $ref: '#/$defs/string', }, - circularRef: { - $ref: '/bundled.schema.json/#/$defs/circularRef', + { + title: 'String', }, - undefinedRef: { - $ref: '#/$defs/undefined', + ], + }, + }, +}; + +const absoluteInternalSchema: RJSFSchema = { + ...internalSchema, + $defs: { + ...internalSchema.$defs, + circularRef: { $ref: 'https://example.com/bundled.schema.json/#/$defs/circularRef' }, + undefinedRef: { $ref: 'https://example.com/bundled.ref.json#/$defs/undefined' }, + }, + properties: { + ...internalSchema.properties, + string: { $ref: 'https://example.com/bundled.ref.json#/$defs/string' }, + allOf: { + allOf: [ + { + $ref: 'https://example.com/bundled.ref.json#/$defs/string', }, - }, - properties: { - num: { - type: 'integer', + { + title: 'String', }, - string: { - $ref: '#/$defs/string', + ], + }, + }, +}; + +const bundledSchema: RJSFSchema = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + $id: 'https://example.com/bundled.schema.json', + $defs: { + bundledSchema: internalSchema, + bundledSchemaArray: { + anyOf: [ + { + $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://example.com/bundled.ref.array.1.json', + type: 'object', }, - }, + { + $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://example.com/bundled.ref.array.2.json', + type: 'object', + }, + ], }, bundledAbsoluteRef: { $ref: 'https://example.com/bundled.ref.json', @@ -81,6 +134,9 @@ const bundledSchema: RJSFSchema = { bundledAbsoluteRefWithAnchor: { $ref: 'https://example.com/bundled.ref.json/#/properties/num', }, + bundledAbsoluteRefWithinArray: { + $ref: 'https://example.com/bundled.ref.array.1.json', + }, bundledRelativeRef: { $ref: '/bundled.ref.json', }, @@ -96,6 +152,9 @@ const bundledSchema: RJSFSchema = { undefinedRef: { $ref: '/undefined.ref.json', }, + undefinedRefWithAnchor: { + $ref: '/bundled.ref.json#/$defs/undefinedRef', + }, }, properties: { undefined: { @@ -150,38 +209,43 @@ describe('findSchemaDefinition()', () => { ); }); it('correctly resolves absolute bundled refs when JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchema)).toBe(bundledSchema.$defs!.bundledSchema); + expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); }); it('correctly resolves absolute bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinition('#/$defs/bundledAbsoluteRefWithAnchor', bundledSchema)).toBe( - (bundledSchema.$defs!.bundledSchema as RJSFSchema).properties!.num, + absoluteInternalSchema.properties!.num, + ); + }); + it('correctly resolves absolute bundled refs in arrays within a JSON Schema Draft 2020-12', () => { + expect(findSchemaDefinition('#/$defs/bundledAbsoluteRefWithinArray', bundledSchema)).toBe( + (bundledSchema.$defs!.bundledSchemaArray as RJSFSchema).anyOf![0], ); }); it('correctly resolves absolute bundled refs within a JSON Schema Draft 2020-12 without an `$id`', () => { const { $id: d, ...bundledSchemaWithoutId } = bundledSchema; - expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toBe( - bundledSchema.$defs!.bundledSchema, + expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toStrictEqual( + absoluteInternalSchema, ); }); it('correctly resolves relative bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinition('#/$defs/bundledRelativeRef', bundledSchema)).toBe(bundledSchema.$defs!.bundledSchema); + expect(findSchemaDefinition('#/$defs/bundledRelativeRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); }); it('correctly resolves relative bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinition('#/$defs/bundledRelativeRefWithAnchor', bundledSchema)).toBe( - (bundledSchema.$defs!.bundledSchema as RJSFSchema).$defs!.string, + absoluteInternalSchema.$defs!.string, ); }); it('correctly resolves indirect bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinition('#/$defs/indirectRef', bundledSchema)).toBe(bundledSchema.$defs!.bundledSchema); + expect(findSchemaDefinition('#/$defs/indirectRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); }); it('correctly resolves refs with explicit base URI in a bundled JSON Schema', () => { - expect(findSchemaDefinition('bundled.ref.json', bundledSchema, 'https://example.com/undefined.ref.json')).toBe( - bundledSchema.$defs!.bundledSchema, - ); + expect( + findSchemaDefinition('bundled.ref.json', bundledSchema, 'https://example.com/undefined.ref.json'), + ).toStrictEqual(absoluteInternalSchema); }); it('correctly resolves local refs with explicit base URI in a bundled JSON Schema', () => { expect(findSchemaDefinition('#/properties/num', bundledSchema, 'https://example.com/bundled.ref.json')).toBe( - (bundledSchema.$defs!.bundledSchema as RJSFSchema).properties!.num, + absoluteInternalSchema.properties!.num, ); }); it('throws error when relative ref is undefined in a bundled JSON Schema', () => { @@ -189,6 +253,11 @@ describe('findSchemaDefinition()', () => { 'Could not find a definition for /undefined.ref.json', ); }); + it('throws error when relative ref with anchor is undefined in a bundled JSON Schema', () => { + expect(() => findSchemaDefinition('#/$defs/undefinedRefWithAnchor', bundledSchema)).toThrowError( + 'Could not find a definition for https://example.com/bundled.ref.json#/$defs/undefined', + ); + }); it('throws error when local ref is undefined in a bundled JSON Schema with explicit base URI', () => { expect(() => findSchemaDefinition('#/properties/undefined', bundledSchema, 'https://example.com/bundled.ref.json'), @@ -201,7 +270,7 @@ describe('findSchemaDefinition()', () => { }); it('throws error when ref is a deep circular reference in a bundled JSON Schema', () => { expect(() => findSchemaDefinition('#/$defs/circularRef', bundledSchema)).toThrowError( - 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> /bundled.schema.json/#/$defs/circularRef -> #/$defs/circularRef', + 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> https://example.com/bundled.schema.json/#/$defs/circularRef', ); }); }); @@ -250,51 +319,59 @@ describe('findSchemaDefinitionRecursive()', () => { ).toThrowError('Could not find a definition for #/properties/num'); }); it('correctly resolves absolute bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchema)).toBe( - bundledSchema.$defs!.bundledSchema, + expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchema)).toStrictEqual( + absoluteInternalSchema, ); }); it('correctly resolves absolute bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRefWithAnchor', bundledSchema)).toBe( - (bundledSchema.$defs!.bundledSchema as RJSFSchema).properties!.num, + absoluteInternalSchema.properties!.num, + ); + }); + it('correctly resolves absolute bundled refs in arrays within a JSON Schema Draft 2020-12', () => { + expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRefWithinArray', bundledSchema)).toBe( + (bundledSchema.$defs!.bundledSchemaArray as RJSFSchema).anyOf![0], ); }); it('correctly resolves absolute bundled refs within a JSON Schema Draft 2020-12 without an `$id`', () => { const { $id: d, ...bundledSchemaWithoutId } = bundledSchema; - expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toBe( - bundledSchema.$defs!.bundledSchema, + expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toStrictEqual( + absoluteInternalSchema, ); }); it('correctly resolves relative bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinitionRecursive('#/$defs/bundledRelativeRef', bundledSchema)).toBe( - bundledSchema.$defs!.bundledSchema, + expect(findSchemaDefinitionRecursive('#/$defs/bundledRelativeRef', bundledSchema)).toStrictEqual( + absoluteInternalSchema, ); }); it('correctly resolves relative bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinitionRecursive('#/$defs/bundledRelativeRefWithAnchor', bundledSchema)).toBe( - (bundledSchema.$defs!.bundledSchema as RJSFSchema).$defs!.string, + absoluteInternalSchema.$defs!.string, ); }); it('correctly resolves indirect bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinitionRecursive('#/$defs/indirectRef', bundledSchema)).toBe( - bundledSchema.$defs!.bundledSchema, - ); + expect(findSchemaDefinitionRecursive('#/$defs/indirectRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); }); it('correctly resolves relative refs with explicit base URI in a bundled JSON Schema', () => { expect( findSchemaDefinitionRecursive('bundled.ref.json', bundledSchema, [], 'https://example.com/undefined.ref.json'), - ).toBe(bundledSchema.$defs!.bundledSchema); + ).toStrictEqual(absoluteInternalSchema); }); it('correctly resolves local refs with explicit base URI in a bundled JSON Schema', () => { expect( findSchemaDefinitionRecursive('#/properties/num', bundledSchema, [], 'https://example.com/bundled.ref.json'), - ).toBe((bundledSchema.$defs!.bundledSchema as RJSFSchema).properties!.num); + ).toBe(absoluteInternalSchema.properties!.num); }); it('throws error when relative ref is undefined in a bundled JSON Schema', () => { expect(() => findSchemaDefinitionRecursive('#/$defs/undefinedRef', bundledSchema)).toThrowError( 'Could not find a definition for /undefined.ref.json', ); }); + it('throws error when relative ref with anchor is undefined in a bundled JSON Schema', () => { + expect(() => findSchemaDefinitionRecursive('#/$defs/undefinedRefWithAnchor', bundledSchema)).toThrowError( + 'Could not find a definition for https://example.com/bundled.ref.json#/$defs/undefined', + ); + }); it('throws error when local ref is undefined in a bundled JSON Schema with explicit base URI', () => { expect(() => findSchemaDefinitionRecursive( @@ -312,7 +389,7 @@ describe('findSchemaDefinitionRecursive()', () => { }); it('throws error when ref is a deep circular reference in a bundled JSON Schema', () => { expect(() => findSchemaDefinitionRecursive('#/$defs/circularRef', bundledSchema, [])).toThrowError( - 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> /bundled.schema.json/#/$defs/circularRef -> #/$defs/circularRef', + 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> https://example.com/bundled.schema.json/#/$defs/circularRef', ); }); }); From 68f994b6b848d8df3b9e9a3b51c21bf84b1d2de2 Mon Sep 17 00:00:00 2001 From: GlassOfWhiskey Date: Sat, 19 Jul 2025 17:18:25 +0200 Subject: [PATCH 2/2] Applied required changes --- CHANGELOG.md | 13 ++- packages/utils/src/createSchemaUtils.ts | 9 +- packages/utils/src/findSchemaDefinition.ts | 8 +- packages/utils/test/createSchemaUtils.test.ts | 35 ++++++ .../utils/test/findSchemaDefinition.test.ts | 100 +++++++++--------- 5 files changed, 99 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 425a17b8b7..8a6760986c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ should change the heading of the (upcoming) version to include a major version b --> +# 6.0.0-beta.13 + +## rjsf/utils + +- Always make all references absolute in nested bundled schemas + # 6.0.0-beta.12 ## @rjsf/core @@ -30,13 +36,6 @@ should change the heading of the (upcoming) version to include a major version b - Updated `SchemaUtils` and `createSchemaUtils()` to add a new `getRootSchema()` function -# 6.0.0-beta.11 - -## rjsf/utils - -- Always make all references absolute in nested bundled schemas - - # 6.0.0-beta.11 ## @rjsf/antd diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index 28a3db9c2d..d3d1d21571 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -29,6 +29,9 @@ import { toIdSchema, toPathSchema, } from './schema'; +import { makeAllReferencesAbsolute } from './findSchemaDefinition'; +import { ID_KEY, JSON_SCHEMA_DRAFT_2020_12, SCHEMA_KEY } from './constants'; +import get from 'lodash/get'; /** The `SchemaUtils` class provides a wrapper around the publicly exported APIs in the `utils/schema` directory such * that one does not have to explicitly pass the `validator`, `rootSchema`, `experimental_defaultFormStateBehavior` or @@ -57,7 +60,11 @@ class SchemaUtils, ) { - this.rootSchema = rootSchema; + if (rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) { + this.rootSchema = makeAllReferencesAbsolute(rootSchema, get(rootSchema, ID_KEY, '#')); + } else { + this.rootSchema = rootSchema; + } this.validator = validator; this.experimental_defaultFormStateBehavior = experimental_defaultFormStateBehavior; this.experimental_customMergeAllOf = experimental_customMergeAllOf; diff --git a/packages/utils/src/findSchemaDefinition.ts b/packages/utils/src/findSchemaDefinition.ts index df88011153..4b06f8cac9 100644 --- a/packages/utils/src/findSchemaDefinition.ts +++ b/packages/utils/src/findSchemaDefinition.ts @@ -51,7 +51,7 @@ function findEmbeddedSchemaRecursive(sc * @param schema - The schema to be processed * @param baseURI - The base URI to be used for resolving relative references */ -function makeAllReferencesAbsolute(schema: S, baseURI: string): S { +export function makeAllReferencesAbsolute(schema: S, baseURI: string): S { const currentURI = get(schema, ID_KEY, baseURI); // Make all other references absolute if (REF_KEY in schema) { @@ -114,9 +114,6 @@ export function findSchemaDefinitionRecursive(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) { @@ -128,9 +125,6 @@ export function findSchemaDefinitionRecursive { expect(schemaUtils.getValidator()).toBe(testValidator); }); + describe('2020-12 schema', () => { + const rootSchema2020: RJSFSchema = { + [SCHEMA_KEY]: JSON_SCHEMA_DRAFT_2020_12, + [ID_KEY]: 'https://example.com/2020-12.json', + type: 'object', + $defs: { + example: { + type: 'integer', + }, + }, + [PROPERTIES_KEY]: { + ref: { + [REF_KEY]: '#/$defs/example', + }, + }, + }; + const schemaUtils2020: SchemaUtilsType = createSchemaUtils(testValidator, rootSchema2020, defaultFormStateBehavior); + + it('getRootSchema()', () => { + expect(schemaUtils2020.getRootSchema()).toEqual({ + ...rootSchema2020, + [PROPERTIES_KEY]: { + ref: { + [REF_KEY]: 'https://example.com/2020-12.json#/$defs/example', + }, + }, + }); + }); + }); + describe('doesSchemaUtilsDiffer()', () => { describe('constructed without defaultFormStateBehavior', () => { const schemaUtils: SchemaUtilsType = createSchemaUtils(testValidator, rootSchema); diff --git a/packages/utils/test/findSchemaDefinition.test.ts b/packages/utils/test/findSchemaDefinition.test.ts index 9828f63044..1a31ff848b 100644 --- a/packages/utils/test/findSchemaDefinition.test.ts +++ b/packages/utils/test/findSchemaDefinition.test.ts @@ -1,5 +1,5 @@ -import { findSchemaDefinition, RJSFSchema } from '../src'; -import { findSchemaDefinitionRecursive } from '../src/findSchemaDefinition'; +import { findSchemaDefinition, ID_KEY, RJSFSchema } from '../src'; +import { findSchemaDefinitionRecursive, makeAllReferencesAbsolute } from '../src/findSchemaDefinition'; const schema: RJSFSchema = { type: 'object', @@ -85,29 +85,6 @@ const internalSchema: RJSFSchema = { }, }; -const absoluteInternalSchema: RJSFSchema = { - ...internalSchema, - $defs: { - ...internalSchema.$defs, - circularRef: { $ref: 'https://example.com/bundled.schema.json/#/$defs/circularRef' }, - undefinedRef: { $ref: 'https://example.com/bundled.ref.json#/$defs/undefined' }, - }, - properties: { - ...internalSchema.properties, - string: { $ref: 'https://example.com/bundled.ref.json#/$defs/string' }, - allOf: { - allOf: [ - { - $ref: 'https://example.com/bundled.ref.json#/$defs/string', - }, - { - title: 'String', - }, - ], - }, - }, -}; - const bundledSchema: RJSFSchema = { $schema: 'https://json-schema.org/draft/2020-12/schema', type: 'object', @@ -209,11 +186,11 @@ describe('findSchemaDefinition()', () => { ); }); it('correctly resolves absolute bundled refs when JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); + expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchema)).toStrictEqual(internalSchema); }); it('correctly resolves absolute bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinition('#/$defs/bundledAbsoluteRefWithAnchor', bundledSchema)).toBe( - absoluteInternalSchema.properties!.num, + internalSchema.properties!.num, ); }); it('correctly resolves absolute bundled refs in arrays within a JSON Schema Draft 2020-12', () => { @@ -223,29 +200,27 @@ describe('findSchemaDefinition()', () => { }); it('correctly resolves absolute bundled refs within a JSON Schema Draft 2020-12 without an `$id`', () => { const { $id: d, ...bundledSchemaWithoutId } = bundledSchema; - expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toStrictEqual( - absoluteInternalSchema, - ); + expect(findSchemaDefinition('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toStrictEqual(internalSchema); }); it('correctly resolves relative bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinition('#/$defs/bundledRelativeRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); + expect(findSchemaDefinition('#/$defs/bundledRelativeRef', bundledSchema)).toStrictEqual(internalSchema); }); it('correctly resolves relative bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinition('#/$defs/bundledRelativeRefWithAnchor', bundledSchema)).toBe( - absoluteInternalSchema.$defs!.string, + internalSchema.$defs!.string, ); }); it('correctly resolves indirect bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinition('#/$defs/indirectRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); + expect(findSchemaDefinition('#/$defs/indirectRef', bundledSchema)).toStrictEqual(internalSchema); }); it('correctly resolves refs with explicit base URI in a bundled JSON Schema', () => { expect( findSchemaDefinition('bundled.ref.json', bundledSchema, 'https://example.com/undefined.ref.json'), - ).toStrictEqual(absoluteInternalSchema); + ).toStrictEqual(internalSchema); }); it('correctly resolves local refs with explicit base URI in a bundled JSON Schema', () => { expect(findSchemaDefinition('#/properties/num', bundledSchema, 'https://example.com/bundled.ref.json')).toBe( - absoluteInternalSchema.properties!.num, + internalSchema.properties!.num, ); }); it('throws error when relative ref is undefined in a bundled JSON Schema', () => { @@ -255,7 +230,7 @@ describe('findSchemaDefinition()', () => { }); it('throws error when relative ref with anchor is undefined in a bundled JSON Schema', () => { expect(() => findSchemaDefinition('#/$defs/undefinedRefWithAnchor', bundledSchema)).toThrowError( - 'Could not find a definition for https://example.com/bundled.ref.json#/$defs/undefined', + 'Could not find a definition for #/$defs/undefined', ); }); it('throws error when local ref is undefined in a bundled JSON Schema with explicit base URI', () => { @@ -270,7 +245,7 @@ describe('findSchemaDefinition()', () => { }); it('throws error when ref is a deep circular reference in a bundled JSON Schema', () => { expect(() => findSchemaDefinition('#/$defs/circularRef', bundledSchema)).toThrowError( - 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> https://example.com/bundled.schema.json/#/$defs/circularRef', + 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> /bundled.schema.json/#/$defs/circularRef -> #/$defs/circularRef', ); }); }); @@ -319,13 +294,11 @@ describe('findSchemaDefinitionRecursive()', () => { ).toThrowError('Could not find a definition for #/properties/num'); }); it('correctly resolves absolute bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchema)).toStrictEqual( - absoluteInternalSchema, - ); + expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchema)).toStrictEqual(internalSchema); }); it('correctly resolves absolute bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRefWithAnchor', bundledSchema)).toBe( - absoluteInternalSchema.properties!.num, + internalSchema.properties!.num, ); }); it('correctly resolves absolute bundled refs in arrays within a JSON Schema Draft 2020-12', () => { @@ -336,31 +309,29 @@ describe('findSchemaDefinitionRecursive()', () => { it('correctly resolves absolute bundled refs within a JSON Schema Draft 2020-12 without an `$id`', () => { const { $id: d, ...bundledSchemaWithoutId } = bundledSchema; expect(findSchemaDefinitionRecursive('#/$defs/bundledAbsoluteRef', bundledSchemaWithoutId)).toStrictEqual( - absoluteInternalSchema, + internalSchema, ); }); it('correctly resolves relative bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinitionRecursive('#/$defs/bundledRelativeRef', bundledSchema)).toStrictEqual( - absoluteInternalSchema, - ); + expect(findSchemaDefinitionRecursive('#/$defs/bundledRelativeRef', bundledSchema)).toStrictEqual(internalSchema); }); it('correctly resolves relative bundled refs with anchors within a JSON Schema Draft 2020-12', () => { expect(findSchemaDefinitionRecursive('#/$defs/bundledRelativeRefWithAnchor', bundledSchema)).toBe( - absoluteInternalSchema.$defs!.string, + internalSchema.$defs!.string, ); }); it('correctly resolves indirect bundled refs within a JSON Schema Draft 2020-12', () => { - expect(findSchemaDefinitionRecursive('#/$defs/indirectRef', bundledSchema)).toStrictEqual(absoluteInternalSchema); + expect(findSchemaDefinitionRecursive('#/$defs/indirectRef', bundledSchema)).toStrictEqual(internalSchema); }); it('correctly resolves relative refs with explicit base URI in a bundled JSON Schema', () => { expect( findSchemaDefinitionRecursive('bundled.ref.json', bundledSchema, [], 'https://example.com/undefined.ref.json'), - ).toStrictEqual(absoluteInternalSchema); + ).toStrictEqual(internalSchema); }); it('correctly resolves local refs with explicit base URI in a bundled JSON Schema', () => { expect( findSchemaDefinitionRecursive('#/properties/num', bundledSchema, [], 'https://example.com/bundled.ref.json'), - ).toBe(absoluteInternalSchema.properties!.num); + ).toBe(internalSchema.properties!.num); }); it('throws error when relative ref is undefined in a bundled JSON Schema', () => { expect(() => findSchemaDefinitionRecursive('#/$defs/undefinedRef', bundledSchema)).toThrowError( @@ -369,7 +340,7 @@ describe('findSchemaDefinitionRecursive()', () => { }); it('throws error when relative ref with anchor is undefined in a bundled JSON Schema', () => { expect(() => findSchemaDefinitionRecursive('#/$defs/undefinedRefWithAnchor', bundledSchema)).toThrowError( - 'Could not find a definition for https://example.com/bundled.ref.json#/$defs/undefined', + 'Could not find a definition for #/$defs/undefined', ); }); it('throws error when local ref is undefined in a bundled JSON Schema with explicit base URI', () => { @@ -389,7 +360,34 @@ describe('findSchemaDefinitionRecursive()', () => { }); it('throws error when ref is a deep circular reference in a bundled JSON Schema', () => { expect(() => findSchemaDefinitionRecursive('#/$defs/circularRef', bundledSchema, [])).toThrowError( - 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> https://example.com/bundled.schema.json/#/$defs/circularRef', + 'Definition for #/$defs/circularRef contains a circular reference through /bundled.ref.json/#/$defs/circularRef -> /bundled.schema.json/#/$defs/circularRef -> #/$defs/circularRef', ); }); }); + +describe('makeAllReferencesAbsolute()', () => { + it('correctly makes all references absolute in a JSON Schema', () => { + expect(makeAllReferencesAbsolute(internalSchema, internalSchema[ID_KEY]!)).toStrictEqual({ + ...internalSchema, + $defs: { + ...internalSchema.$defs, + circularRef: { $ref: 'https://example.com/bundled.schema.json/#/$defs/circularRef' }, + undefinedRef: { $ref: 'https://example.com/bundled.ref.json#/$defs/undefined' }, + }, + properties: { + ...internalSchema.properties, + string: { $ref: 'https://example.com/bundled.ref.json#/$defs/string' }, + allOf: { + allOf: [ + { + $ref: 'https://example.com/bundled.ref.json#/$defs/string', + }, + { + title: 'String', + }, + ], + }, + }, + }); + }); +});