diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 4e2be256ecb..5eba4220c22 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -552,11 +552,7 @@ def _get_openapi_path( operation_responses: dict[int, OpenAPIResponse] = { 422: { "description": "Validation Error", - "content": { - "application/json": { - "schema": {"$ref": COMPONENT_REF_PREFIX + "HTTPValidationError"}, - }, - }, + "content": {"application/json": {"schema": {"$ref": f"{COMPONENT_REF_PREFIX}HTTPValidationError"}}}, }, } @@ -761,6 +757,9 @@ def _openapi_operation_parameters( if field_info.description: parameter["description"] = field_info.description + if field_info.openapi_examples: + parameter["examples"] = field_info.openapi_examples + if field_info.deprecated: parameter["deprecated"] = field_info.deprecated diff --git a/aws_lambda_powertools/event_handler/openapi/params.py b/aws_lambda_powertools/event_handler/openapi/params.py index 1d6b5da145c..7d4b25269a2 100644 --- a/aws_lambda_powertools/event_handler/openapi/params.py +++ b/aws_lambda_powertools/event_handler/openapi/params.py @@ -20,6 +20,7 @@ ) if TYPE_CHECKING: + from aws_lambda_powertools.event_handler.openapi.models import Example from aws_lambda_powertools.event_handler.openapi.types import CacheKey """ @@ -117,6 +118,7 @@ def __init__( max_digits: int | None = _Unset, decimal_places: int | None = _Unset, examples: list[Any] | None = None, + openapi_examples: dict[str, Example] | None = None, deprecated: bool | None = None, include_in_schema: bool = True, json_schema_extra: dict[str, Any] | None = None, @@ -205,8 +207,13 @@ def __init__( if examples is not None: kwargs["examples"] = examples + if openapi_examples is not None: + kwargs["openapi_examples"] = openapi_examples + current_json_schema_extra = json_schema_extra or extra + self.openapi_examples = openapi_examples + kwargs.update( { "annotation": annotation, @@ -262,6 +269,7 @@ def __init__( max_digits: int | None = _Unset, decimal_places: int | None = _Unset, examples: list[Any] | None = None, + openapi_examples: dict[str, Example] | None = None, deprecated: bool | None = None, include_in_schema: bool = True, json_schema_extra: dict[str, Any] | None = None, @@ -353,6 +361,7 @@ def __init__( decimal_places=decimal_places, deprecated=deprecated, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -392,6 +401,7 @@ def __init__( max_digits: int | None = _Unset, decimal_places: int | None = _Unset, examples: list[Any] | None = None, + openapi_examples: dict[str, Example] | None = None, deprecated: bool | None = None, include_in_schema: bool = True, json_schema_extra: dict[str, Any] | None = None, @@ -480,6 +490,7 @@ def __init__( decimal_places=decimal_places, deprecated=deprecated, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -522,6 +533,7 @@ def __init__( max_digits: int | None = _Unset, decimal_places: int | None = _Unset, examples: list[Any] | None = None, + openapi_examples: dict[str, Example] | None = None, deprecated: bool | None = None, include_in_schema: bool = True, json_schema_extra: dict[str, Any] | None = None, @@ -616,6 +628,7 @@ def __init__( decimal_places=decimal_places, deprecated=deprecated, examples=examples, + openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, **extra, @@ -669,6 +682,7 @@ def __init__( max_digits: int | None = _Unset, decimal_places: int | None = _Unset, examples: list[Any] | None = None, + openapi_examples: dict[str, Example] | None = None, deprecated: bool | None = None, include_in_schema: bool = True, json_schema_extra: dict[str, Any] | None = None, diff --git a/docs/core/event_handler/_openapi_customization_parameters.md b/docs/core/event_handler/_openapi_customization_parameters.md index 6b87ce5c598..27e7c6915cc 100644 --- a/docs/core/event_handler/_openapi_customization_parameters.md +++ b/docs/core/event_handler/_openapi_customization_parameters.md @@ -1,25 +1,25 @@ Whenever you use OpenAPI parameters to validate [query strings](api_gateway.md#validating-query-strings) or [path parameters](api_gateway.md#validating-path-parameters), you can enhance validation and OpenAPI documentation by using any of these parameters: -| Field name | Type | Description | -|-----------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data | -| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) | -| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) | -| `description` | `str` | Human-readable description | -| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers | -| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers | -| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers | -| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers | -| `min_length` | `int` | Minimum length for strings | -| `max_length` | `int` | Maximum length for strings | -| `pattern` | `string` | A regular expression that the string must match. | -| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details | -| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers | -| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers | -| `max_digits` | `int` | Maximum number of allow digits for strings | -| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers | -| `examples` | `List[Any]` | List of examples of the field | -| `deprecated` | `bool` | Marks the field as deprecated | -| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema | -| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property | +| Field name | Type | Description | +| --------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data | +| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) | +| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) | +| `description` | `str` | Human-readable description | +| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers | +| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers | +| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers | +| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers | +| `min_length` | `int` | Minimum length for strings | +| `max_length` | `int` | Maximum length for strings | +| `pattern` | `string` | A regular expression that the string must match. | +| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details | +| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers | +| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers | +| `max_digits` | `int` | Maximum number of allow digits for strings | +| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers | +| `openapi_examples` | `dict[str, Example]` | A list of examples to be displayed in the SwaggerUI interface. Avoid using the `examples` field for this purpose. | +| `deprecated` | `bool` | Marks the field as deprecated | +| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema | +| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property | diff --git a/tests/functional/event_handler/_pydantic/test_openapi_params.py b/tests/functional/event_handler/_pydantic/test_openapi_params.py index 0273c4b5712..2cf77a7de08 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_params.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_params.py @@ -608,3 +608,44 @@ def handler() -> Todo: assert "completed" in todo_schema.properties completed_property = todo_schema.properties["completed"] assert completed_property.examples == [True] + + +def test_openapi_with_openapi_example(): + app = APIGatewayRestResolver() + + first_example = Example(summary="Example1", description="Example1", value="a") + second_example = Example(summary="Example2", description="Example2", value="b") + + @app.get("/users", summary="Get Users", operation_id="GetUsers", description="Get paginated users", tags=["Users"]) + def handler( + count: Annotated[ + int, + Query( + openapi_examples={ + "first_example": first_example, + "second_example": second_example, + }, + ), + ] = 1, + ): + print(count) + raise NotImplementedError() + + schema = app.get_openapi_schema() + + get = schema.paths["/users"].get + assert len(get.parameters) == 1 + assert get.summary == "Get Users" + assert get.operationId == "GetUsers" + assert get.description == "Get paginated users" + assert get.tags == ["Users"] + + parameter = get.parameters[0] + assert parameter.required is False + assert parameter.name == "count" + assert parameter.examples["first_example"] == first_example + assert parameter.examples["second_example"] == second_example + assert parameter.in_ == ParameterInType.query + assert parameter.schema_.type == "integer" + assert parameter.schema_.default == 1 + assert parameter.schema_.title == "Count"