From c45b8547f3aa1d0eac73e21c721c850034fde93e Mon Sep 17 00:00:00 2001 From: Abhishek Jha Date: Mon, 19 Oct 2026 06:28:52 +0530 Subject: [PATCH 1/7] fix(event_handler): correct six OpenAPI schema inaccuracies Signed-off-by: Abhishek Jha --- .../event_handler/openapi/encoders.py | 5 ++ .../event_handler/openapi/schema_generator.py | 50 +++++++++++++------ .../event_handler/openapi/types.py | 2 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/aws_lambda_powertools/event_handler/openapi/encoders.py b/aws_lambda_powertools/event_handler/openapi/encoders.py index 5d55280382c..53b7a950ffe 100644 --- a/aws_lambda_powertools/event_handler/openapi/encoders.py +++ b/aws_lambda_powertools/event_handler/openapi/encoders.py @@ -327,6 +327,11 @@ def decimal_encoder(dec_value: Decimal) -> int | float: >>> decimal_encoder(Decimal("1")) 1 """ + if not dec_value.is_finite(): + raise SerializationError( + f"Unable to serialize the Decimal value {dec_value} as it is not finite.", + "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#serializing-objects", + ) if dec_value.as_tuple().exponent >= 0: # type: ignore[operator] return int(dec_value) else: diff --git a/aws_lambda_powertools/event_handler/openapi/schema_generator.py b/aws_lambda_powertools/event_handler/openapi/schema_generator.py index 0d3de7e1f8c..ebeabe16943 100644 --- a/aws_lambda_powertools/event_handler/openapi/schema_generator.py +++ b/aws_lambda_powertools/event_handler/openapi/schema_generator.py @@ -253,6 +253,8 @@ def _build_responses( dependant=dependant, model_name_map=model_name_map, field_mapping=field_mapping, + resp_code=resp_code, + status_code=status_code, ) else: response_schema = _build_return_schema( @@ -353,8 +355,13 @@ def _create_regular_parameter( field_mapping=field_mapping, ) + # Path parameter names must match the route-template placeholders, which are + # keyed by the Python argument name - an explicit alias would otherwise emit + # a name that does not exist in the path template. + param_name = param.name if field_info.in_.value == "path" else param.alias + parameter: dict[str, Any] = { - "name": param.alias, + "name": param_name, "in": field_info.in_.value, "required": param.required, "schema": param_schema, @@ -370,18 +377,25 @@ def _create_regular_parameter( return parameter -def _get_basic_type_schema(param_type: type) -> dict[str, str]: +def _get_basic_type_schema(param_type: type) -> dict[str, Any]: """Get basic OpenAPI schema for simple types.""" - type_map: dict[type, str] = {bool: "boolean", int: "integer", float: "number"} try: - for base_type, schema_type in type_map.items(): - if issubclass(param_type, base_type): - return {"type": schema_type} - return {"type": "string"} - except TypeError: + from pydantic import TypeAdapter + + return TypeAdapter(param_type).json_schema() + except Exception: return {"type": "string"} +def _validate_openapi_extension_keys(openapi_extensions: dict[str, Any]) -> None: + """Ensure every OpenAPI extension key respects the x- prefix convention.""" + from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError + + for extension_key in openapi_extensions: + if not str(extension_key).startswith("x-"): + raise SchemaValidationError("An OpenAPI extension key must start with x-") + + def _apply_optional_fields( operation: dict[str, Any], *, @@ -392,6 +406,7 @@ def _apply_optional_fields( if security: operation["security"] = security if openapi_extensions: + _validate_openapi_extension_keys(openapi_extensions) operation.update(openapi_extensions) @@ -501,16 +516,21 @@ def _build_custom_response( dependant: Dependant, model_name_map: dict[TypeModelOrEnum, str], field_mapping: dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + resp_code: int, + status_code: int = DEFAULT_STATUS_CODE, ) -> OpenAPIResponse: """Build a single custom response, resolving model references in content.""" if "content" not in response: - response["content"] = { - DEFAULT_CONTENT_TYPE: _build_return_schema( - param=dependant.return_param, - model_name_map=model_name_map, - field_mapping=field_mapping, - ), - } + # Only the route's own status code inherits the return-type schema; + # description-only entries (e.g. a 404) stay description-only. + if resp_code == status_code: + response["content"] = { + DEFAULT_CONTENT_TYPE: _build_return_schema( + param=dependant.return_param, + model_name_map=model_name_map, + field_mapping=field_mapping, + ), + } return response for content_type, payload in response["content"].items(): diff --git a/aws_lambda_powertools/event_handler/openapi/types.py b/aws_lambda_powertools/event_handler/openapi/types.py index 52915538b3e..de4b74b38c5 100644 --- a/aws_lambda_powertools/event_handler/openapi/types.py +++ b/aws_lambda_powertools/event_handler/openapi/types.py @@ -35,7 +35,7 @@ # For security reasons, we hide **msg** details (don't leak Python, Pydantic or filenames) "type": {"title": "Error Type", "type": "string"}, }, - "required": ["loc", "msg", "type"], + "required": ["loc", "type"], } validation_error_response_definition = { From 388b514264974128063fd047ff03eea98a1c7746 Mon Sep 17 00:00:00 2001 From: Abhishek Jha Date: Mon, 19 Oct 2026 06:30:47 +0530 Subject: [PATCH 2/7] fix(event_handler): correct six OpenAPI schema inaccuracies Signed-off-by: Abhishek Jha --- .../event_handler/middlewares/openapi_validation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py index 31bcfd70cbc..3bb1d2b02d4 100644 --- a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +++ b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py @@ -26,7 +26,7 @@ RequestValidationError, ResponseValidationError, ) -from aws_lambda_powertools.event_handler.openapi.params import Param, UploadFile +from aws_lambda_powertools.event_handler.openapi.params import Param, ParamTypes, UploadFile from aws_lambda_powertools.event_handler.openapi.types import UnionType from aws_lambda_powertools.utilities.data_classes.alb_event import ALBEvent from aws_lambda_powertools.utilities.data_classes.common import CaseInsensitiveDict @@ -422,6 +422,11 @@ def _request_params_to_args( loc = (field_info.in_.value, field.alias) value = received_params.get(field.alias) + # Route args are keyed by the path-template placeholder (the Python argument + # name), so an explicit Path alias must not make the param unsatisfiable. + if value is None and field_info.in_ == ParamTypes.path and field.name in received_params: + value = received_params[field.name] + # If we don't have a value, see if it's required or has a default if value is None: _handle_missing_field_value(field, values, errors, loc) From 69634846068bb3f06e4b37c2bd528bd783afab43 Mon Sep 17 00:00:00 2001 From: Abhishek Jha Date: Mon, 19 Oct 2026 06:33:36 +0530 Subject: [PATCH 3/7] fix(event_handler): correct six OpenAPI schema inaccuracies Signed-off-by: Abhishek Jha --- aws_lambda_powertools/event_handler/api_gateway.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 70b099269db..312ac85dde0 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -1863,6 +1863,10 @@ def get_openapi_schema( if not isinstance(openapi_extensions, dict): openapi_extensions = {} + for extension_key in openapi_extensions: + if not str(extension_key).startswith("x-"): + raise SchemaValidationError("An OpenAPI extension key must start with x-") + output: dict[str, Any] = { "openapi": openapi_version, "info": info, From a872d35ffcbba8708cecd0903e96b5480f8d171a Mon Sep 17 00:00:00 2001 From: Abhishek Jha Date: Mon, 19 Oct 2026 06:34:51 +0530 Subject: [PATCH 4/7] fix(event_handler): correct six OpenAPI schema inaccuracies Signed-off-by: Abhishek Jha --- .../event_handler/_pydantic/test_openapi_params.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/functional/event_handler/_pydantic/test_openapi_params.py b/tests/functional/event_handler/_pydantic/test_openapi_params.py index af33ef9daf6..41c780bfbe2 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_params.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_params.py @@ -71,7 +71,10 @@ def search_handler(params: Annotated[QueryParams, Query()]): elif param.name == "search": assert param.required is False # Optional field assert param.description == "Search term" - assert param.schema_.type == "string" + # str | None is rendered as an anyOf, not a bare string + assert param.schema_.type is None + assert param.schema_.anyOf is not None + assert [s.type for s in param.schema_.anyOf] == ["string", "null"] def test_openapi_pydantic_header_params(): @@ -119,7 +122,10 @@ def protected_handler(headers: Annotated[HeaderParams, Header()]): elif param.name == "accept-language": assert param.required is False # Optional field assert param.description == "Language preference" - assert param.schema_.type == "string" + # str | None is rendered as an anyOf, not a bare string + assert param.schema_.type is None + assert param.schema_.anyOf is not None + assert [s.type for s in param.schema_.anyOf] == ["string", "null"] def test_openapi_pydantic_mixed_params(): From 19b7fab564e1ecf389db891076df7c3c769eb9df Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 25 Oct 2026 17:47:27 +0000 Subject: [PATCH 5/7] refactor(event_handler): reuse _validate_openapi_extension_keys in get_openapi_schema to keep ruff complexity limits --- aws_lambda_powertools/event_handler/api_gateway.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 312ac85dde0..0e1c8a5a559 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -1841,6 +1841,7 @@ def get_openapi_schema( get_definitions, ) from aws_lambda_powertools.event_handler.openapi.models import OpenAPI, PathItem, Tag + from aws_lambda_powertools.event_handler.openapi.schema_generator import _validate_openapi_extension_keys from aws_lambda_powertools.event_handler.openapi.types import ( COMPONENT_REF_TEMPLATE, ) @@ -1863,9 +1864,7 @@ def get_openapi_schema( if not isinstance(openapi_extensions, dict): openapi_extensions = {} - for extension_key in openapi_extensions: - if not str(extension_key).startswith("x-"): - raise SchemaValidationError("An OpenAPI extension key must start with x-") + _validate_openapi_extension_keys(openapi_extensions) output: dict[str, Any] = { "openapi": openapi_version, From dd45a584e3ff8a7f1d087e69f6995126fcf419f9 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sun, 25 Oct 2026 18:11:52 +0000 Subject: [PATCH 6/7] test(event_handler): add regression tests for the six OpenAPI schema fixes --- .../_pydantic/test_openapi_encoders.py | 11 +++ .../_pydantic/test_openapi_extensions.py | 27 +++++++ .../_pydantic/test_openapi_params.py | 71 ++++++++++++++++++- .../_pydantic/test_openapi_responses.py | 30 ++++++++ .../test_openapi_validation_middleware.py | 20 ++++++ 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/tests/functional/event_handler/_pydantic/test_openapi_encoders.py b/tests/functional/event_handler/_pydantic/test_openapi_encoders.py index beac1764064..a6f945b8463 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_encoders.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_encoders.py @@ -242,3 +242,14 @@ def serializer(value): # THEN we should get the custom serializer output assert result == {"kind": "serialized"} + + +@pytest.mark.parametrize("value", ["NaN", "Infinity", "-Infinity"]) +def test_openapi_encode_non_finite_decimal_raises_serialization_error(value): + from decimal import Decimal + + # GIVEN a Decimal that has no JSON number representation + # WHEN we try to encode it + # THEN we raise a SerializationError instead of leaking a TypeError + with pytest.raises(SerializationError, match="not finite"): + jsonable_encoder(Decimal(value)) diff --git a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py index e7b64cba38b..383117dc8a3 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py @@ -2,7 +2,10 @@ import json +import pytest + from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Router +from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError from aws_lambda_powertools.event_handler.openapi.models import ( APIKey, APIKeyIn, @@ -266,3 +269,27 @@ def lambda_handler_post(): assert ( schema["paths"]["/test"]["post"]["x-amazon-apigateway-request-validator"] == openapi_extension_validator_detail ) + + +def test_openapi_extension_root_level_requires_x_prefix(): + # GIVEN an APIGatewayRestResolver instance + app = APIGatewayRestResolver() + + # WHEN we get the OpenAPI schema with a root extension that does not start with x- + # THEN it must be rejected like Server and SecurityScheme extensions already are + with pytest.raises(SchemaValidationError, match="must start with x-"): + app.get_openapi_json_schema(openapi_extensions={"amazon-apigateway-cors": {"maxAge": 0}}) + + +def test_openapi_extension_operation_level_requires_x_prefix(): + # GIVEN a route with an operation extension that does not start with x- + app = APIGatewayRestResolver() + + @app.get("/test", openapi_extensions={"amazon-apigateway-integration": {"type": "aws_proxy"}}) + def lambda_handler(): + pass + + # WHEN we get the OpenAPI schema + # THEN it must be rejected + with pytest.raises(SchemaValidationError, match="must start with x-"): + app.get_openapi_json_schema() diff --git a/tests/functional/event_handler/_pydantic/test_openapi_params.py b/tests/functional/event_handler/_pydantic/test_openapi_params.py index 41c780bfbe2..5e9bf28d5a3 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_params.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_params.py @@ -1,10 +1,11 @@ import json from dataclasses import dataclass from datetime import datetime +from enum import Enum from typing import Annotated, Literal import pytest -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Response, Router from aws_lambda_powertools.event_handler.openapi.models import ( @@ -1674,3 +1675,71 @@ def handler(x_custom: Annotated[str, Header]) -> dict: assert result["statusCode"] == 200 response_body = json.loads(result["body"]) assert response_body["header"] == "my-value" + + +def test_openapi_query_model_fields_keep_their_types(): + # GIVEN a Pydantic model used as Query() with optional, sequence and Enum fields + class Color(Enum): + red = "red" + blue = "blue" + + class Filters(BaseModel): + limit: int | None = None + tags: list[str] = [] + color: Color = Color.red + + app = APIGatewayRestResolver(enable_validation=True) + + @app.get("/search") + def search(filters: Annotated[Filters, Query()]): + return filters.model_dump() + + # WHEN we get the OpenAPI schema + schema = json.loads(app.get_openapi_json_schema()) + parameters = {p["name"]: p["schema"] for p in schema["paths"]["/search"]["get"]["parameters"]} + + # THEN each field keeps its own type instead of collapsing to string + assert parameters["limit"] == {"anyOf": [{"type": "integer"}, {"type": "null"}]} + assert parameters["tags"] == {"type": "array", "items": {"type": "string"}} + assert parameters["color"]["enum"] == ["red", "blue"] + assert parameters["color"]["type"] == "string" + + +def test_openapi_query_model_arbitrary_type_falls_back_to_string(): + # GIVEN a Pydantic model used as Query() with a field whose type has no JSON schema + class Opaque: + pass + + class Filters(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + token: Opaque + + app = APIGatewayRestResolver(enable_validation=True) + + @app.get("/search") + def search(filters: Annotated[Filters, Query()]): + return {} + + # WHEN we get the OpenAPI schema + schema = json.loads(app.get_openapi_json_schema()) + parameters = {p["name"]: p["schema"] for p in schema["paths"]["/search"]["get"]["parameters"]} + + # THEN the field is documented as a string rather than failing schema generation + assert parameters["token"] == {"type": "string"} + + +def test_openapi_validation_error_schema_does_not_require_hidden_msg(): + # GIVEN a resolver with validation enabled + app = APIGatewayRestResolver(enable_validation=True) + + @app.get("/") + def handler(): + return {} + + # WHEN we get the OpenAPI schema + schema = json.loads(app.get_openapi_json_schema()) + + # THEN the ValidationError component only requires the fields it actually exposes + validation_error = schema["components"]["schemas"]["ValidationError"] + assert validation_error["required"] == ["loc", "type"] + assert "msg" not in validation_error["properties"] diff --git a/tests/functional/event_handler/_pydantic/test_openapi_responses.py b/tests/functional/event_handler/_pydantic/test_openapi_responses.py index b7af4168b3e..23c63be2eea 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_responses.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_responses.py @@ -1,3 +1,4 @@ +import json from secrets import randbelow from pydantic import BaseModel @@ -492,3 +493,32 @@ def items_by_name() -> dict[str, Item]: dict_schema = schema.paths["/items/by-name"].get.responses[200].content["application/json"].schema_ assert dict_schema.type == "object" assert dict_schema.additionalProperties.ref == "#/components/schemas/Item" + + +def test_openapi_custom_response_without_content_keeps_description_only(): + # GIVEN a route with a return type and a description-only custom response for another status code + class Todo(BaseModel): + id: int + + app = APIGatewayRestResolver(enable_validation=True) + + @app.get( + "/todos/", + responses={ + 200: {"description": "The todo"}, + 404: {"description": "Todo not found"}, + 201: {"description": "Created"}, + }, + ) + def get_todo(todo_id: int) -> Todo: + return Todo(id=todo_id) + + # WHEN we get the OpenAPI schema + schema = json.loads(app.get_openapi_json_schema()) + responses = schema["paths"]["/todos/{todo_id}"]["get"]["responses"] + + # THEN only the route's own status code carries the return-type content + assert responses["200"]["description"] == "The todo" + assert "content" in responses["200"] + assert responses["404"] == {"description": "Todo not found"} + assert responses["201"] == {"description": "Created"} diff --git a/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py b/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py index 0af4cdcfb0e..9195222d9ff 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py @@ -4390,3 +4390,23 @@ def handler() -> Response[None]: result = app(gw_event_alb, {}) assert result["statusCode"] == 204 assert result["body"] == "" + + +def test_validate_path_param_with_alias(gw_event): + # GIVEN a path parameter declared with an explicit alias + app = APIGatewayRestResolver(enable_validation=True) + gw_event["path"] = "/users/42" + gw_event["httpMethod"] = "GET" + + @app.get("/users/") + def get_user(user_id: Annotated[int, Path(alias="userId")]): + return {"user_id": user_id} + + # WHEN the route is resolved + result = app(gw_event, {}) + + # THEN the value is found by the path template name and the schema names the template placeholder + assert result["statusCode"] == 200 + assert json.loads(result["body"]) == {"user_id": 42} + parameters = app.get_openapi_schema().paths["/users/{user_id}"].get.parameters + assert [p.name for p in parameters] == ["user_id"] From 96dd320a40898cba5a00c62f06b13b19339e0137 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 29 Oct 2026 13:08:57 +0000 Subject: [PATCH 7/7] fix(event_handler): warn instead of raising when an OpenAPI extension key lacks the x- prefix --- .../event_handler/openapi/schema_generator.py | 17 +++++++++++--- .../_pydantic/test_openapi_extensions.py | 22 +++++++++++-------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/aws_lambda_powertools/event_handler/openapi/schema_generator.py b/aws_lambda_powertools/event_handler/openapi/schema_generator.py index ebeabe16943..2eb9b55a0b5 100644 --- a/aws_lambda_powertools/event_handler/openapi/schema_generator.py +++ b/aws_lambda_powertools/event_handler/openapi/schema_generator.py @@ -388,12 +388,23 @@ def _get_basic_type_schema(param_type: type) -> dict[str, Any]: def _validate_openapi_extension_keys(openapi_extensions: dict[str, Any]) -> None: - """Ensure every OpenAPI extension key respects the x- prefix convention.""" - from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError + """Warn when an OpenAPI extension key does not respect the x- prefix convention. + + The OpenAPI spec reserves the `x-` prefix for extensions. We warn instead of raising so that + applications already serving a schema with a wrong key keep working; this becomes an error in v4. + """ + import warnings + + from aws_lambda_powertools.warnings import PowertoolsUserWarning for extension_key in openapi_extensions: if not str(extension_key).startswith("x-"): - raise SchemaValidationError("An OpenAPI extension key must start with x-") + warnings.warn( + f"OpenAPI extension key `{extension_key}` must start with `x-`; it is still emitted, " + "but this will become an error in a future major version.", + category=PowertoolsUserWarning, + stacklevel=2, + ) def _apply_optional_fields( diff --git a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py index 383117dc8a3..c7f5937bdbd 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py @@ -5,7 +5,6 @@ import pytest from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Router -from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError from aws_lambda_powertools.event_handler.openapi.models import ( APIKey, APIKeyIn, @@ -14,6 +13,7 @@ OAuthFlows, Server, ) +from aws_lambda_powertools.warnings import PowertoolsUserWarning def test_openapi_extension_root_level(): @@ -271,17 +271,19 @@ def lambda_handler_post(): ) -def test_openapi_extension_root_level_requires_x_prefix(): +def test_openapi_extension_root_level_warns_without_x_prefix(): # GIVEN an APIGatewayRestResolver instance app = APIGatewayRestResolver() # WHEN we get the OpenAPI schema with a root extension that does not start with x- - # THEN it must be rejected like Server and SecurityScheme extensions already are - with pytest.raises(SchemaValidationError, match="must start with x-"): - app.get_openapi_json_schema(openapi_extensions={"amazon-apigateway-cors": {"maxAge": 0}}) + # THEN we warn and still emit the schema, so existing applications keep working + with pytest.warns(PowertoolsUserWarning, match="must start with `x-`"): + schema = json.loads(app.get_openapi_json_schema(openapi_extensions={"amazon-apigateway-cors": {"maxAge": 0}})) + assert schema["amazon-apigateway-cors"] == {"maxAge": 0} -def test_openapi_extension_operation_level_requires_x_prefix(): + +def test_openapi_extension_operation_level_warns_without_x_prefix(): # GIVEN a route with an operation extension that does not start with x- app = APIGatewayRestResolver() @@ -290,6 +292,8 @@ def lambda_handler(): pass # WHEN we get the OpenAPI schema - # THEN it must be rejected - with pytest.raises(SchemaValidationError, match="must start with x-"): - app.get_openapi_json_schema() + # THEN we warn and the extension is still emitted on the operation + with pytest.warns(PowertoolsUserWarning, match="must start with `x-`"): + schema = json.loads(app.get_openapi_json_schema()) + + assert schema["paths"]["/test"]["get"]["amazon-apigateway-integration"] == {"type": "aws_proxy"}