Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,17 @@ def handler(self, app: EventHandlerInstance, next_middleware: NextMiddleware) ->
headers,
)

# Process cookie values
cookie_values, cookie_errors = _request_params_to_args(
route.dependant.cookie_params,
app.current_event.resolved_cookies_field,
)

values.update(path_values)
values.update(query_values)
values.update(header_values)
errors += path_errors + query_errors + header_errors
values.update(cookie_values)
errors += path_errors + query_errors + header_errors + cookie_errors

# Process the request body, if it exists
if route.dependant.body_params:
Expand Down
73 changes: 73 additions & 0 deletions aws_lambda_powertools/event_handler/openapi/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,79 @@ def alias(self, value: str | None = None):
self._alias = value.lower()


class Cookie(Param): # type: ignore[misc]
"""
A class used internally to represent a cookie parameter in a path operation.
"""

in_ = ParamTypes.cookie

def __init__(
self,
default: Any = Undefined,
*,
default_factory: Callable[[], Any] | None = _Unset,
annotation: Any | None = None,
alias: str | None = None,
alias_priority: int | None = _Unset,
# MAINTENANCE: update when deprecating Pydantic v1, import these types
# str | AliasPath | AliasChoices | None
validation_alias: str | None = _Unset,
serialization_alias: str | None = None,
title: str | None = None,
description: str | None = None,
gt: float | None = None,
ge: float | None = None,
lt: float | None = None,
le: float | None = None,
min_length: int | None = None,
max_length: int | None = None,
pattern: str | None = None,
discriminator: str | None = None,
strict: bool | None = _Unset,
multiple_of: float | None = _Unset,
allow_inf_nan: bool | None = _Unset,
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,
**extra: Any,
):
super().__init__(
default=default,
default_factory=default_factory,
annotation=annotation,
alias=alias,
alias_priority=alias_priority,
validation_alias=validation_alias,
serialization_alias=serialization_alias,
title=title,
description=description,
gt=gt,
ge=ge,
lt=lt,
le=le,
min_length=min_length,
max_length=max_length,
pattern=pattern,
discriminator=discriminator,
strict=strict,
multiple_of=multiple_of,
allow_inf_nan=allow_inf_nan,
max_digits=max_digits,
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,
)


class Body(FieldInfo): # type: ignore[misc]
"""
A class used internally to represent a body parameter in a path operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,17 @@ def raw_query_string(self) -> str:
def cookies(self) -> list[str]:
return self.get("cookies") or []

@property
def resolved_cookies_field(self) -> dict[str, str]:
"""
Parse cookies from the dedicated ``cookies`` field in API Gateway HTTP API v2 format.

The ``cookies`` field contains a list of strings like ``["session=abc", "theme=dark"]``.
"""
from aws_lambda_powertools.utilities.data_classes.common import _parse_cookie_string

return _parse_cookie_string("; ".join(self.cookies))

@property
def request_context(self) -> RequestContextV2:
return RequestContextV2(self["requestContext"])
Expand Down
41 changes: 41 additions & 0 deletions aws_lambda_powertools/utilities/data_classes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@
)


def _parse_cookie_string(cookie_string: str) -> dict[str, str]:
"""Parse a cookie string (``key=value; key2=value2``) into a dict."""
cookies: dict[str, str] = {}
for segment in cookie_string.split(";"):
stripped = segment.strip()
if "=" in stripped:
name, _, value = stripped.partition("=")
cookies[name.strip()] = value.strip()
return cookies


class CaseInsensitiveDict(dict):
"""Case insensitive dict implementation. Assumes string keys only."""

Expand Down Expand Up @@ -203,6 +214,36 @@ def resolved_headers_field(self) -> dict[str, str]:
"""
return self.headers

@property
def resolved_cookies_field(self) -> dict[str, str]:
"""
This property extracts cookies from the request as a dict of name-value pairs.

By default, cookies are parsed from the ``Cookie`` header.
Uses ``self.headers`` (CaseInsensitiveDict) first for reliable case-insensitive
lookup, then falls back to ``resolved_headers_field`` for proxies that only
populate multi-value headers (e.g., ALB without single-value headers).
Subclasses may override this for event formats that provide cookies
in a dedicated field (e.g., API Gateway HTTP API v2).
"""
# Primary: self.headers is CaseInsensitiveDict — case-insensitive lookup
cookie_value: str | list[str] = self.headers.get("cookie") or ""

# Fallback: resolved_headers_field covers ALB/REST v1 multi-value headers
# where the event may not have a single-value 'headers' dict at all
if not cookie_value:
headers = self.resolved_headers_field or {}
cookie_value = headers.get("cookie") or headers.get("Cookie") or ""

# Multi-value headers (ALB, REST v1) may return a list
if isinstance(cookie_value, list):
cookie_value = "; ".join(cookie_value)

if not cookie_value:
return {}

return _parse_cookie_string(cookie_value)

@property
def is_base64_encoded(self) -> bool | None:
return self.get("isBase64Encoded")
Expand Down
Loading
Loading