connexion
connexion copied to clipboard
Connexion 3 ambiguous API routing confuses connexion validation
Description
If we have two endpoints in the open API spec that have a similar routing path, then connexion gets confused and adds the same validation to both endpoints.
In the example below we have two endpoints:
/v1/{first}/{last}
/v1/bye/{param1}
Both take string parameters but the hello also expects a header. When running this, we can see that connexion expects both endpoints to have the header, which is incorrect.
openapi: "3.0.3"
info:
title: Helloworld API
description: API defining the operations available in the Helloworld API
version: 0.1.0
servers:
- url: "https://localhost/"
description: Production API endpoint for the Hello World API
paths:
/v1/{first}/{last}:
get:
summary: Retrieve a greeting message
operationId: asgi.get_greet
parameters:
- name: title
in: header
schema:
type: string
required: true
- name: first
in: path
schema:
type: string
required: true
- name: last
in: path
schema:
type: string
required: true
responses:
'200':
description: A successful response
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Hello, World!
/v1/bye/{param1}:
get:
summary: Retrieve a greeting message
operationId: asgi.get_bye
parameters:
- name: param1
in: path
schema:
type: string
required: true
responses:
'200':
description: A successful response
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Hello, World!
Expected behaviour
Header Validation should only apply to the "hello" endpoint and not the "bye" endpoint.
Actual behaviour
# Correct validation
> curl -s localhost:8000/v1/alan/plonk -H 'title: mr'
"Hello mr alan plonk"
# Incorrect validation, title is not relevant for this endpoint
> curl -s localhost:8000/v1/bye/alan
{"type": "about:blank", "title": "Bad Request", "detail": "Missing header parameter 'title'", "status": 400}
# If header is passed, it works
> curl -s localhost:8000/v1/bye/alan -H 'title: mr'
"Goodbye alan"
Steps to reproduce
Create file called run.py
from connexion import FlaskApp
from connexion import request
import traceback
app = FlaskApp(__name__)
def get_greet(first, last):
return f"Hello {request.headers['title']} {first} {last}", 200
def get_bye(param1):
return f"Goodbye {param1}", 200
app.add_api("openapi.yaml")
Run using uvicorn run:app
Additional info:
Output of the commands:
- Python 3.10.12
- Connexion Version: 3.0.6
Just a note that this used to work on Connexion 2.x and is holding us up from migrating to Connexion 3
This is an issue with Starlette route priority. https://www.starlette.io/routing/#route-priority
The recommendation is to order the routes such that more specific (e.g. my bye example) routes are placed first. This however impacts the end-user of these APIs as it will then dictate the swagger-ui ordering.
I realise this isn't an issue with connexion per-se, but it does feel a little clunky.
I have found an acceptable workaround. If you use tags, swagger ui orders them alphabetically within the tag. I'm happy to close this as it is no longer a blocker, but I still think it's important to call this out for anyone upgrading from connexion 2.x as the behaviour is different.