redoc icon indicating copy to clipboard operation
redoc copied to clipboard

Improper handling of type compatibility in allOf clauses for OpenAPI 3.1 Spec

Open yrosen-infinidat opened this issue 2 months ago • 3 comments

Since OpenAPI 3.1, the type schema field can now be a list, for example:

"type": ["string", "null"]

Would mean that the schema can be a string or null.

Redoc verifies that when "overriding" a property with allOf, the type is identical (see here). But the check is done using ===, which does not work well for lists, hence I'm getting this error:

Incompatible types in allOf at "/properties/error/properties/code": "string,null" and "string,null"

Expected behavior If both types in the parent and child schema are ["string", "null"], there shouldn't be an error. The solution is probably to modify the comparison to be smarter than a simple ===.

yrosen-infinidat avatar Nov 16 '25 10:11 yrosen-infinidat

share your schema or a repro

jeremyfiel avatar Nov 25 '25 19:11 jeremyfiel

Oh, thought I had, my bad.

{
  "openapi": "3.1.0",
  "info": {
    "title": "Cars",
    "version": "1.0"
  },
  "paths": {
    "/test": {
      "get": {
        "operationId": "testGet",
        "responses": {
          "200": {
            "description": "default response",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/Union"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Union": {
        "type": "object",
        "oneOf": [
          {
            "$ref": "#/components/schemas/Porsche"
          },
          {
            "$ref": "#/components/schemas/PorscheGT3"
          }
        ]
      },
      "Porsche": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PorscheGT3": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/Porsche"
          },
          {
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "const": "Porsche GT3"
              },
              "weissach": {
                "type": "boolean"
              }
            }
          }
        ]
      }
    }
  }
}

Of course this is just a simple example, my actual schemas are much more complex, but the problem is well represented here. The overriding of the "name" property results in the following error:

Incompatible types in allOf at "#/components/schemas/Union/oneOf/1/properties/name": "string,null" and "string,null"

yrosen-infinidat avatar Nov 26 '25 09:11 yrosen-infinidat

Can you share the command you are using?

I tried here on version 2.8 and it doesn't seem to have any issue building the preview command with --product redoc

redocly preview -d ./redocly --product redoc -p 5501


Separately, your schema is potentially invalid the way you have defined it.

The oneOf must match only one(XOR) schema and it currently has the ability to pass both oneOf subschemas with the following example:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "oneOf": [
    {
      "$ref": "#/$defs/Porsche"
    },
    {
      "$ref": "#/$defs/PorscheGT3"
    }
  ],
  "$defs": {
    "Porsche": {
      "type": "object",
      "properties": {
        "name": {
          "type": ["string", "null"]
        }
      }
    },
    "PorscheGT3": {
      "type": "object",
      "allOf": [
        {
          "$ref": "#/$defs/Porsche"
        }
      ],
      "properties": {
        "name": {
          "type": ["string", "null"],
          "const": "Porsche GT3"
        },
        "weissach": {
          "type": "boolean"
        }
      }
    }
  }
}

With this instance

{
  "name": "Porsche GT3",
  "weissach": true
}

the name attribute matches both subschemas.

Because the const value is a string, it will always match oneOf/0. The allOf is causing you unnecessary problems here. Remove that and add a pattern to constrain the GT3 value

    "Porsche": {
      "type": "object",
      "properties": {
        "name": {
          "type": ["string", "null"],
          "pattern": "^.+(?<!GT3)$"  //negative lookbehind to constrain the GT3 value only to the second subschema
        }
      }
    },
    "PorscheGT3": {
      "type": "object",
      "properties": {
        "name": {
          "type": ["string", "null"],
          "const": "Porsche GT3"
        },
        "weissach": {
          "type": "boolean"
        }
      }
    }

if you want to post your schemas, I can review them to look over how you have defined some of these patterns.

jeremyfiel avatar Dec 04 '25 04:12 jeremyfiel