Skip to content

Circular references in Definitions Object #822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
vanderlee opened this issue Oct 29, 2016 · 17 comments
Closed

Circular references in Definitions Object #822

vanderlee opened this issue Oct 29, 2016 · 17 comments

Comments

@vanderlee
Copy link
Contributor

Syntactically, it seems possible to construct an infinite circular reference using Definitions Objects.

Obviously, such a reference could never actually be called or returned. I can't find any conclusive answer on this in the specs. The editor at http://editor.swagger.io/ seems to accept it as valid syntax, but produces an undefined Schema.

How should such a situation be treated?

Example

{
    "swagger": "2.0",
    "info": {
        "title": "vanderlee/PHPSwaggerGen",
        "version": "2.3.7"
    },
    "host": "example.com",
    "basePath": "\/base",
    "paths": {
        "\/customers": {
            "get": {
                "tags": [
                    "Test"
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#\/definitions\/Person"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "Address": {
            "type": "object",
            "required": [
                "city",             
        "occupant"
            ],
            "properties": {
                "city": {
                    "type": "string"
                },
                "occupant": {
                    "$ref": "#\/definitions\/Person"
                }
            }
        },
        "Person": {
            "type": "object",
            "required": [
                "name",
                "home"
            ],
            "properties": {
                "name": {
                    "type": "string"
                },
                "home": {
                    "$ref": "#\/definitions\/Address"
                }
            }
        }
    },
    "tags": [
        {
            "name": "Test"
        }
    ]
}
@webron
Copy link
Member

webron commented Oct 31, 2016

Putting tooling aside, what you describe is not something that can actually be represented as a JSON and returned by the API. The circular dependencies is not the issue, it's the fact you made it infinite by requiring the properties to always exist.

Say you want to have an Address that specified the occupant, but also one that can be used to specify the address to a Person, you'd need to break Address to two models, and using them for different purposes.

@vanderlee
Copy link
Contributor Author

You're pretty much repeating what I said.
Fixing the JSON is not the point.

As far as I can tell, the Open-API spec allows these infinite loops, even though it's impossible in practice (as you reiterated). Shouldn't the spec explicitely forbid such situations or atleast address them in a way that allows implementors to deal with them?

A standard specification document must be unambigious, on this particular issue, it is not.

@webron
Copy link
Member

webron commented Nov 1, 2016

It wasn't clear from your original question what it is that you actually seek.

It's not the job of the spec to deal with such a case. It's JSON Schema that allows it, and not the OpenAPI spec itself. You can do a lot of crazy things or things that won't make sense using JSON Schema, and we allow users to use it for modeling for its common general use, but we cannot safe-guard every case.

@vanderlee
Copy link
Contributor Author

Okay, too bad the spec will leave in this ambiguity. I'll just add checks to my own tools to prevent users accidentally specifying impossibilities.

@ePaul
Copy link
Contributor

ePaul commented Nov 10, 2016

I can imagine people wanting to have such circular dependencies – you'll just have some of the properties being non-required. The actually transmitted object then will have its depth truncated after some level (e.g. by omitting further references from all entities which were already included before).

@SaravanZ
Copy link

SaravanZ commented May 6, 2018

Hi , Is there any resolution here so far (Open API 2.0, 3.0) ? or this is how the spec is meant to render the references with circular pattern? Thanks.

@tedepstein
Copy link
Contributor

Just read through this, and I think a few points are worth adding, or reiterating:

  • Circular references in the schemas do not necessarily imply circular references in the data. It's possible for Person to have properties like spouse, children, or parents that also refer to Person, but will never realistically form a cycle in an actual message.
  • Where two or more references form a cycle, making each of those references required does indeed make the schema unsatisfiable. There is no finite message that could possibly conform to the schema. This is just one of many ways to create an unsatisfiable schema. Some of these conditions are potentially easy to detect, but others (like conflicting regular expressions in pattern constraints) are a lot harder to prove satisfiable or not. I suspect this is why JSON Schema (and therefore OpenAPI) decided to leave it up to users.
  • What's being called "ambiguity" in the OpenAPI and/or JSON Schema specification is not really ambiguity. It's a partitioning of responsibilities, leaving it up to the user to ensure that schemas are satisfiable and logically consistent. Of course, tools can try to detect and flag logical inconsistencies. The specification doesn't require tools to do this, but it doesn't preclude it.

@SaravanZ
Copy link

SaravanZ commented May 7, 2018

Thanks for adding your thoughts here.
Re.your first point, not sure what you mean by "realistically form a cycle in an actual message".
To be exact , in my case : The circular reference is more of an object referring to itself. Like an employee - manager relationship.
{
"existingFiles": [
{
"type": "TBC",
"code": "TBC",
"details": "TBC",
"authors": [
{
"type": "TBC",
"fullName": "David Michael Jones",
"title": "TBC",
"firstName": "TBC",
"middleName": "TBC",
"lastName": "TBC",
"coAuthors": [
{
//refer author {}
}
]
}
]
}
]
}

But the Open API spec 2.0 renders null in place of reference in the example and just [] for a model.
To me , what I'm trying to model here is perfectly valid as all author's attributes are applicable to co-authors as well. So I should be able to re-use the definition of author.
I expected the spec would render a model like
"authors":[
{
//attributes
"coAuthors":[{//same attributes as in author}]
}
]
In other words an author object encloses a coAuthors objects array of type author. My team here have created the Java representation of this model without any trouble. So not sure what's contradicting when it comes to the spec or the schemas used within?...

@darrelmiller
Copy link
Member

@SaravanZ You keep using phrases like "OpenAPI spec 2.0 renders". The OpenAPI specification doesn't render anything. Do you actually mean the Swagger tooling "renders"?

@SaravanZ
Copy link

SaravanZ commented May 7, 2018

@darrelmiller for info , I'm not trying to generate any code from the spec etc., all I'm trying to achieve is to describe the model of my API using Open API 2.0 (swagger 2.0). So I think I'm using incorrect terms - so swagger tooling it is (if that is what processes the YAML and renders as specification).

@darrelmiller
Copy link
Member

darrelmiller commented May 7, 2018

@SaravanZ Swagger-UI is one tool that takes an OpenAPI description and renders HTML documentation of the API. If that's what you are using then I would recommend raising an issue there.

@SaravanZ
Copy link

SaravanZ commented May 7, 2018

Thanks @darrelmiller. I believe swagger editor that I'm using locally uses the same tooling , so I can raise it there.

@loganfreeman
Copy link

It is sad that circular reference could result in Java heap space error.

@grokify
Copy link

grokify commented May 6, 2020

Here's some info for people running into this issue.

I ran into this issue as we are currently auto-generating a spec from Java code with circular definitions due to this type of structure. The use case is specified above:

@tedepstein wrote:

Just read through this, and I think a few points are worth adding, or reiterating:

  • Circular references in the schemas do not necessarily imply circular references in the data. It's possible for Person to have properties like spouse, children, or parents that also refer to Person, but will never realistically form a cycle in an actual message.

API Reference

API Reference tools have varying levels of support:

  • Swagger UI: Schema definition will show property name but no definition. Example will not have the property name at all.
  • Redoc: combined example / definition will show property name but empty object "{}" defintion
  • ReadMe.io: won't render at all.

OpenAPI Generator

I use the OpenAPI Generator to auto-generate Go client SDKs. Out of the box, I couldn't create a working Go client SDK, but Go supports circular references so I did some post processing and it now works. Read more here.

@delanym
Copy link

delanym commented Sep 9, 2020

I can imagine people wanting to have such circular dependencies – you'll just have some of the properties being non-required. The actually transmitted object then will have its depth truncated after some level (e.g. by omitting further references from all entities which were already included before).

I'm still not sure what to do about this. I can't mock my schema because of one circular dependency, even though its child is not required. How do I get around it?

@handrews
Copy link
Member

handrews commented Sep 9, 2020

@delanym you need to ask that question in the repository for the tooling that you are using. This repository is for the specification itself, and this is not a problem with the specification.

To reiterate using points @tedepstein wrote in an earlier comment:

Where two or more references form a cycle, making each of those references required does indeed make the schema unsatisfiable. There is no finite message that could possibly conform to the schema. This is just one of many ways to create an unsatisfiable schema. Some of these conditions are potentially easy to detect, but others (like conflicting regular expressions in pattern constraints) are a lot harder to prove satisfiable or not. I suspect this is why JSON Schema (and therefore OpenAPI) decided to leave it up to users.

Yes, this is why we (JSON Schema project) do not forbid this in the spec. This sort of things is appropriate for a linter. Just like a programming language compiler does not catch every problem, so you use a linter. The linter will often catch things that may or may not be a problem, and it can check for things that are too expensive to check in the compiler (or in the case of JSON Schema, the validator, code generator, etc.)

What's being called "ambiguity" in the OpenAPI and/or JSON Schema specification is not really ambiguity. It's a partitioning of responsibilities, leaving it up to the user to ensure that schemas are satisfiable and logically consistent. Of course, tools can try to detect and flag logical inconsistencies. The specification doesn't require tools to do this, but it doesn't preclude it.

Yes to this as well. To understand why the general case of detecting this is hard, consider a combination of $refs and oneOf. You might write a schema that technically has an unsatisfiable cycle through one branch of the oneOf, except that you know that whenever that branch of the oneOf is used, some other aspect of the data will ensure that the cycle never occurs. There are many situations like this that are possible to write: unsatisfiable in some conditions, but in practical terms just fine. These cases are impossible to determine because the implementation doesn't know that your actual real data won't hit the "problem." So again, a linter can warn that there is a potential problem, but the validator should allow it because in practice it might not be a problem at all.

We're trying to encourage a larger ecosystem of linters :-)

@dok
Copy link

dok commented Sep 22, 2020

Hey everyone (and @grokify)! I'm a ReadMe dev that recently worked on this issue.

We do support circular reference specs now. For an immediate example, see check out this link. Here's another example (click on circular reference in the dropdown).

The nitty-gritty details on rendering an OpenAPI spec is that when we do detect a circular reference within a spec, we attempt to render. When it is a oneOf, allOf, anyOf or an array of references, we allow rendering, because the circular reference is circumvented by the fact that adding an array item within the form is a user-driven action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests