1- from typing import Any
1+ from typing import Any , Final , TypedDict
22
33from aiohttp import web
44from aiohttp .web_exceptions import HTTPError
1010from ..mimetype_constants import MIMETYPE_APPLICATION_JSON
1111from ..rest_constants import RESPONSE_MODEL_POLICY
1212from ..rest_responses import is_enveloped
13- from ..status_codes_utils import get_code_description
13+ from ..status_codes_utils import get_code_description , is_error
14+
15+
16+ class EnvelopeDict (TypedDict ):
17+ data : Any
18+ error : Any
1419
1520
1621def wrap_as_envelope (
17- data : Any = None ,
18- error : Any = None ,
19- ) -> dict [ str , Any ] :
22+ data : Any | None = None ,
23+ error : Any | None = None ,
24+ ) -> EnvelopeDict :
2025 return {"data" : data , "error" : error }
2126
2227
2328def create_data_response (data : Any , * , status : int = HTTP_200_OK ) -> web .Response :
29+ """Creates a JSON response with the given data and ensures it is wrapped in an envelope."""
30+
31+ assert ( # nosec
32+ is_error (status ) is False
33+ ), f"Expected a non-error status code, got { status = } "
34+
2435 enveloped_payload = wrap_as_envelope (data ) if not is_enveloped (data ) else data
2536 return web .json_response (enveloped_payload , dumps = json_dumps , status = status )
2637
2738
39+ MAX_STATUS_MESSAGE_LENGTH : Final [int ] = 50
40+
41+
42+ def safe_status_message (
43+ message : str | None , max_length : int = MAX_STATUS_MESSAGE_LENGTH
44+ ) -> str | None :
45+ """
46+ Truncates a status-message (i.e. `reason` in HTTP errors) to a maximum length, replacing newlines with spaces.
47+
48+ If the message is longer than max_length, it will be truncated and "..." will be appended.
49+
50+ This prevents issues such as:
51+ - `aiohttp.http_exceptions.LineTooLong`: 400, message: Got more than 8190 bytes when reading Status line is too long.
52+ - Multiline not allowed in HTTP reason attribute (aiohttp now raises ValueError).
53+
54+ See:
55+ - When to use http status and/or text messages https://github.com/ITISFoundation/osparc-simcore/pull/7760
56+ - [RFC 9112, Section 4.1: HTTP/1.1 Message Syntax and Routing](https://datatracker.ietf.org/doc/html/rfc9112#section-4.1) (status line length limits)
57+ - [RFC 9110, Section 15.5: Reason Phrase](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5) (reason phrase definition)
58+ """
59+ assert max_length > 0 # nosec
60+
61+ if not message :
62+ return None
63+
64+ flat_message = message .replace ("\n " , " " )
65+ if len (flat_message ) <= max_length :
66+ return flat_message
67+
68+ # Truncate and add ellipsis
69+ return flat_message [: max_length - 3 ] + "..."
70+
71+
2872def create_http_error (
2973 errors : list [Exception ] | Exception ,
3074 error_message : str | None = None ,
@@ -45,7 +89,9 @@ def create_http_error(
4589 is_internal_error = bool (http_error_cls == web .HTTPInternalServerError )
4690
4791 status_reason = status_reason or get_code_description (http_error_cls .status_code )
48- error_message = error_message or status_reason
92+ error_message = error_message or get_code_description (http_error_cls .status_code )
93+
94+ assert len (status_reason ) < MAX_STATUS_MESSAGE_LENGTH # nosec
4995
5096 if is_internal_error and skip_internal_error_details :
5197 error = ErrorGet .model_validate (
@@ -90,29 +136,3 @@ def exception_to_response(exc: HTTPError) -> web.Response:
90136 reason = exc .reason ,
91137 text = exc .text ,
92138 )
93-
94-
95- def safe_status_message (message : str | None , max_length : int = 50 ) -> str | None :
96- """
97- Truncates a status-message (i.e. `reason` in HTTP errors) to a maximum length, replacing newlines with spaces.
98-
99- If the message is longer than max_length, it will be truncated and "..." will be appended.
100-
101- This prevents issues such as:
102- - `aiohttp.http_exceptions.LineTooLong`: 400, message: Got more than 8190 bytes when reading Status line is too long.
103- - Multiline not allowed in HTTP reason attribute (aiohttp now raises ValueError).
104-
105- See:
106- - When to use http status and/or text messages https://github.com/ITISFoundation/osparc-simcore/pull/7760
107- - [RFC 9112, Section 4.1: HTTP/1.1 Message Syntax and Routing](https://datatracker.ietf.org/doc/html/rfc9112#section-4.1) (status line length limits)
108- - [RFC 9110, Section 15.5: Reason Phrase](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5) (reason phrase definition)
109- """
110- if not message :
111- return None
112-
113- flat_message = message .replace ("\n " , " " )
114- if len (flat_message ) <= max_length :
115- return flat_message
116-
117- # Truncate and add ellipsis
118- return flat_message [: max_length - 3 ] + "..."
0 commit comments