prism: Circular $ref pointer not supported

Describe the bug

A clear and concise description of what the bug is. Pointing prism to a file that contains components that use recursive field definition, Prism fails with the message:

Circular $ref pointer found at /tmp/test.yaml#/components/schemas/Person/properties/spouse

To Reproduce

A minimal API definition triggering the error message:

info:
  title: "Circular reference example"
  description: "Exemplifies a schema with circular references"
  version: 0.1.0

paths:
  /directory:
    get:
      responses:
        "200":
          description: "The request was successfully processed"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Person"
              example:
                name: John
                spouse:
                  name: Cynthia

components:

  schemas:

    Person:
      nullable: true
      properties:
        name:
          type: string
          nullable: false
        spouse:
          $ref: "#/components/schemas/Person"

Expected behavior

As the OAS file specifies a valid API, the server should be able to mock that API. While having a circular reference can theoretically lead to a infinite recursion, this can be in practice avoided by having a nullable reference to the child object.

Environment

  • Prism version: [4.1.0]

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 6
  • Comments: 27 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Hey Everyone,

Finally got some time to work on circular ref support in prism, if you update prism to the latest, circular refs should be supported. Please create a new issue if something is not working as expected!

Hi,

I just faced the same issue today. I can understand that dynamically mocking and serializing a circular reference causes an issue. In my case though, the open api file provides a response example and prism would never have to do such a thing.

Nonetheless, just describing the circular reference in the OpenApi specification file prevent prism from booting 😕 Is there any way to work around this ?

3 cents from me.

Maybe if you have circular references and it’s hard(or maybe even impossible) to generate response but you have an example - then stick with it - otherwise - fail?

Sample schema:

swagger: "2.0"
info:
  description: "Test"
  version: "1.0.0"
  title: "Swagger Petstore"
  termsOfService: "http://swagger.io/terms/"
  contact:
    email: "apiteam@swagger.io"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "petstore.swagger.io"
basePath: "/v2"
tags:
- name: "users"
  description: "Everything about your Users"
  externalDocs:
    description: "Find out more"
    url: "http://swagger.io"
schemes:
- "http"
paths:
  /users/me:
    get:
      tags:
      - "users"
      summary: "get any pet"
      description: "no description for this endpoint"
      operationId: "get"
      produces:
      - "application/json"
      responses:
        "200":
          description: "successful operation"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/User"
        "400":
          description: "Invalid status value"
definitions:
  User:
    type: "object"
    required:
    - "id"
    - "name"
    - "friends"
    properties:
      id:
        type: "integer"
        format: "int64"
        example: 1
      name:
        type: "string"
        example: "Lincoln"
      friends:
        type: "array"
        items:
          $ref: "#/definitions/User"
        example: [{
          id: 2,
          name: "John",
          friends: []
        }]

Generated example by swagger editor: image

Actual frustrating picture that I get with current version of prism:

image

P.S. there’s a difference between living in an ideal world(building ideal API) vs adopting. P.P.S. actually prism 3.x was doing this trick with examples. I don’t know why this functionality was removed 😦

BR, Dionis

Another $0.02 to ask for prioritizing support for stopping at non-required recursive fields or null.

We’re building OData APIs where this is incredibly commonplace, because the schema is full of non-required fields that allow you to explicitly use a $expand query parameter to add a navigation across a link relationship (which doesn’t implicitly cause the reciprocal link to get $expanded, so no recursion in the response body in practice).

However the schema needs to include all the possible combinations that could be $expanded, thus frequently producing recursion.

I’m blocked from any chance of adopting Prism without this support.

@XVincentX I don’t think @andrewsomething was chasing for an update, just asking for insight into how circular references work in the validation proxy.

@andrewsomething Hello! Validation and response generation are two very different concepts. Trying to create JSON based on schemas which have self-referencing relationships and knowing when to stop trying to create more JSON in that infinite loop is the main problem discussed in this thread. Looking at an instance of JSON and figuring out if it’s valid against the schema is waaaaaaay easier.

I’d recommend giving it a try and see if you have any problems. If you do, let us know. If not, enjoy!

Hey @dargiri,

I’ve explained why the functionality was disabled in https://github.com/stoplightio/prism/issues/1456#issuecomment-702176386

TL;DR: it’s not easy to know where we can handle circular references successfully and where not.

Rather than trying to do something according to common sense, ship something for the sake of and then get tons of issues with edge cases or one scenario making another one not work correctly — I decided it was a better idea to admit we do not have a story flashed out yet and, as long we don’t have it, I am not going to let it happen.

If this takes the priority, more than happy to work seriously on this.

@philsturgeon Wrong mention here. I’m not the author of the spouse example 😃

@linkdd could you narrow it down a bit? Haha can’t go digging through all that! 😃

@blemasle I don’t understand this JSON:

{"name":"John","spouse":{"name":"Cynthia","spouse":null}}

This looks like John is married to Cynthia but Cynthia is not married to John.

Poor John.

This doesn’t make any sense fundamentally so it’s going to lead to confusing conversations trying to use it as an example.

If you wanted a null in there, then sure your OpenAPI should be this:

    Person:
      properties:
        name:
          type: string
        spouse:
          oneOf: 
            - $ref: "#/components/schemas/Person"
            - nullable: true

Supporting this in Prism would just be a case of picking oneOf’s at random, which I remember discussing in the past. Some people want oneOfs to always show the first, which is where you’d get into trouble, because again the chain would never be broken by a null.

Possibly a more logical example would be parent.

Person:
  properties:
    name:
      type: string
    manager:
      oneOf: 
        - $ref: "#/components/schemas/Person"
        - nullable: true

This is another chain which is not infinite but could be. It makes me wonder why people design APIs like this because clients should be able to follow a link if they want that information instead of having recursion thrust upon them.

{
  "name":"John",
  "manager": {
    "name":"Cynthia",
    "manger":{
      "name":"Frank",
      "manger":{
        "name":"Mary",
        "manger":{
          "name":"George",
          "manger":{
            "name":"Big BOSS CEO",
            "manger":null
          }
        }
      }
    }
  }
}

If you’re using Prism you’re probably designing an API right now, and if you’re doing that its not too late to change the design. Use any Hypermedia Format and insert links, which might look as simple as this:

{
  "name":"John",
  "links": {
    "manager":"https://api.example.com/people/cynthia"
  }
}

Prism is happy. Your API is happy. Your clients are happy.

If anyone has any other examples of circular references which should exist, lmk, and in the men time @XVincentX can we look into that oneOf shortcut with null? We can’t do anything about any of these other situations so far but we can t least handle that.

@XVincentX thanks for your reply 😃

If I can add: in the provided example, in practice would become Person (A) -> Spouse -> Person (B) -> Spouse -> Person (A) (can be a copy object, not memory reference to the previously defined one) - maybe there could be an option to resolve an arbitrary number of recursive levels and then give up (with the difference that this number could be overridden in the config)?

After all, the contract doesn’t specify how deep the circular reference needs to be (therefore one level would always be enough to match the contract?).