swagger-ui icon indicating copy to clipboard operation
swagger-ui copied to clipboard

Recursive rendering needs work

Open julienkosinski opened this issue 7 years ago • 55 comments

Hello,

  • swagger-ui version 3.0.17
  • a swagger file reproducing the issue : https://gist.githubusercontent.com/julienkosinski/287a3b77e25a6ca3e6f7a0293bea4e3d/raw/fd94d4f1457ccbaa538f27996dba5d077d9c6f36/gistfile1.txt

I have the following model definitions spec:

    "definitions": {
        "models.Equipment": {
            "title": "Equipment",
            "type": "object",
            "properties": {
                "Features": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/models.Feature"
                    }
                },
                "Id": {
                    "type": "integer",
                    "format": "int64"
                },
                "IdType": {
                    "type": "string"
                },
                "Name": {
                    "type": "string"
                },
                "Price": {
                    "type": "integer",
                    "format": "int32"
                }
            }
        },
        "models.Feature": {
            "title": "Feature",
            "type": "object",
            "properties": {
                "Equipments": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/models.Equipment"
                    }
                },
                "Id": {
                    "type": "integer",
                    "format": "int64"
                },
                "IdFeature": {
                    "$ref": "#/definitions/models.Feature"
                },
                "Name": {
                    "type": "string"
                }
            }
        }
    }

In the Feature model, the Equipments property is defined as an array of Equipment models, but Swagger UI 3.x renders it as an empty array []. Everywhere Feature model is used, like as examples for POST method in Feature I have this kind of display.

swagger-ui bug

We think it may be a bug caused by circular references. Thanks to Helen.

Thank you very much!

julienkosinski avatar Jul 02 '17 18:07 julienkosinski

@shockey @webron I mention you just to make sure this has been noticed, don't be offended :).

julienkosinski avatar Jul 15 '17 20:07 julienkosinski

@julienkosinski thanks, I'll get this triaged on Monday 😄

shockey avatar Jul 15 '17 20:07 shockey

I'm seeing two things here:

  1. Feature -> Equipments is being passed a $ref to render... this shouldn't be happening.
  2. The nested model is showing a very strange generated name:

image

This isn't quite right - we'll look into it 😄

shockey avatar Jul 18 '17 00:07 shockey

In case it helps to find the issue => This indeed seems to be "Circular references" ... it also seems to appear without array wrapping.

"definitions": {
   "Classification": {
            "title": "Classification",
            "properties": {
                "ID": {
                    "type": "integer",
                    "default": 126132
                },
                "rank": {
                    "type": "string"
                },
                "scientificname": {
                    "type": "string"
                },
                "child": {
                    "$ref": "#/definitions/Classification"
                }
            },
            "xml": {
                "name": "Classification",
                "wrapped": false
            }
        }
}

Results in: image

In the 2.x versions this was handled as follows: image

Further the Model example values (JSON and XML) don't handle these nested definitions. The property is just removed from the list. But this could be another issue.

image

KevVerF avatar Jul 25 '17 11:07 KevVerF

Just ran into this as well using the OpenAPI 3 spec:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Circular reference example",
    "version": "0.1"
  },
  "paths": {},
  "components": {
    "schemas": {
      "SelfReferencingSchema": {
        "type": "object",
        "properties": {
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SelfReferencingSchema"
            }
          }
        }
      }
    }
  }
}

Yields the following: image

I'm not familiar with the history or previous implementations of this or what's possible/hard, but personally I think it'd be nice to show the title for a circular reference but not expand it by default. E.g.: image

tobymurray avatar Oct 03 '17 21:10 tobymurray

Here's a screenshot of another example I was testing (screenshot was made using v3.3.1). The structure of the schema object for the “containingFolder” attribute for “Folder” is similar to the one for “resourceFork” for “File”; I’m guessing the reason that it’s not shown that “containingFolder” is another “Folder” is due to the circularity.

swagger_ui

Here’s the OpenAPI object:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Test",
    "version": "0.1"
  },
  "components": {
    "schemas": {
      "Fork": {
        "type": "object",
        "properties": {
          "data": {
            "type": "string",
            "format": "base64"
          }
        }
      },
      "File": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "dataFork": {
            "$ref": "#/components/schemas/Fork"
          },
          "resourceFork": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Fork"
              }
            ]
          },
          "containingFolder": {
            "$ref": "#/components/schemas/Folder"
          }
        }
      },
      "Folder": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "containingFolder": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Folder"
              }
            ]
          },
          "containedItems": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/File"
                },
                {
                  "$ref": "#/components/schemas/Folder"
                }
              ]
            }
          }
        }
      }
    }
  }
}

Rinzwind avatar Oct 04 '17 10:10 Rinzwind

@Rinzwind

          "resourceFork": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Fork"
              }
            ]

"type": "null" is not valid in OpenAPI, because there's no null type - that's one of the differences from JSON Schema. OpenAPI uses the nullable attribute instead.

I'm not sure if there's a way to combine $ref with nullable. You'll probably need to add nullable: true directly to the referenced schemas (Fork and Folder).

hkosova avatar Oct 04 '17 11:10 hkosova

"type": "null" is not valid in OpenAPI, because there's no null type - that's one of the differences from JSON Schema. OpenAPI uses the nullable attribute instead.

@hkosova, I had missed that, thanks for pointing it out!

I'm not sure if there's a way to combine $ref with nullable. You'll probably need to add nullable: true directly to the referenced schemas (Fork and Folder).

I’m not sure that adding nullable directly to the referenced schemas is going to match with what I was trying to express: for the references to Fork from File, only the value for “resourceFork” can be null, the value for “dataFork” can not be null. I guess I could still express this as follows, which as far as I can tell is at least in accordance with the OpenAPI specification? But using an “anyOf” with just one contained schema seems clumsy, is there a better way to handle this?

... 
"File": {
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "dataFork": {
      "$ref": "#/components/schemas/Fork"
    },
    "resourceFork": {
      "nullable": true,
      "anyOf": [
        {
          "$ref": "#/components/schemas/Fork"
        }
      ]
    },
    ...

Rinzwind avatar Oct 04 '17 12:10 Rinzwind

@Rinzwind can't think of a different way to express it.

webron avatar Oct 04 '17 23:10 webron

@Rinzwind can't think of a different way to express it.

Thanks for the feedback. I’ve posted an issue for it at the OpenAPI Specification repository.

Rinzwind avatar Oct 05 '17 12:10 Rinzwind

Could you also use "allOf" . ? If so, would there be a difference between using the `anyOf`` method?

... 
"File": {
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "dataFork": {
      "$ref": "#/components/schemas/Fork"
    },
    "resourceFork": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Fork"
        },
        {
          "nullable": true
        }
      ]
    },
    ...

jonschoning avatar Oct 25 '17 16:10 jonschoning

allOf would actually be better than anyOf in this case.

webron avatar Oct 25 '17 17:10 webron

@webron: I am not sure whether the example given by @jonschoning has the intended semantics.

The section on “allOf” in the JSON Schema Validation proposal says: “An instance validates successfully against this keyword if it validates successfully against all schemas defined by this keyword's value.”

As far as I understand, null validates successfully against the schema { "nullable": true }. But null does not validate successfully against the schema { "$ref": "#/components/schemas/Fork" }. Therefore, I take it null also does not validate successfully against this schema:

{
  "allOf": [
    {
      "$ref": "#/components/schemas/Fork"
    },
    {
      "nullable": true
    } ] }

In other words, in @jonschoning's example, null would not be a valid value for “resourceFork”. Or did I misunderstand how schemas work in OpenAPI?

Rinzwind avatar Oct 26 '17 09:10 Rinzwind

@rinzwind at the same time that allOf is interpreted the ref is dereferenced, so the net effect results in the nullable being a part of the refs definition. You can check this understanding by considering how allOf works with appending properties to a ref with allOf

jonschoning avatar Oct 26 '17 15:10 jonschoning

Actually, @Rinzwind's point is correct, and that won't work. However, using anyOf also means that any type can be used because of nullable only schema.

webron avatar Oct 26 '17 15:10 webron

@webron I have a hard time finding a working example of how nullable works as it's not present in https://github.com/OAI/OpenAPI-Specification/tree/master/examples/v3.0 https://github.com/OAI/OpenAPI-Specification/search?utf8=%E2%9C%93&q=nullable&type= also it is not clear in https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#fixed-fields-20

jonschoning avatar Oct 26 '17 16:10 jonschoning

If you find that an example is missing, feel free to open an issue on that repo. Not really sure what's not clear about it though.

webron avatar Oct 26 '17 16:10 webron

@jonschoning As far as I understand, the following is meant by “Allows sending a null value for the defined schema. Default value is false.”: “When true, allows sending any value; when not present or false, allows sending any value except null.”

Rewording the definition according to the terminology employed in JSON Schema Validation (using in particular the wording for “uniqueItems” as inspiration):

nullable

The value of this keyword MUST be a boolean.

If this keyword has boolean value true, any instance validates successfully. If this keyword has boolean value false, any instance validates successfully unless it is null.

If not present, this keyword MUST be considered present with boolean value false.

I hope that helps. Keep in mind though that this is not a direct quote from the specification, but just my understanding of it.

Edit: I'm not quite sure whether my rewording in JSON Schema Validation terms is correct. It seems it would not imply that null validates successfully against {"type":"integer", "nullable": "true" } as intended. I'm not sure how to fix this. I'm inclined towards extending “linearity” with an additional exception that states that if the instance is null, and the keyword “nullable” is present with value true, then the instance validates successfully regardless of other keywords. But I’m not sure at this point whether that is correct.

Rinzwind avatar Oct 26 '17 21:10 Rinzwind

Ok, I understand how, thank you.

It seems one cannot simply specify a ref should be nullable then with anyOf or allOf without also changing what validates per Ron's comment

jonschoning avatar Oct 26 '17 22:10 jonschoning

It seems one cannot simply specify a ref should be nullable then with anyOf or allOf without also changing what validates per Ron's comment

Indeed, as far as I understand, a schema like {"anyOf":[...,{"nullable":"true"}]} would allow null, but also any other value. The schema {"nullable":"true"} in OpenAPI is not equivalent to the schema {"type":"null"} in (pure) JSON Schema. I find the latter easier to understand, I'm not sure why OpenAPI introduced the former. I think I intuitively grasp its meaning, but as mentioned above, I'm having some trouble giving an exact definition for it. I agree with your earlier comment that the OpenAPI specification isn't very clear about the definition of “nullable”.

Rinzwind avatar Oct 29 '17 08:10 Rinzwind

It looks like the original issue has been resolved, but there's still work to do on recursive rendering. The ticket has been renamed to reflect that.

webron avatar Jul 05 '18 17:07 webron

It looks like the original issue has been resolved, but there's still work to do on recursive rendering. The ticket has been renamed to reflect that.

Hi, can I just check in to see if anyone has been working on this? I see the recursive rendering still not working in swagger

Handled1 avatar Sep 03 '19 16:09 Handled1

+1. I have the following object:

account:
  type: object
  allOf:
    - $ref: '../openapi.yaml#/components/schemas/baseEntity'
    - properties:    
        childAccounts:
          type: array
            items:
              $ref: '#/components/schemas/

This causes an error in Swagger-UI:

image

index.js:2247 TypeError: Cannot read property '1' of undefined
    at l (core.js:244)
    at Object.p [as applyPatch] (core.js:277)
    at Object.applyPatch (index.js:940)
    at e.value (index.js:2258)
    at index.js:2242
    at Array.forEach (<anonymous>)
    at e.value (index.js:2214)
    at index.js:2279
    at async Promise.all (:5001/index 0)
(anonymous) @ index.js:2247
value @ index.js:2214
(anonymous) @ index.js:2279
Promise.then (async)
value @ index.js:2276
(anonymous) @ index.js:2232
value @ index.js:2214
a @ index.js:2412
(anonymous) @ index.js:2390
value @ index.js:2380
(anonymous) @ index.js:2406
value @ index.js:2380
(anonymous) @ index.js:2406
value @ index.js:2380
(anonymous) @ index.js:2406
value @ index.js:2380
(anonymous) @ index.js:2406
value @ index.js:2380
(anonymous) @ index.js:2406
value @ index.js:2380
(anonymous) @ index.js:2406
value @ index.js:2380
v @ index.js:2419
Rt @ index.js:2707
(anonymous) @ index.js:2802
c @ runtime.js:45
(anonymous) @ runtime.js:271
e.<computed> @ runtime.js:97
o @ asyncToGenerator.js:5
s @ asyncToGenerator.js:27
(anonymous) @ asyncToGenerator.js:34
t @ _export.js:36
(anonymous) @ asyncToGenerator.js:23
Ft @ index.js:2813
In.resolveSubtree @ index.js:2777
resolveSubtree @ index.js:22
(anonymous) @ actions.js:178
c @ runtime.js:45
(anonymous) @ runtime.js:271
e.<computed> @ runtime.js:97
o @ asyncToGenerator.js:5
s @ asyncToGenerator.js:27
Promise.then (async)
o @ asyncToGenerator.js:15
s @ asyncToGenerator.js:27
(anonymous) @ asyncToGenerator.js:34
t @ _export.js:36
(anonymous) @ asyncToGenerator.js:23
(anonymous) @ actions.js:176
(anonymous) @ actions.js:176
c @ runtime.js:45
(anonymous) @ runtime.js:271
e.<computed> @ runtime.js:97
o @ asyncToGenerator.js:5
s @ asyncToGenerator.js:27
(anonymous) @ asyncToGenerator.js:34
t @ _export.js:36
(anonymous) @ asyncToGenerator.js:23
b @ debounce.js:95
x @ debounce.js:144
w @ debounce.js:132
setTimeout (async)
w @ debounce.js:135
setTimeout (async)
(anonymous) @ debounce.js:103
E @ debounce.js:172
(anonymous) @ actions.js:243
(anonymous) @ utils.js:134
(anonymous) @ bindActionCreators.js:3
(anonymous) @ OperationContainer.jsx:155
value @ OperationContainer.jsx:89
e.notifyAll @ CallbackQueue.js:74
close @ ReactReconcileTransaction.js:78
closeAll @ Transaction.js:207
perform @ Transaction.js:154
perform @ Transaction.js:141
perform @ ReactUpdates.js:87
w @ ReactUpdates.js:170
closeAll @ Transaction.js:207
perform @ Transaction.js:154
batchedUpdates @ ReactDefaultBatchingStrategy.js:60
e @ ReactUpdates.js:198
a @ ReactUpdateQueue.js:22
enqueueSetState @ ReactUpdateQueue.js:216
s.setState @ ReactBaseClasses.js:62
i.handleChange @ connect.js:302
f @ createStore.js:172
(anonymous) @ utils.js:137
(anonymous) @ bindActionCreators.js:3
(anonymous) @ wrap-actions.js:9
r @ system.js:174
(anonymous) @ system.js:461
(anonymous) @ index.js:22
r @ system.js:174
(anonymous) @ system.js:461
(anonymous) @ actions.js:77
(anonymous) @ utils.js:134
(anonymous) @ bindActionCreators.js:3
(anonymous) @ wrap-actions.js:5
r @ system.js:174
(anonymous) @ system.js:461
(anonymous) @ index.js:11
r @ system.js:174
(anonymous) @ system.js:461
p @ download-url.js:37
Promise.then (async)
(anonymous) @ download-url.js:26
(anonymous) @ utils.js:134
(anonymous) @ bindActionCreators.js:3
h @ index.js:153
Vn @ index.js:180
window.onload @ (index):42
load (async)
(anonymous) @ (index):39
Show 52 more frames
system.js:464 TypeError: Cannot read property 'every' of undefined
    at actions.js:191
    at reducers.js:93
    at immutable.js:3084
    at ft.__iterate (immutable.js:2206)
    at o.__iterateUncached (immutable.js:3083)
    at ce (immutable.js:604)
    at o.K.__iterate (immutable.js:320)
    at o.forEach (immutable.js:4381)
    at immutable.js:2069
    at ft.Ue.withMutations (immutable.js:1353)

As soon as I comment out that property, no errors in the UI.

lorthirk avatar Sep 10 '19 08:09 lorthirk

+1. I use this definition:

"Comment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "post": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "parent": {
            "type": "string"
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          },
          "createdAt": {
            "type": "string",
            "example": "2019-08-16T01:04:02.504Z"
          },
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            }
          },
          "rating": {
            "type": "number"
          },
          "rated": {
            "$ref": "#/components/schemas/UserRate"
          }
        }
      },

children is null in response body.

Darkzarich avatar Oct 01 '19 23:10 Darkzarich

Encounter the same problem of recursive data structure...swagger-ui just don't rendering it properly. Is there any plan to fix this?

LaysDragon avatar Oct 22 '19 08:10 LaysDragon

+1

mabzzz avatar Dec 05 '19 05:12 mabzzz

+1

zkabic avatar Jan 29 '20 10:01 zkabic

Is there any update on this?

ChinmayanP avatar Feb 26 '20 09:02 ChinmayanP

+1 Seems to be related, but with different errors in UI (Swashbuckle.AspNetCore.SwaggerUI 5.0.0): image

{
  "swagger": "2.0",
  "info": {
    "description": "Version 1",
    "version": "v1"
  },
  "paths": {
    "/Recursive": {
      "get": {
        "tags": [
          "Recursive"
        ],
        "produces": [
          "text/plain",
          "application/json",
          "text/json"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "schema": {
              "$ref": "#/definitions/RecursiveType"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "RecursiveType": {
      "type": "object",
      "properties": {
        "item": {
          "allOf": [
            {
              "$ref": "#/definitions/RecursiveType"
            }
          ],
          "readOnly": true
        }
      }
    }
  }
}

manmoth avatar Feb 28 '20 11:02 manmoth

Hello,

I just encountered this issue as well. I have a list of Item instances. Each Item instance in that list has a property that's also a list of Item instances; a recursive data structure. Swagger does not like this.

Is there something that can be done for now, besides excluding the property that is?

Cybrosys avatar Mar 17 '20 12:03 Cybrosys