Infinite recursion when "$ref" at root.
Describe the bug
When the root type of a schema is a $ref, statham has infinite recursion.
Steps to Reproduce
Working schema:
{
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": false,
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#"
}
Schema that breaks statham:
{
"$ref": "#/definitions/Example",
"definitions": {
"Example": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
Expected behaviour I expect both above schemas to output identical/similar models.
Actual behaviour stack overflow:
Traceback (most recent call last):
File "/opt/homebrew/bin/statham", line 8, in <module>
sys.exit(entry_point())
^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/statham/__main__.py", line 105, in entry_point
output.write(main(uri)) # pragma: no cover
^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/statham/__main__.py", line 94, in main
RefDict.from_uri(input_uri), context_labeller=title_labeller()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_dict.py", line 46, in from_uri
uri, value = _resolve_uri(uri)
^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_dict.py", line 11, in _resolve_uri
return resolve_uri_to_urivalue_pair(uri)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 183, in resolve_uri_to_urivalue_pair
remote_uri, value = pointer.resolve_with_uri(document)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 98, in resolve_with_uri
remote_uri, remote = self.resolve_remote_with_uri(doc, -1)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 58, in resolve_remote_with_uri
resolved_remote_uri, value = resolve_uri_to_urivalue_pair(remote_uri)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 183, in resolve_uri_to_urivalue_pair
[ … ]
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 98, in resolve_with_uri
remote_uri, remote = self.resolve_remote_with_uri(doc, -1)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 51, in resolve_remote_with_uri
isinstance(doc, abc.Mapping) and isinstance(doc.get("$ref"), str)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen abc>", line 119, in __instancecheck__
RecursionError: maximum recursion depth exceeded in comparison
System information
- OS: MacOS Ventura 13.3.1a (arm64)
- Python version: 3.11.3
- Installed python libraries: (output of
pip freeze)
> pip freeze | grep statham
statham-schema==0.14.0
Workaround
I guess I won't use a "$ref" at my root.
Though the schema generator I'm using (https://github.com/StefanTerdell/zod-to-json-schema) does this by default.
I also tried just pointing directly to the definition I wanted to generate instead of relying on the "$ref" at the root:
But I got an error:
> statham --input example.json#/definitions/Example
Traceback (most recent call last):
File "/opt/homebrew/bin/statham", line 8, in <module>
sys.exit(entry_point())
^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/statham/__main__.py", line 105, in entry_point
output.write(main(uri)) # pragma: no cover
^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/statham/__main__.py", line 94, in main
RefDict.from_uri(input_uri), context_labeller=title_labeller()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_dict.py", line 46, in from_uri
uri, value = _resolve_uri(uri)
^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_dict.py", line 11, in _resolve_uri
return resolve_uri_to_urivalue_pair(uri)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 183, in resolve_uri_to_urivalue_pair
remote_uri, value = pointer.resolve_with_uri(document)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 98, in resolve_with_uri
remote_uri, remote = self.resolve_remote_with_uri(doc, -1)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/ref_pointer.py", line 54, in resolve_remote_with_uri
remote_uri = self.uri.relative(doc["$ref"]).get(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/json_ref_dict/uri.py", line 65, in relative
raise ReferenceParseError(
json_ref_dict.exceptions.ReferenceParseError: Reference: '#/definitions/Example' from context 'example.json#/definitions/Example' is self-referential. Cannot resolve.```
A root-level $ref doesn't entirely sense from my reading of the spec. From the JSON Reference spec:
Any members other than "$ref" in a JSON Reference object SHALL be ignored.
The definitions key is immediately discarded, and the ref target references itself, causing a recursive loop.
Just to give some context:
Statham is the first JSONSchema tool I've encountered that doesn't handle a root $ref. Admittedly, I'm not usually working with JSONSchemas. I've just been investigating options over the last couple days for a use case that came up.
Things that supported it:
- zod-to-json-schema
- https://www.jsonschemavalidator.net/ which I found when just trying to do a quick schema test online.
- https://github.com/koxudaxi/datamodel-code-generator which I think I'm going to end up using for this project.
The main advantage that I see is that the root type gets a name, instead of relying on its file name to name it. Plus all your schemas are defined in the same "definitions" block instead of having them in two different places. 🤷
Also, I only figured that out after some testing on my part. I very nearly abandoned investigating statham earlier and just assumed the tool was broken. If you don't want to support root "$ref", it might be a good idea to check for root "$ref" and give an error that it's not supported. My initial perception was that statham might've been broken/unmaintained.
Sorry for being verbose. I also maintain some OSS projects and I prefer users report issues rather than not. Trying to return the favor. Thanks for making this tool available! 👍