swagger_codegen icon indicating copy to clipboard operation
swagger_codegen copied to clipboard

KeyError on a valid minimal schema that doesn't define "components"

Open coderfromhere opened this issue 5 years ago • 9 comments

Hi @asyncee

I've got a minimal spec that passes OAS3 validation, yet the codegen fails around schema components. I think it expects shared schemas to exist, and I wonder if there's a quick solution to specs without defined schemas.

Traceback (most recent call last):
  File "/Users/coderfromhere/project/.venv/bin/swagger_codegen", line 8, in <module>
    sys.exit(app())
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/typer/main.py", line 211, in __call__
    return get_command(self)()
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/typer/main.py", line 494, in wrapper
    return callback(**use_params)  # type: ignore
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/swagger_codegen/cli/main.py", line 37, in generate
    base_schema = load_base_schema(uri)
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/swagger_codegen/parsing/loaders.py", line 43, in load_base_schema
    add_x_names(schema)
  File "/Users/coderfromhere/project/.venv/lib/python3.8/site-packages/swagger_codegen/parsing/loaders.py", line 55, in add_x_names
    for name, schema in schema["components"]["schemas"].items():
KeyError: 'components'

The spec:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Title",
    "description": "Desc",
    "contact": {
      "name": "Me",
      "url": "https://github.com/coderfromhere",
      "email": "[email protected]"
    },
    "version": "1.0"
  },
  "servers": [
    {
      "url": "https://api-dev.example.com/v2",
      "description": "Development server"
    },
    {
      "url": "https://api-integration.example.com/v2",
      "description": "Integration server"
    },
    {
      "url": "https://api.example.com/v2",
      "description": "Production server"
    }
  ],
  "paths": {
    "/auth": {
      "post": {
        "tags": [
          "Authentication & Authorization"
        ],
        "summary": "Authentication",
        "description": "Authentication",
        "operationId": "auth",
        "parameters": [
          {
            "name": "Accept-Language",
            "in": "header",
            "description": "List of acceptable human languages for response.",
            "required": false,
            "style": "simple",
            "explode": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-HTTP-Method-Override",
            "in": "header",
            "description": "Put here the HTTP method you want, when making call by the POST method to avoid interference of nasty firewall rules.",
            "required": false,
            "style": "simple",
            "explode": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "apiKey": {
                    "type": "string",
                    "description": "The open api key that is given to the specific system."
                  },
                  "locale": {
                    "type": "string",
                    "description": "Locale"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "Timezone in POSIX format."
                  },
                  "source": {
                    "allOf": [
                      {
                        "type": "object",
                        "properties": {
                          "tenant": {
                            "type": "string",
                            "description": "The tenant to which the origin belongs."
                          }
                        }
                      },
                      {
                        "title": "Source",
                        "required": [
                          "type",
                          "name",
                          "version",
                          "instance"
                        ],
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "system",
                              "database",
                              "file-system"
                            ],
                            "description": "The type of the source."
                          },
                          "name": {
                            "type": "string",
                            "description": "The name of the source."
                          },
                          "version": {
                            "type": "string",
                            "description": "The version of the source."
                          },
                          "instance": {
                            "type": "string",
                            "description": "Specific instance of the source."
                          },
                        },
                        "x-tags": [
                          "domain"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "title": "Response",
                      "required": [
                        "code",
                        "locale",
                      ],
                      "type": "object",
                      "properties": {
                        "code": {
                          "minimum": 0,
                          "type": "integer",
                          "description": "Response code"
                        },
                        "locale": {
                          "type": "string",
                          "description": "Response locale"
                        }
                      },
                      "x-tags": [
                        "system"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}

coderfromhere avatar Dec 04 '20 14:12 coderfromhere

Hi!

That is definitely a bug. The project was built to fulfill my personal needs: a FastAPI-generated openapi schema and i needed this feature quickly, that is why there are too many large dependencies, schemathesis for example.

I must say that inlined schemas, ones defined outside a components section, need some pre-processing to be implemented, because it is very hard to generate reasonable name for such schemas, especially with complex nested schemas.

There are implementation of such pre-processing, but it is not full, because it supports only response schemas without any nesting.

I will try to get my hands on it, but can not guarantee that feature will be implemented asap, sorry.

asyncee avatar Dec 08 '20 13:12 asyncee

I can remove the necessity for components section to present in openapi schema, but it will not solve more general problem of pre-processing (it will cause errors with source field from your example).

asyncee avatar Dec 08 '20 13:12 asyncee

Hi @asyncee , thanks for your feedback! I definitely don't expect it to be fixed asap, I just thought maybe it was a known TODO in your schedule and there's a planned solution for it. Maybe I'll have time to have a look at it myself soon, but if not, let's just keep track of any progress here.

coderfromhere avatar Dec 08 '20 14:12 coderfromhere

I'll try to investigate and fix this/refactor on the next week, it seems that the library is used by more people than i expected :)

asyncee avatar Dec 15 '20 17:12 asyncee

I have pushed to master some fixes that handle issues with components section and with names generation for properties defined as allOf / anyOf / oneOf. However, the issue with allOf / anyOf / oneOf definitions that are parts of root schema is not solved yet.

asyncee avatar Jan 02 '21 12:01 asyncee

This is great news, thanks!

I've got one question though - https://github.com/asyncee/swagger_codegen/commit/229ba4a8a6d6c45db70c659470c6ebd03cdd1d5c#diff-6681172804e0dd9fe50e59607bb598d2d56d409a2584ba3d7d4fbded9ccad83dR55-R80 - shouldn't Union be used for parsing anyOf instead of allOf?

coderfromhere avatar Jan 02 '21 19:01 coderfromhere

You are absolutely right, but currently all three use Union. In fact, the allOf case is not supported and i'm not sure what right implementation may be.

asyncee avatar Jan 06 '21 09:01 asyncee

Hi!

I managed to implement a proper transformer function for this kind of recursive definitions in my implementation of an openapi codegen. I used the recursive descent technique. It's far from being polished, but still indicates the general idea. You can find it here. I hope it will assist you with your own implementation. I also extracted a spec parser into a separate library with a minimal set of dependencies, in case you are interested in using typed values instead of dictionaries.

The more codegens are there, the more choices of flavour end users have! :)

avanov avatar Jan 16 '21 21:01 avanov

Hi!

Thats great, i'll check it out!

Also i'm thinking if i should rewrite this library (in the backwards compatible way as much as possible) because it was just a quick solution so there are some architectural problems.

Anyway currently i'm pretty busy on another project, but will get into this later.

Thanks!

asyncee avatar Jan 18 '21 06:01 asyncee