diff --git a/src/core/plugins/json-schema-2020-12-samples/fn/get-json-sample-schema.js b/src/core/plugins/json-schema-2020-12-samples/fn/get-json-sample-schema.js index 84e09f591a0..e009d106ef6 100644 --- a/src/core/plugins/json-schema-2020-12-samples/fn/get-json-sample-schema.js +++ b/src/core/plugins/json-schema-2020-12-samples/fn/get-json-sample-schema.js @@ -13,8 +13,94 @@ const defaultStringifyTypes = ["object"] const makeGetJsonSampleSchema = (getSystem) => (schema, config, contentType, exampleOverride) => { const { fn } = getSystem() + + // Deep-resolve any local $ref nodes inside the schema + const deepResolveRefs = (s, seen = new Set()) => { + try { + if (s == null || typeof s !== "object") return s + + if (Array.isArray(s)) { + return s.map((it) => deepResolveRefs(it, seen)) + } + + if (typeof s.$ref === "string") { + const ref = s.$ref + if (seen.has(ref)) { + // keep as-is to avoid infinite recursion + return s + } + seen.add(ref) + + const sys = getSystem() + const specSelectors = sys?.getSystem?.().specSelectors + + if (fn && typeof fn.getRefSchemaByRef === "function") { + const resolved = fn.getRefSchemaByRef(ref) + if (resolved) return deepResolveRefs(resolved, seen) + } + + if ( + specSelectors && + typeof specSelectors.findDefinition === "function" + ) { + // model name extraction heuristic (shared with other codepaths) + const getModelNameFromRef = (refStr) => { + if (typeof refStr !== "string") return null + if (refStr.indexOf("#/definitions/") !== -1) { + return decodeURIComponent( + refStr.replace(/^.*#\/definitions\//, "") + ) + } + if (refStr.indexOf("#/components/schemas/") !== -1) { + return decodeURIComponent( + refStr.replace(/^.*#\/components\/schemas\//, "") + ) + } + const hashIdx = refStr.indexOf("#") + if (hashIdx !== -1) { + const frag = refStr.slice(hashIdx + 1) + if (frag.indexOf("/components/schemas/") !== -1) { + return decodeURIComponent( + frag.replace(/^.*\/components\/schemas\//, "") + ) + } + if (frag.indexOf("/definitions/") !== -1) { + return decodeURIComponent( + frag.replace(/^.*\/definitions\//, "") + ) + } + } + return null + } + + const modelName = getModelNameFromRef(ref) + if (modelName) { + const def = specSelectors.findDefinition(modelName) + if (def) { + const defJS = typeof def.toJS === "function" ? def.toJS() : def + // recursively resolve refs inside the referenced definition + return deepResolveRefs(defJS, seen) + } + } + } + return s + } + + const out = {} + for (const key in s) { + if (!Object.prototype.hasOwnProperty.call(s, key)) continue + out[key] = deepResolveRefs(s[key], seen) + } + return out + } catch (e) { + return s + } + } + + const schemaToSample = deepResolveRefs(schema) + const res = fn.jsonSchema202012.memoizedSampleFromSchema( - schema, + schemaToSample, config, exampleOverride ) diff --git a/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx b/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx index 1886703f8ba..b10edcf4d19 100644 --- a/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx +++ b/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx @@ -34,6 +34,22 @@ const JSONSchema = forwardRef( ref ) => { const fn = useFn() + // Attempt to resolve unresolved $ref to a local schema with circular detection. + // This is also to avoid infinite expansion when 'expand all' is triggered. + try { + if ( + schema && + schema.$ref && + typeof fn?.getRefSchemaByRef === "function" + ) { + const refSchema = fn.getRefSchemaByRef(schema.$ref) + if (refSchema && typeof refSchema === "object") { + schema = refSchema + } + } + } catch (e) { + // ignore resolution errors and fall back to provided schema + } // this implementation assumes that $id is always non-relative URI const pathToken = identifier || schema?.$id || name const { path } = usePath(pathToken) diff --git a/src/core/plugins/json-schema-2020-12/components/keywords/$ref.jsx b/src/core/plugins/json-schema-2020-12/components/keywords/$ref.jsx index 4971bcef57b..eba128d4f0c 100644 --- a/src/core/plugins/json-schema-2020-12/components/keywords/$ref.jsx +++ b/src/core/plugins/json-schema-2020-12/components/keywords/$ref.jsx @@ -1,13 +1,55 @@ /** * @prettier */ -import React from "react" - +import React, { useContext } from "react" import { schema } from "../../prop-types" +import { JSONSchemaContext } from "../../context" const $ref = ({ schema }) => { if (!schema?.$ref) return null + const fn = useContext(JSONSchemaContext).fn + + // If the system exposed a ref resolver, attempt to show a friendly label + try { + if (fn && typeof fn.getRefSchemaByRef === "function") { + const resolved = fn.getRefSchemaByRef(schema.$ref) + if (resolved && typeof resolved === "object") { + // array shorthand + const type = Array.isArray(resolved.type) + ? resolved.type[0] + : resolved.type + if (type === "array") { + const items = resolved.items + const itemLabel = items?.title || items?.$ref || items?.$id || "any" + return ( +