KeyError on a valid minimal schema that doesn't define "components"
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"
]
}
]
}
}
}
}
}
}
}
}
}
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.
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).
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.
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 :)
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.
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?
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.
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! :)
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!