spec-first / connexion

Connexion is a modern Python web framework that makes spec-first and api-first development easy.
https://connexion.readthedocs.io/en/latest/
Apache License 2.0
4.5k stars 765 forks source link

Connexion 3 ambiguous API routing confuses connexion validation #1899

Open aiman-alsari opened 8 months ago

aiman-alsari commented 8 months ago

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:

aiman-alsari commented 8 months ago

Just a note that this used to work on Connexion 2.x and is holding us up from migrating to Connexion 3

aiman-alsari commented 5 months ago

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.

aiman-alsari commented 5 months ago

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.