Skip to content

Bug: OpenAPI schema return types bleed across routes when reusing response dictionaries #7711

@giuseppe16180

Description

@giuseppe16180

Expected Behaviour

When building an API using the APIGatewayRestResolver, I should be able to reuse common response dictionary objects across multiple routes. The generated OpenAPI schema should accurately reflect the unique return type annotation for each function, even when they share the same response configuration dictionary object.

For example, given these two routes:

Route Return Type Expected OpenAPI Schema Response Model
/exams Response[list[ExamSummary]] A list of ExamSummary objects
/exams/<exam_id> Response[ExamConfig] A single ExamConfig object

Each route should display its own correct return type in the OpenAPI schema.

Current Behaviour

The framework leverages response dictionaries by reference (since multiple routes share the same dictionary object) and mutates them when injecting the return type schema.

As a result, the OpenAPI schema generated is incorrect due to a schema bleed: the return type from one of the routes persists in the shared dictionary and is then erroneously used for other routes.

In my specific local tests with the code snippet below, it appears the schema for /exams/<exam_id> is being injected first (or whichever route is processed first by the OpenAPI generator):

  1. The /exams endpoint incorrectly shows a response model for ExamConfig instead of the expected list[ExamSummary] type.
  2. The /exams/<exam_id> endpoint correctly shows a response model for ExamConfig.

Code snippet

class Responses:
    """Pre-configured OpenAPI response schemas."""

    # Base responses
    OK = {200: OpenAPIResponse(description="Successful operation")}

    #...

    # Common combinations
    STANDARD_ERRORS = {**NOT_FOUND, **CONFLICT, **VALIDATION_ERROR, **SERVER_ERROR}

    @classmethod
    def combine(cls, *response_dicts: dict[int, OpenAPIResponse]) -> dict[int, OpenAPIResponse]:
        """Combine multiple response dictionaries."""
        result = {}
        for response_dict in response_dicts:
            result.update(response_dict)
        return result


@app.get(
    "/exams",
    tags=["Exams"],
    responses=Responses.combine(Responses.OK, Responses.STANDARD_ERRORS),
)
def list_exams() -> Response[list[ExamSummary]]:
    """Lists all available exams."""
    # ... returns list[ExamSummary] but the OpenAPI says it returns ExamConfig


@app.get(
    "/exams/<exam_id>",
    tags=["Exams"],
    responses=Responses.combine(Responses.OK, Responses.STANDARD_ERRORS), # Reusing the shared Responses.OK dictionary
)
def get_exam_config(exam_id: str) -> Response[ExamConfig]:
    """Get the configuration for a specific exam"""
    # ... returns ExamConfig

Possible Solution

The issue appears to be in the _get_openapi_path method in api_gateway.py, where the response dictionary is mutated directly without creating a copy first.

Suggested fix - perform a deep copy before mutation:

# Add the response to the OpenAPI operation
if self.responses:
    for status_code in list(self.responses):
        # Current implementation (mutates the shared dictionary):
        # response = self.responses[status_code] 
        
        # Proposed fix - deep copy to prevent mutation of the source object:
        response = copy.deepcopy(self.responses[status_code])

        # Case 1: there is not 'content' key
        if "content" not in response:
            # This mutation would now only affect the local copy
            response["content"] = { 
                _DEFAULT_CONTENT_TYPE: self._openapi_operation_return(...)
            }

Steps to Reproduce

  1. Create a shared response dictionary in a Responses class (e.g., Responses.OK)
  2. Define multiple routes that reuse the same response dictionary via Responses.combine()
  3. Use different return type annotations for each route (e.g., Response[list[ExamSummary]] vs Response[ExamConfig])
  4. Generate the OpenAPI schema
  5. Inspect the response models in the OpenAPI schema - observe that one route's return type incorrectly appears in another route's schema

Powertools for AWS Lambda (Python) version

latest

AWS Lambda function runtime

3.13

Packaging format used

PyPi

Debugging logs

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

Status

Working on it

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions