Skip to content

Bug: RestAPI event resolver: OpenAPI generation with content-type header generated wrong spec #8347

Description

@RynoM

Expected Behaviour

Given the following route in our application:

@app.patch("/event-types/<event_type_id>/approved-consumers")
def patch_approved_consumers(
    event_type_id: str,
    approved_consumers: Annotated[list[ApprovedConsumersPatchOperation], Body()],
    content_type: Annotated[
        Literal["application/json-patch+json"],
        Header(alias="Content-Type"),
    ],
) -> EventTypeModel:
    """Patch approved consumers for an EventType."""
    return approved_consumers_controller.patch_approved_consumers(
        event_type_id, approved_consumers
    )

We would expect the following snippet in our OpenAPI spec:

        "requestBody": {
          "content": {
            "application/json-patch+json": {
              "schema": {
                "items": {
                  "$ref": "#/components/schemas/ApprovedConsumersPatchOperation"
                },
                "type": "array",
                "title": "Approved Consumers"
              }
            }
          },
          "required": true
        },

Current Behaviour

However currently this generates:

    "/event-types/{event_type_id}/approved-consumers": {
      "patch": {
        "summary": "PATCH /event-types/{event_type_id}/approved-consumers",
        "operationId": "patch_approved_consumers",
        "parameters": [
          {
            "required": true,
            "schema": {
              "type": "string",
              "title": "Event Type Id"
            },
            "name": "event_type_id",
            "in": "path"
          },
          {
            "required": true,
            "schema": {
              "type": "string",
              "const": "application/json-patch+json",
              "title": "Content-Type"
            },
            "name": "content-type",
            "in": "header"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "items": {
                  "$ref": "#/components/schemas/ApprovedConsumersPatchOperation"
                },
                "type": "array",
                "title": "Approved Consumers"
              }
            }
          },
          "required": true
        },

Code snippet

@app.patch("/event-types/<event_type_id>/approved-consumers")
def patch_approved_consumers(
    event_type_id: str,
    approved_consumers: Annotated[list[ApprovedConsumersPatchOperation], Body()],
    content_type: Annotated[
        Literal["application/json-patch+json"],
        Header(alias="Content-Type"),
    ],
) -> EventTypeModel:
    """Patch approved consumers for an EventType."""
    return approved_consumers_controller.patch_approved_consumers(
        event_type_id, approved_consumers
    )

Possible Solution

Couple headers should be treated as special cases:
https://swagger.io/docs/specification/v3_0/describing-parameters/#header-parameters

Steps to Reproduce

  • define route
  • generate openapi spec from it

Powertools for AWS Lambda (Python) version

latest

AWS Lambda function runtime

3.13

Packaging format used

PyPi

Debugging logs

Activity

  1. added
    bugSomething isn't working
    triagePending triage from maintainers
    on Jul 14, 2026
  2. DebadityaHait commented on Aug 1, 2026

    @DebadityaHait
    Contributor

    Thanks for the detailed reproduction. I checked the current develop implementation, and the request-body media type is intentionally derived from Body.media_type in openapi/schema_generator.py; Header(...) creates a separate in: header parameter.

    The resolver already supports the expected request-body entry by declaring the body parameter as:

    approved_consumers: Annotated[
        list[ApprovedConsumersPatchOperation],
        Body(media_type=application/json-patch+json),
    ]

    That produces requestBody.content[application/json-patch+json] directly. Keeping the Content-Type header annotation is separate from selecting the OpenAPI request-body representation.

    Could you confirm whether moving the media-type declaration to Body(...) addresses the generated schema for your route? If it does, this appears to be a supported usage path rather than a generator defect.

  3. RynoM commented on Aug 3, 2026

    @RynoM
    Author

    Yes you are right, adding the media type to the body correctly generates the schema. However adding this alone does not actually prevent other media types being rejected, I guess by design? Im wondering what the design philosophy should be then for a use-case like this. We might keep the header validation separate then?

  4. DebadityaHait commented on Aug 3, 2026

    @DebadityaHait
    Contributor

    Thanks, that separates two concerns cleanly.

    With request validation enabled, retaining the Header(alias=Content-Type) parameter with a Literal[...] is what enforces the accepted value at runtime; Body(media_type=application/json-patch+json) supplies the correct requestBody.content entry. The resolver therefore needs both declarations for this use case.

    The remaining OpenAPI-generation defect is narrower: it serializes that runtime-only Content-Type validation as an in: header parameter. OpenAPI does not allow Content-Type header parameters; the request body content media type is its representation instead. A focused fix could keep the header field in the runtime dependant/validation path, but omit a header parameter named content-type from the generated schema. A functional test would assert that the media type remains under requestBody.content and that no Content-Type parameter is emitted.

    Would maintainers consider that the intended behavior? If so, I can prepare the small schema-generator patch and regression test.

  5. leandrodamascena commented on Aug 10, 2026

    @leandrodamascena
    Contributor

    Hey @RynoM and @DebadityaHait, thanks a lot for working through this together. The back and forth here made the actual problem much clearer than the original report, and I appreciate it.

    I think two separate things were mixed up:

    The request body media type was a usage thing, not a bug. Body(media_type="application/json-patch+json") generates the right requestBody.content entry, as you confirmed.

    The real bug is narrower: we emit Content-Type as an in: header parameter. The spec says a header parameter named Accept, Content-Type or Authorization "SHALL be ignored", so what we generate is not invalid, but it is noise that every tool throws away, and it pollutes the docs. The request body content key is the correct representation.

    I checked and the runtime validation is doing real work here: with Literal[...] on that header, a wrong media type gets a 422 and the right one passes. So the fix has to keep the field in the dependant and validation path, and only skip it when generating the schema. Removing it outright would silently kill that validation.

    On scope, I'd like to keep this to Content-Type only for now. I looked at our test suite and we have existing tests asserting authorization shows up as a parameter, so filtering all three names at once changes behaviour people may be relying on in their generated docs. Authorization really belongs in a Security Scheme, but that is a bigger conversation and I don't want to sneak it into a bug fix. Happy to open a separate issue for it if you think it's worth it.

    @DebadityaHait if you're still up for it, please go ahead with the PR. Scope it to Content-Type (case insensitive, since we normalise header names to lowercase), and for tests it would be great to cover: the media type stays under requestBody.content, no content-type parameter is emitted, and the runtime still returns 422 for a wrong media type. That last one matters most to me.

    Thanks again to both of you.

  6. powertools-for-aws-oss-automation commented on Aug 10, 2026

    @powertools-for-aws-oss-automation

    Warning

    This issue is now closed. Please be mindful that future comments are hard for our team to see.
    If you need more assistance, please either reopen the issue, or open a new issue referencing this one.
    If you wish to keep having a conversation with other community members under this issue feel free to do so.

  7. added
    pending-releaseFix or implementation already in dev waiting to be released
    and removed
    triagePending triage from maintainers
    on Aug 10, 2026
  8. github-actions commented on Aug 10, 2026

    @github-actions
    Contributor

    This is now released under 3.34.0 version!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions