Closed pintux closed 4 years ago
Could you share your spec or at least minimal reproducible sample?
Looks like you are allOf
ing schemas with different type
somewhere in the spec, e.g.:
allOf:
- type: string
- type: array
items:
type: string
Hi @RomanGotsiy,
you can reproduce the issue with the following complete spec:
{
"openapi": "3.0.2",
"info": { "title": "Server REST API v3", "version": "7.0.0-dev1" },
"components": {
"schemas": {
"errorResponse": {
"type": "object",
"properties": { "error": { "type": "integer", "minimum": 100 }, "message": { "type": "string" }, "info": { "type": "string" } },
"required": ["error", "message"]
},
"data_collection": {
"description": "Data Collection",
"type": "object",
"allOf": [{ "$ref": "#/components/schemas/hasAccountId" }],
"properties": {
"fields": {
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/components/schemas/metaField" },
{ "$ref": "#/components/schemas/stringField" },
{ "$ref": "#/components/schemas/selectField" },
{ "$ref": "#/components/schemas/numberField" },
{ "$ref": "#/components/schemas/ratingField" },
{ "$ref": "#/components/schemas/booleanField" }
]
}
}
}
},
"hasAccountId": {
"type": "object",
"required": ["acct_id"],
"properties": { "acct_id": { "description": "Account ID", "type": "string", "readOnly": true } }
},
"nonEmptyString": { "type": "string", "minLength": 1 },
"abstractField": {
"type": "object",
"required": ["type"],
"properties": {
"id": { "$ref": "#/components/schemas/nonEmptyString" },
"type": { "$ref": "#/components/schemas/nonEmptyString" },
"labelId": { "$ref": "#/components/schemas/nonEmptyString" },
"format": { "$ref": "#/components/schemas/nonEmptyString" }
}
},
"metaField": {
"type": "object",
"allOf": [{ "$ref": "#/components/schemas/abstractField" }],
"oneOf": [
{ "type": "object", "properties": { "format": { "enum": ["break"] } } },
{
"type": "object",
"required": ["id"],
"properties": { "format": { "enum": ["message"] }, "message": { "$ref": "#/components/schemas/nonEmptyString" } }
},
{ "type": "object", "required": ["id", "labelId"], "properties": { "format": { "enum": ["section"] }, "implicit": { "type": "boolean" } } }
],
"properties": { "type": { "enum": ["meta"] } }
},
"dataField": {
"type": "object",
"allOf": [{ "$ref": "#/components/schemas/abstractField" }],
"properties": {
"placeholderId": { "type": "string", "description": "label id for a placeholder text to display to the user when no value is provided" },
"required": { "type": "boolean", "description": "For the Data Collection to be valid, a value must be provided" },
"hidden": { "type": "boolean", "description": "If true, the field is not displayed to the agent", "default": false },
"editable": { "type": "boolean", "description": "If true, the agent can modify the value of the field" },
"defaultConstant": {
"oneOf": [{ "type": "string" }, { "type": "number" }, { "type": "boolean" }],
"description": "A default value, maybe overridden by a variable or the user"
},
"defaultVariableId": {
"type": "string",
"description": "Id of the variable to use to fill the field. If no value is retrieved, defaultConstant is used instead"
},
"editIfDefault": {
"type": "boolean",
"description": "If true, the field is displayed and can be edited by the user even if a value was set by either a variable or a constant",
"default": false
}
}
},
"stringField": {
"type": "object",
"required": ["type"],
"allOf": [{ "$ref": "#/components/schemas/dataField" }],
"properties": {
"type": { "type": "string", "enum": ["string"] },
"format": {
"type": "string",
"enum": [
"text",
"textarea",
"email",
"nickname",
"firstname",
"lastname",
"phonenum",
"link",
"date",
"time",
"date-time",
"dropdown",
"userid",
"avatar",
"intprefix",
"street",
"city",
"province",
"region",
"state",
"country"
],
"default": "text"
},
"defaultConstant": { "type": "string", "description": "A default value, maybe overridden by a variable or the user" },
"minLength": { "type": "integer" },
"maxLength": { "type": "integer" },
"validation": { "type": "string", "description": "Regular expression to validate the field, not applied to default values" }
}
},
"selectField": {
"type": "object",
"required": ["type"],
"allOf": [{ "$ref": "#/components/schemas/dataField" }],
"properties": {
"type": { "enum": ["dropdown"] },
"options": { "type": "object", "additionalProperties": { "type": "string", "description": "Label id of the option" }, "minProperties": 1 }
}
},
"numberField": {
"type": "object",
"required": ["type"],
"allOf": [{ "$ref": "#/components/schemas/dataField" }],
"properties": {
"type": { "type": "string", "enum": ["number"] },
"format": { "type": "string", "enum": ["number", "rating"], "default": "number" },
"defaultConstant": { "type": "number", "description": "A default value, maybe overridden by a variable or the user" },
"min": { "type": "integer" },
"max": { "type": "integer" }
}
},
"ratingField": {
"type": "object",
"required": ["format", "style"],
"allOf": [{ "$ref": "#/components/schemas/numberField" }],
"properties": { "format": { "enum": ["rating"] }, "style": { "type": "string" } }
},
"booleanField": {
"type": "object",
"required": ["type"],
"allOf": [{ "$ref": "#/components/schemas/dataField" }],
"properties": {
"type": { "type": "string", "enum": ["boolean"] },
"format": { "type": "string", "enum": ["checkbox", "radio"], "default": "checkbox" },
"defaultConstant": { "type": "boolean", "description": "A default value, maybe overridden by a variable or the user" },
"trueLabel": { "type": "string", "description": "Id of the string describing the \"true\" value" },
"falseLabel": { "type": "string", "description": "Id of the string describing the \"false\" value" },
"validation": { "type": "boolean", "description": "Value that the field must have for the Data Collection to be valid" }
}
}
},
"responses": {
"defaultError": {
"description": "Default/generic error response",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/errorResponse" } } }
},
"notFound": {
"description": "The requested/specified resource was not found",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/errorResponse" } } }
}
},
"parameters": {
"id": { "description": "Unique identifier of the resource", "name": "id", "in": "path", "schema": { "type": "string" }, "required": true },
"limit": {
"name": "limit",
"in": "query",
"description": "Maximum number of items to return",
"schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 100 }
},
"skip": {
"name": "skip",
"in": "query",
"description": "Skip the specified number of items",
"schema": { "type": "integer", "default": 0, "minimum": 0 }
},
"fields": {
"name": "fields",
"in": "query",
"description": "Return only the specified properties",
"schema": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
},
"sort": {
"name": "sort",
"in": "query",
"description": "Sorting criteria, using RQL syntax (a,-b,+c)",
"schema": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
},
"query": {
"name": "q",
"in": "query",
"description": "Return only items matching the specified [RQL](https://github.com/persvr/rql) query. This parameter can also be used to specify the ordering criteria of the results",
"schema": { "type": "string" }
},
"version": { "description": "Resource version", "name": "version", "in": "path", "schema": { "type": "integer" }, "required": true }
},
"securitySchemes": {
"basic": { "type": "http", "scheme": "basic" }
}
},
"paths": {
"/data-collections": {
"get": {
"operationId": "DataCollection.query",
"tags": ["DataCollection"],
"responses": {
"200": {
"description": "List of matching DataCollections",
"content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/data_collection" } } } },
"headers": {
"Link": { "description": "Data pagination links, as described in RFC5988. Currently only rel=next is supported", "schema": { "type": "string" } },
"Results-Matching": { "description": "Total number of resources matching the query", "schema": { "type": "integer", "minimum": 0 } },
"Results-Skipped": {
"description": "Number of resources skipped to return the current batch of resources",
"schema": { "type": "integer", "minimum": 0 }
}
}
},
"default": { "$ref": "#/components/responses/defaultError" }
},
"summary": "Retrieve a list of DataCollections",
"parameters": [
{ "$ref": "#/components/parameters/limit" },
{ "$ref": "#/components/parameters/skip" },
{ "$ref": "#/components/parameters/fields" },
{ "$ref": "#/components/parameters/sort" },
{ "$ref": "#/components/parameters/query" }
],
"security": [{ "basic": [] }]
},
"post": {
"operationId": "DataCollection.create",
"tags": ["DataCollection"],
"responses": {
"201": {
"description": "DataCollection successfully created",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/data_collection" } } },
"headers": { "Location": { "description": "URI of the newly created resource", "schema": { "type": "string", "format": "uri" } } }
},
"default": { "$ref": "#/components/responses/defaultError" }
},
"summary": "Create a new DataCollection",
"requestBody": {
"description": "DataCollection to be created, omitting the metadata",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/data_collection" } } },
"required": true
},
"security": [{ "basic": [] }]
}
}
},
"tags": [{ "name": "DataCollection", "description": "Data Collection Resource", "x-id": "id", "x-summary-fields": ["id", "labelId"] }],
"servers": [{ "url": "https://my-foo-server.test.com/api/v3" }]
}
This looks like an issue in ReDoc with allOf
merging.
You have a pretty complex schema. I think I've fixed it locally but the fix is a bit dangerous so I wanna test it carefully.
Hopefully will land in the upcoming release
I am facing the same issue, is there a way I can get the local fix you made? so that I can test it on my local branch?
@sujaybhowmick, Would be simpler if you just share your spec or minimal reproducible sample.
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "ReDoc Bug",
"description": "ReDoc Bug\n"
},
"servers": [
{
"url": "some server",
"description": "some description"
}
],
"tags": [
{
"name": "MyAPI",
"description": "Some API"
}
],
"paths": {
"/myapi": {
"post": {
"tags": [
"MyAPI"
],
"summary": "",
"description": "some description\n",
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "Authorization Header Base64 encoded String",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"pramam1": {
"type": "string",
"enum": [
"password"
],
"description": "Grant type supported by OAuth implementation\n"
},
"param2": {
"type": "string",
"description": "some param 2\n"
},
"param3": {
"type": "string",
"description": "some description\n"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"type": "string",
"properties": {
"name": {
"type": "string"
}
}
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
}
}
}
}
}
}
}
}
}
Error produce when trying to use redoc-cli
Prerendering docs
Error: Incompatible types in allOf at ""
at OpenAPIParser.mergeAllOf (/Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:8385:23)
at new SchemaModel (/Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:8610:30)
at new MediaTypeModel (/Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:8875:38)
at /Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:8945:20
at Array.map (<anonymous>)
at new MediaContentModel (/Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:8942:45)
at new ResponseModel (/Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:9012:28)
at /Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:9141:24
at Array.map (<anonymous>)
at OperationModel.get (/Users/sujaybhowmick/.nvm/versions/node/v12.2.0/lib/node_modules/redoc-cli/node_modules/redoc/bundles/redoc.lib.js:9140:18)
@sujaybhowmick You have the issue in your spec:
"allOf": [
{
"type": "string",
"properties": {
"name": {
"type": "string"
}
}
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
While this JSON schema is technically valid it is incorrect: there is no valid data that validates against this schema.
allOf
means that data must validate against all of the subschemas.
In your case, if the data is string
it can't be array
at the same time and vice versa so your schema won't validate any data. You can try it in JSON schema validator: https://www.jsonschemavalidator.net/
Also, the usage of type: "string"
and properties
in the first allOf
item doesn't make any sense too. This part will validate only against string
and properties
doesn't matter here.
I would recommend you learning JSON schema more deeply, here is the best resource I've seen.
How about changing it to
openapi: "3.0.0"
info:
version: 1.0.0
title: ReDoc Bug
description: |
ReDoc Bug
servers:
- url: some server
description: some description
tags:
- name: MyAPI
description: Some API
paths:
'/myapi':
post:
tags:
- MyAPI
summary: >-
description: |
some description
parameters:
- name: Authorization
in: header
description: Authorization Header Base64 encoded String
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
pramam1:
type: string
enum: ['password']
description: |
Grant type supported by OAuth implementation
param2:
type: string
description: |
some param 2
param3:
type: string
description: |
some description
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- type: object
properties:
name:
type: string
- type: array
items:
type: string
This works, looks like ReDoc cannot handle mixed allOf types. I was trying to keep the example simple.
openapi: "3.0.0"
info:
version: 1.0.0
title: ReDoc Bug
description: |
ReDoc Bug
servers:
- url: some server
description: some description
tags:
- name: MyAPI
description: Some API
paths:
'/myapi':
post:
tags:
- MyAPI
summary: >-
description: |
some description
parameters:
- name: Authorization
in: header
description: Authorization Header Base64 encoded String
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
pramam1:
type: string
enum: ['password']
description: |
Grant type supported by OAuth implementation
param2:
type: string
description: |
some param 2
param3:
type: string
description: |
some description
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- type: object
properties:
name:
type: string
- type: object
properties:
id:
type: string
Sorry I am using YAML language, the JSON equivalents for the problematic part
Working example
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
]
}
}
}
}
}
Bug example
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "ReDoc Bug",
"description": "ReDoc Bug\n"
},
"servers": [
{
"url": "some server",
"description": "some description"
}
],
"tags": [
{
"name": "MyAPI",
"description": "Some API"
}
],
"paths": {
"/myapi": {
"post": {
"tags": [
"MyAPI"
],
"summary": "",
"description": "some description\n",
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "Authorization Header Base64 encoded String",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"pramam1": {
"type": "string",
"enum": [
"password"
],
"description": "Grant type supported by OAuth implementation\n"
},
"param2": {
"type": "string",
"description": "some param 2\n"
},
"param3": {
"type": "string",
"description": "some description\n"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
}
}
}
}
}
}
}
}
}
Thank you for building this and sharing the code, I will try to fix it myself if possible
Same problem here friends, I'll try evaluate that suggestions...In the SwaggerHub mydocs works perfectly.
Yeah, I switched to swaggerhub and it is really worth the money very professional and has awesome integration and response on issues. Our customers who are very big banks also love it
Now working my redoc docs and I've understood the point here.
In the swaggerhub you can "override" properties, because of that my docs was working there. In the opposite, Redoc doesn't allow that.
So, You can't have same properties in allof schemas with different types.
@sujaybhowmick Redoc is a good free tool, but if the clients agrees to pay for it, go ahead!
Best Regards!
@thiago670, Of course, it is a good tool and is free. But if I am charging my client, I need to make sure I am able to service them for what they pay.
Any progress on this? Type override is still not supported.
Same problem!
While I generally laud strictness when defining API's, I'm running into a particular issue where I'm building a middleware in front of a vendor appliance. Most of the API's are passthrough, where my middleware is merely performing what is needed to auth the incoming request before sending it on to the upstream appliance.
As such, I selectively expose the raw swagger annotations for the endpoints that I filter that the upstream appliance itself provides. The issue is these docs are messy, but there are around 760 paths and 950+ definitions. To attempt to clean up the "types" on each of these models would be possible as I am generating these docs programmatically, but it would be really nice if there was maybe a flag that would enable something similar to this logic:
if L.Type && R.Type
-- if config.Strict
---- compare types, fail if incompatible
-- else
---- return R.Type
else if ! L.Type && R.Type
-- return R.Type
else if L.Type && ! R.Type
-- return L.Type
This absolutely does not solve all cases and will assuredly produce issues of its own, however I presume that most of the time (as is my requirement), you'll want to use the right-hand-most value of type value conflicts when merging in an allOf
block.
For me this was addressed by 6e607b9a2928b062c7705087432c0f0d88e74f5d.
Using 2.0.0-rc.4 with an OpenAPI spec 3.0.2 file, parsing fails and I'm getting the following error in the browser console:
redoc.standalone.js:26397 Error: Incompatible types in allOf at "undefined" at e.mergeAllOf (redoc.standalone.js:38564) at redoc.standalone.js:38614 at Array.map ()
at r (redoc.standalone.js:38613)
at e.hoistOneOfs (redoc.standalone.js:38625)
at e.mergeAllOf (redoc.standalone.js:38537)
at e.mergeAllOf (redoc.standalone.js:38568)
at redoc.standalone.js:38861
at Array.map ()
at e.initOneOf (redoc.standalone.js:38859)
I'm not able to understand the point in which fails.
My OpenAPI spec 3.0.2 json file validates correctly using a tool like oas-validate from https://github.com/Mermade/oas-kit