mike-oakley / openapi-pydantic

Pydantic OpenAPI schema implementation
Other
48 stars 7 forks source link

Literal values are not being correctly serialised for OpenAPI v3 #23

Closed metamoof closed 6 months ago

metamoof commented 11 months ago

I regularly deal with an API where a value is either something that fits some minimal requirements or is an empty string. I've made a small example here:

import sys
from typing import Literal

import pydantic
from openapi_pydantic.v3.v3_0_3 import OpenAPI
from openapi_pydantic.v3.v3_0_3.util import construct_open_api_with_schema_class
from pydantic import BaseModel, Field, constr

assert pydantic.__version__ == "2.0.2"
assert sys.version_info >= (3, 10)  # I use py 3.10

class Currency(BaseModel):
    currency: constr(min_length=3, max_length=3) | Literal[""] = Field(
        description="pong", example="pong"
    )

api = {
    "openapi": "3.0.3",
    "info": {
        "title": "A test API",
        "version": "1.0.0",
    },
    "paths": {
        "/ping": {
            "get": {
                "responses": {
                    "200": {
                        "description": "pong",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/Currency"}
                            }
                        },
                    }
                },
            }
        }
    },
}

open_api = OpenAPI.model_validate(api)
open_api = construct_open_api_with_schema_class(
    open_api, schema_classes=[Currency], by_alias=False
)

with open("test_output.json", "w") as f:
    f.write(open_api.model_dump_json(by_alias=True, exclude_none=True, indent=2))

The output from this is:

{
  "openapi": "3.0.3",
  "info": {
    "title": "A test API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "paths": {
    "/ping": {
      "get": {
        "responses": {
          "200": {
            "description": "pong",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Currency"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {
      "Currency": {
        "title": "Currency",
        "required": [
          "currency"
        ],
        "type": "object",
        "properties": {
          "currency": {
            "title": "Currency",
            "anyOf": [
              {
                "maxLength": 3,
                "minLength": 3,
                "type": "string"
              },
              {
                "const": ""
              }
            ],
            "description": "pong",
            "example": "pong"
          }
        }
      }
    }
  }
}

My syntax checker tells me const is not allowed in a property.

Presumably this is permissible under JSON Schemas, but the OpenAPI library should be replacing this with a second string block, filtering this out or otherwise flagging it.

metamoof commented 11 months ago

I've created an appropriate GenerateJsonSchema object to try and pass to OpenAPI, but I can't see how to do that either....

mike-oakley commented 11 months ago

Looks like @hathawsh may be resolving this already? https://github.com/mike-oakley/openapi-pydantic/pull/22/commits/77a58cadf5ba8bad80de4cf00579ed083e651e04

mike-oakley commented 6 months ago

Hey @metamoof - version 0.4.0 has just been released which includes the fix I mentioned above - would you mind trying that and let me know if you still have issues? Closing this for now 👍🏼

hosaka commented 1 week ago

Not sure if intended, an enum with a single type will still get the "const" keyword in v3.0 (https://github.com/mike-oakley/openapi-pydantic/blob/main/openapi_pydantic/v3/v3_0_3/util.py#L108)

class InteractableType(str, Enum):
    """
    Interactable type.
    """

    IMAGE_DISPLAY = "IMAGE_DISPLAY"

will produce:

      "InteractableType": {
        "title": "InteractableType",
        "enum": [
          "IMAGE_DISPLAY"
        ],
        "type": "string",
        "description": "Interactable type.",
        "const": "IMAGE_DISPLAY"
      },

which openapi-generator-cli (for example) will complain about when generating code. If "const" is not a part of v3 spec, it should not be generated.