jsonschema2md
jsonschema2md copied to clipboard
Recursive reference causes problem
Hi all,
I encountered an issue when trying to generate documentation from a set of schemas that contain a circular reference.
{
"$id": "https://example.org/foo",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"foo": {
"$ref": "#/definitions/bar"
}
},
"definitions": {
"bar": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/bar"
}
}
]
}
}
}
I get the following exception...
(node:53742) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
at formatRaw (internal/util/inspect.js:1014:12)
at formatValue (internal/util/inspect.js:793:10)
at inspect (internal/util/inspect.js:326:10)
at formatWithOptionsInternal (internal/util/inspect.js:1994:40)
at formatWithOptions (internal/util/inspect.js:1878:10)
at Object.value (internal/console/constructor.js:306:14)
at Object.log (internal/console/constructor.js:341:61)
at reducer (/usr/local/lib/node_modules/@adobe/jsonschema2md/lib/traverseSchema.js:17:11)
at Array.reduce (<anonymous>)
at reducer (/usr/local/lib/node_modules/@adobe/jsonschema2md/lib/traverseSchema.js:29:39)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:53742) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:53742) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
The schema seems to be valid. I can avoid the issue by rewriting the schemas as...
{
"$id": "https://example.org/foo",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"foo": {
"$ref": "#/definitions/bar"
}
},
"definitions": {
"bar": {
"$id": "https://example.org/bar",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"$ref": "https://example.org/bar"
}
}
]
}
}
}
It seems that the error surfaces only with relative references.
When traversing a schema, it keeps track of a Set of "seen" items, but the "get" from the proxy handler returns a new Proxy object for every subschema, whether it has been retrieved previously or not, so the recursively retrieved subschema is a new Proxy and is not in the "seen" Set.
On the schemaProxy side, if you create a registry of previously proxied subschemas and reuse them, then the "seen" mechanism seems to work as it should. (With this patch applied, your example does not throw an error)
diff --git a/lib/schemaProxy.js b/lib/schemaProxy.js
index 2010be2..338f070 100644
--- a/lib/schemaProxy.js
+++ b/lib/schemaProxy.js
@@ -30,7 +30,7 @@ function loadExamples(file, num = 1) {
}
const handler = ({
- root = '', filename = '.', schemas, parent, slugger,
+ root = '', filename = '.', schemas, subschemas, parent, slugger,
}) => {
const meta = {};
@@ -157,19 +157,29 @@ const handler = ({
}
// console.log('making new proxy from', target, prop, 'receiver', receiver[symbols.id]);
- const subschema = new Proxy(retval, handler({
- root: `${root}/${prop}`,
- parent: receiver,
- filename,
- schemas,
- slugger,
- }));
+ let subschema;
+ if (subschemas.has(retval)) {
+ subschema = subschemas.get(retval);
+ }
+ else {
+ subschema = new Proxy(retval, handler({
+ root: `${root}/${prop}`,
+ parent: receiver,
+ filename,
+ schemas,
+ subschemas,
+ slugger,
+ }));
+
+ subschemas.set(retval, subschema);
+ }
if (subschema[keyword`$id`]) {
// stow away the schema for lookup
// eslint-disable-next-line no-param-reassign
schemas.known[subschema[keyword`$id`]] = subschema;
}
+
return subschema;
}
return retval;
@@ -187,12 +197,13 @@ module.exports.loader = () => {
known: {},
files: {},
};
+ const subschemas = new Map();
const slugger = ghslugger();
return (schema, filename) => {
// console.log('loading', filename);
- const proxied = new Proxy(schema, handler({ filename, schemas, slugger }));
+ const proxied = new Proxy(schema, handler({ filename, schemas, subschemas, slugger }));
schemas.loaded.push(proxied);
if (proxied[keyword`$id`]) {
// stow away the schema for lookup
:tada: This issue has been resolved in version 7.1.2 :tada:
The release is available on:
Your semantic-release bot :package::rocket: