jacksmith15 / statham-schema

Statham is a Python Model Parsing Library for JSON Schema.
https://statham-schema.readthedocs.io/en/latest/
MIT License
39 stars 10 forks source link

Infinite recursion when "$ref" at root. #92

Open NfNitLoop opened 1 year ago

NfNitLoop commented 1 year ago

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

> 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.

NfNitLoop commented 1 year ago

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.```
jacksmith15 commented 1 year ago

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.

NfNitLoop commented 1 year ago

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:

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. 🤷

NfNitLoop commented 1 year ago

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! 👍