swagger-ui
swagger-ui copied to clipboard
Recursive rendering needs work
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.
We think it may be a bug caused by circular references. Thanks to Helen.
Thank you very much!
@shockey @webron I mention you just to make sure this has been noticed, don't be offended :).
@julienkosinski thanks, I'll get this triaged on Monday 😄
I'm seeing two things here:
-
Feature -> Equipments
is being passed a $ref to render... this shouldn't be happening. - The nested model is showing a very strange generated name:
This isn't quite right - we'll look into it 😄
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:
In the 2.x versions this was handled as follows:
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.
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:
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.:
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.
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
"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).
"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
withnullable
. You'll probably need to addnullable: 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 can't think of a different way to express it.
@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.
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
}
]
},
...
allOf
would actually be better than anyOf
in this case.
@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 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
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 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
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.
@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 valuefalse
, any instance validates successfully unless it isnull
.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.
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
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”.
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.
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
+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:
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.
+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.
Encounter the same problem of recursive data structure...swagger-ui just don't rendering it properly. Is there any plan to fix this?
+1
+1
Is there any update on this?
+1 Seems to be related, but with different errors in UI (Swashbuckle.AspNetCore.SwaggerUI 5.0.0):
{
"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
}
}
}
}
}
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?