Repository: litestar-org/litestar

Issue:
Bug: Optional types generate incorrect OpenAPI schemas
**Describe the bug**

1. Optional types in query parameters don't seem to play well with OpenAPI, resulting in a bad schema, *except* that upon some more testing if there is more than one non-None type in the annotation it works.

A query parameter annotated `int | None` produces incorrectly:
```
{ "oneOf": [
    { "type": null" },
    { "oneOf": [] }
]}
```

While one annotated `int | str | date | None` produces correctly:
```
{ "oneOf": [
    { "type": "null" },
    { "type": "integer" },
    { "type": "string" },
    { "type": "string", "format": "date" }
]}
```

2. Additionally, optional types are being assumed to be optional parameters, resulting in `"required": false` regardless of whether the parameter has a default value or not.

3. And finally, route return types that are optional are _mostly_ correct, but for some reason include an empty node (e.g. `{ "oneOf": [ { "type": "null" }, { "type": "integer" }, {} }`). This results in `/schema/redoc` including a possible type of `Any`.

**To Reproduce**

(I ran this on 3.11, but IIRC 3.8+ for `| None` should work. I also tried with `Optional` and got the same results.)

As a minimal example:
```py
# starlite_optionals.py
from datetime import date

from starlite import Starlite, get


@get("/a")
def a(v: int | None) -> int | None:
    return v


@get("/b")
def b(v: int | str | date | None) -> int | str | date | None:
    return v


@get("/c")
def c(v: int | None = None) -> int | None:
    return v


@get("/d")
def d(v: int | str | date | None = None) -> int | str | date | None:
    return v


app = Starlite(route_handlers=[a, b, c, d], debug=True)
```

Check the generated schema below, or by running `uvicorn starlite_optionals:app` and navigating to <http://127.0.0.1:8000/schema/openapi.json> and/or <http://127.0.0.1:8000/schema/>.

<details>
<summary>Generated OpenAPI schema.</summary>

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "Starlite API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "paths": {
    "/a": {
      "get": {
        "operationId": "AA",
        "parameters": [
          {
            "name": "v",
            "in": "query",
            "required": false,
            "deprecated": false,
            "allowEmptyValue": false,
            "allowReserved": false,
            "schema": {
              "oneOf": [
                {
                  "type": "null"
                },
                {
                  "oneOf": []
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request fulfilled, document follows",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "null"
                    },
                    {
                      "type": "integer"
                    },
                    {}
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request syntax or unsupported method",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status_code": {
                      "type": "integer"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "extra": {
                      "additionalProperties": {},
                      "type": [
                        "null",
                        "object",
                        "array"
                      ]
                    }
                  },
                  "type": "object",
                  "required": [
                    "detail",
                    "status_code"
                  ],
                  "description": "Validation Exception",
                  "examples": [
                    {
                      "status_code": 400,
                      "detail": "Bad Request",
                      "extra": {}
                    }
                  ]
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/b": {
      "get": {
        "operationId": "BB",
        "parameters": [
          {
            "name": "v",
            "in": "query",
            "required": false,
            "deprecated": false,
            "allowEmptyValue": false,
            "allowReserved": false,
            "schema": {
              "oneOf": [
                {
                  "type": "null"
                },
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                },
                {
                  "type": "string",
                  "format": "date"
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request fulfilled, document follows",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "null"
                    },
                    {
                      "type": "integer"
                    },
                    {
                      "type": "string"
                    },
                    {
                      "type": "string",
                      "format": "date"
                    },
                    {}
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request syntax or unsupported method",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status_code": {
                      "type": "integer"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "extra": {
                      "additionalProperties": {},
                      "type": [
                        "null",
                        "object",
                        "array"
                      ]
                    }
                  },
                  "type": "object",
                  "required": [
                    "detail",
                    "status_code"
                  ],
                  "description": "Validation Exception",
                  "examples": [
                    {
                      "status_code": 400,
                      "detail": "Bad Request",
                      "extra": {}
                    }
                  ]
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/c": {
      "get": {
        "operationId": "CC",
        "parameters": [
          {
            "name": "v",
            "in": "query",
            "required": false,
            "deprecated": false,
            "allowEmptyValue": false,
            "allowReserved": false,
            "schema": {
              "oneOf": [
                {
                  "type": "null"
                },
                {
                  "oneOf": []
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request fulfilled, document follows",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "null"
                    },
                    {
                      "type": "integer"
                    },
                    {}
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request syntax or unsupported method",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status_code": {
                      "type": "integer"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "extra": {
                      "additionalProperties": {},
                      "type": [
                        "null",
                        "object",
                        "array"
                      ]
                    }
                  },
                  "type": "object",
                  "required": [
                    "detail",
                    "status_code"
                  ],
                  "description": "Validation Exception",
                  "examples": [
                    {
                      "status_code": 400,
                      "detail": "Bad Request",
                      "extra": {}
                    }
                  ]
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/d": {
      "get": {
        "operationId": "DD",
        "parameters": [
          {
            "name": "v",
            "in": "query",
            "required": false,
            "deprecated": false,
            "allowEmptyValue": false,
            "allowReserved": false,
            "schema": {
              "oneOf": [
                {
                  "type": "null"
                },
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                },
                {
                  "type": "string",
                  "format": "date"
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request fulfilled, document follows",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "null"
                    },
                    {
                      "type": "integer"
                    },
                    {
                      "type": "string"
                    },
                    {
                      "type": "string",
                      "format": "date"
                    },
                    {}
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request syntax or unsupported method",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status_code": {
                      "type": "integer"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "extra": {
                      "additionalProperties": {},
                      "type": [
                        "null",
                        "object",
                        "array"
                      ]
                    }
                  },
                  "type": "object",
                  "required": [
                    "detail",
                    "status_code"
                  ],
                  "description": "Validation Exception",
                  "examples": [
                    {
                      "status_code": 400,
                      "detail": "Bad Request",
                      "extra": {}
                    }
                  ]
                }
              }
            }
          }
        },
        "deprecated": false
      }
    }
  }
}
```

</details>


Discussion:

Resolving patch:
diff --git a/starlite/openapi/schema.py b/starlite/openapi/schema.py
--- a/starlite/openapi/schema.py
+++ b/starlite/openapi/schema.py
@@ -1,3 +1,4 @@
+from dataclasses import replace
 from datetime import datetime
 from decimal import Decimal
 from enum import Enum, EnumMeta
@@ -46,6 +47,7 @@
     CursorPagination,
     OffsetPagination,
 )
+from starlite.utils.types import make_non_optional_union
 
 if TYPE_CHECKING:
     from starlite.plugins.base import OpenAPISchemaPluginProtocol
@@ -333,15 +335,13 @@ def create_schema(
     field: "SignatureField",
     generate_examples: bool,
     plugins: List["OpenAPISchemaPluginProtocol"],
-    ignore_optional: bool = False,
 ) -> "Schema":
     """Create a Schema model for a given SignatureField and if needed - recursively traverse its children as well."""
-
-    if field.is_optional and not ignore_optional:
+    if field.is_optional:
+        non_optional_field = replace(field, field_type=make_non_optional_union(field.field_type))
         non_optional_schema = create_schema(
-            field=field,
+            field=non_optional_field,
             generate_examples=False,
-            ignore_optional=True,
             plugins=plugins,
         )
         schema = Schema(
@@ -387,8 +387,7 @@ def create_schema(
     else:
         # value is not a complex typing - hence we can try and get the value schema directly
         schema = get_schema_for_field_type(field=field, plugins=plugins)
-    if not ignore_optional:
-        schema = update_schema_with_signature_field(schema=schema, signature_field=field)
+    schema = update_schema_with_signature_field(schema=schema, signature_field=field)
     if not schema.examples and generate_examples:
         schema.examples = create_examples_for_field(field=field)
     return schema


Test patch: