openapi.json cannot be delivered after calling an endpoint with a $ref request body
Problem description
When creating a Connexion app with an OpenAPI spec which uses $ref to reference the request body schema, requesting /openapi.json leads to an error after the endpoint which uses $ref in the specification has been called.
Minimal example
- Install
connexion["swagger-ui"](anduvicornfor serving), and paste the files below. - Run the app with
uvicorn run:app - Navigate to Swagger UI: http://localhost:8000/ui and confirm that everything looks good
- Call the
/greetingendpoint - Reload Swagger UI, which leads to an error (see below)
Contents of openapi.yaml:
openapi: "3.0.0"
info:
title: Greeting application
version: 0.0.1
paths:
/greeting:
post:
operationId: run.post_greeting
responses:
'200':
description: "Greeting response"
content:
text/plain:
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Test'
components:
schemas:
Test:
type: object
properties:
name:
type: string
description: A name
Contents of run.py:
from connexion import AsyncApp
app = AsyncApp(__name__)
def post_greeting(body):
return f"Hello {body['name']}", 200
app.add_api("openapi.yaml")
Error message
connexion.exceptions.InvalidSpecification: {'content': {'application/json': {'schema': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'A name'}}, 'components': {'schemas': {'Test': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'A name'}}}}}}}}} is not valid under any of the given schemas
Failed validating 'oneOf' in schema['properties']['paths']['patternProperties']['^\\/']['patternProperties']['^(get|put|post|delete|options|head|patch|trace)$']['properties']['requestBody']:
{'oneOf': [{'$ref': '#/definitions/RequestBody'},
{'$ref': '#/definitions/Reference'}]}
On instance['paths']['/greeting']['post']['requestBody']:
{'content': {'application/json': {'schema': {'type': 'object',
'properties': {'name': {'type': 'string',
'description': 'A '
'name'}},
'components': {'schemas': {'Test': {'type': 'object',
'properties': {'name': {'type': 'string',
'description': 'A '
'name'}}}}}}}}}
More details
The issue seems to be that #2002 returns the processed OpenAPI spec, which contains a components key inside the schema, so that the OpenAPI schema validator complains (correctly) about an invalid spec and /openapi.json refuses to deliver the spec. In fact, some testing shows that reverting bb48fb3e3d3866977777eb4bfcb2c4f502efd4ca fixes the issue.
I'm not really sure but this might be related to the discussion in #1829.
Sample test
Something like the following might work as an integration test with the openapi.yaml from above (I used this for finding bb48fb3e3d3866977777eb4bfcb2c4f502efd4ca; maybe this helps):
from connexion import AsyncApp
from os import path
def post_greeting(body):
return f"Hello {body['name']}", 200
def test_esoteric_error():
app = AsyncApp(__name__)
app.add_api(path.join(path.dirname(__file__), "fixtures/simple/esoteric-error.yaml"))
client = app.test_client()
response = client.get("/openapi.json")
assert response.status_code == 200
response = client.post("/greeting", data={"name": "XYZ"}, headers={"Content-Type": "application/json"})
# assert response.status_code == 200 <-- returns Bad Request for some reason I didn't investigate; however it's sufficient to reproduce the error
response = client.get("/openapi.json")
assert response.status_code == 200
I have a similar error - spec validation failing with components appearing out of nowhere alongside properties and type in requestBody - but I don't have a $ref in requestBody, only in responses" So I don't think that the $ref in requestBody is the cause.
I have the same problem with the migration to connection 3
Same here. Looks like this was introduced in the latest release. As a workaround, downgrading from connexion==3.2.0 to connexion==3.1.0 resolved it for me.
Can confirm I've hit this with Connexion 3.2.0 as well. Rolling back to Connexion 3.1.0 fixes it for now.
Rolling back to 3.1.0 helped us too with an inconsistent validation failure due to a supposed recursion issue when using $ref.
Failed validating 'oneOf' in schema['properties']['paths']['patternProperties']['^\\/']['patternProperties']['^(get|put|post|delete|options|head|patch|trace)$']['properties']['requestBody']:
{'oneOf': [{'$ref': '#/definitions/RequestBody'},
{'$ref': '#/definitions/Reference'}]}
On instance['paths']['/sample']['post']['requestBody']:
{
"required": True,
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "...",
"properties": {...},
"components": {
"parameters": {...},
"schemas": {...},
"requestBodies": {
"Sample": {
"required": True,
"content": "<Recursion on dict with id=140649069447936>"
},
...
},
"responses": {...}
}
}
}
}
}
Simplified version of the spec that was used:
openapi: 3.0.0
info:
title: sample
version: 0.0.1
paths:
/sample:
post:
operationId: sample
requestBody:
$ref: '#/components/requestBodies/Sample'
responses:
200:
$ref: '#/components/responses/Sample'
components:
schemas:
Sample:
type: object
properties:
example:
type: string
requestBodies:
Sample:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Sample'
responses:
Sample:
description: Response of sample endpoint
content:
application/json:
schema:
type: object
I have run into the same issue, rolling back to 3.1.0 fixes the problem. If I have time, I will look into this tomorrow to see if I can find any clues.
In my situation, the (second) spec being validated has changed such the $ref in 'requestBody' in the working (first) validation has been replaced by the full expansion of that requestBody ref / schema. Then the rest of the data structure matches the original working one.
notably, the full expansion also includes the 'components' and 'securitySchemes' sections of my original YAML specification instead of just the schema that defines the requestBody. So yes, some sort of recursion...
the original YAMl is certainly ok, otherwise nothing would ever work.
The spec it is being validated against expects a $ref or a fully compliant RequestBody definition - so something is re-parsing(?) the original YAML file improperly and adding things to the def of the schema used for the requestBody
We have the same issue. We host our service in a Docker container. Running it locally works fine. The issue only appears when deployed behind our reverse proxy.
Also having this problem with connexion 3.2.0 on Python 3.13.2-slim.
We also always use Vacuum (https://github.com/daveshanley/vacuum) to lint the OpenAPI file in the build process. There are no reported problems and I suspect this connexion error is an error. It also seems to comply with the examples in Swagger's own Describing Responses documentation (https://swagger.io/docs/specification/v3_0/describing-responses/).
The specific area of complaint relates to this block in our OpenAPI YAML file (which also uses a reference in #/components): -
/version:
get:
summary: Gets the Account Server version
tags:
- state
operationId: app.api_state.get_version
x-semantic-name: getVersion
security: []
responses:
"400":
description: >
Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/as_error'
"401":
description: >
No authorisation token provided
"403":
description: >
You're not authorised to use this path
content:
application/json:
schema:
$ref: '#/components/schemas/as_error'
"200":
description: >
The Account Server version
content:
application/json:
schema:
$ref: '#/components/schemas/state_get_version_response'
Workaround
I can also confirm that rolling back to connexion 3.1.0 resolves the issue, so we too are now stuck on 3.1.0.
I also saw this and downgrading to 3.1.0 resolved it. This issue does not instill me with great confidence in this library going forward.
I have also encountered this issue,
after investigating I have found that the following line is responsible for injecting the components in the schema
https://github.com/spec-first/connexion/blob/a1c53db7a74d0fbbc22913faf70bc3d1c1b08ee2/connexion/operations/openapi.py#L140C5-L140C19
def with_definitions(self, schema: dict):
if self.components:
schema.setdefault("schema", {})
schema["schema"]["components"] = self.components
return schema
I have confirmed (at least in my case) that commenting the schema["schema"]["components"] = self.components fixes the issue.
However, without knowing the original intent of this line, I am unable to know why this line was added in the first place.
Does anybody have any insight on why we would be needing to inject the components in the schemas of request bodies?
Same is happening for me! Downgrading to 3.1.0 also resolved it.
Same issue here Are there any news on this ?
greetings to the community, please test the fix from PR https://github.com/spec-first/connexion/pull/2069 on your use case and comment here or there.
I don't know if y'all have a watch set on this issue so I'm tagging the commenters on this issue, sorry if you get multiple notifications: @SeaweedbrainCY @felipesetti @f-atwi @mivade @alanbchristie @alfechner @wemcouncil @skibbi @jhuot9 @my-master
Sorry @chrisinmtown for the very late replay.
I do confirm #2069 fixes my issue on my use case. I'll conduct extensive tests and let you know if the issue comes again, but from now, this is working fine !
I applied the patch from #2069 manually to 3.2.0 and the error persists in my case. I added a print("with_definitions called") to connexion to check the updated code is being executed and it is.
INFO: Application startup complete.
INFO: 127.0.0.1:41678 - "GET /ingest/v1/ui/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:41678 - "GET /ingest/v1/openapi.json HTTP/1.1" 200 OK
with_definitions called
DEBUG: /ingest/v1/jobs validating parameters...
with_definitions called
with_definitions called
with_definitions called
with_definitions called
with_definitions called
with_definitions called
with_definitions called
INFO: 127.0.0.1:41678 - "POST /ingest/v1/jobs HTTP/1.1" 201 Created
INFO: 127.0.0.1:41678 - "GET /ingest/v1/ui/ HTTP/1.1" 200 OK
ERROR: <InvalidSpecification: "{'description': 'Success', ...
# many lines follow...
Is there any traction on this? Do we know whether this is a problem in 3.3.0? I'm on 3.1.0, blocked from moving forward because of this.
I believe this was resolved in 3.3.0 (https://github.com/spec-first/connexion/pull/2089 must have made it into that release), as it seems to be working for me now.
I just tested this. Created a new Python 3.12 venv, installed connexion[swagger-ui,uvicorn]==3.3.0, created the two files from @bene1618 and followed the procedure. I can POST to the /greeting endpoint using the Swagger UI as many times as I like, and reload the Swagger UI similarly. So as far as I can tell, this is fixed. I guess that PR #2089 solved this? I see that PR references ("Fixes") this issue, yet this remains open. I hope @RobbeSneyders or @Ruwann will close this issue and mark it as fixed.
Thanks for confirming. This issue was indeed referenced as fixed inPR #2089 but probably not in the right format for Github to automatically pick it up.