From 49def556c9e3a12cb08a9c36ecaed9fba8e35e63 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Fri, 22 May 2026 15:12:10 -0400 Subject: [PATCH] Update unary-get query paramter to match spec Signed-off-by: Oliver Sun --- src/connectrpc/_client_shared.py | 12 +++++------ test/test_client_shared.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 test/test_client_shared.py diff --git a/src/connectrpc/_client_shared.py b/src/connectrpc/_client_shared.py index d0df033..7aa4201 100644 --- a/src/connectrpc/_client_shared.py +++ b/src/connectrpc/_client_shared.py @@ -22,14 +22,14 @@ def prepare_get_params( codec: Codec, request_data: bytes, headers: HTTPHeaders ) -> dict[str, str]: - params = { - "connect": f"v{CONNECT_PROTOCOL_VERSION}", - "message": base64.urlsafe_b64encode(request_data).decode("ascii"), - "base64": "1", - "encoding": codec.name(), - } + # Insertion order is the Query-Get order from the Connect spec; urlencode + # iterates dict items in insertion order, which has been guaranteed since + # Python 3.7 (we require >= 3.10). + params: dict[str, str] = {"connect": f"v{CONNECT_PROTOCOL_VERSION}", "base64": "1"} if "content-encoding" in headers: params["compression"] = headers.pop("content-encoding") + params["encoding"] = codec.name() + params["message"] = base64.urlsafe_b64encode(request_data).decode("ascii") return params diff --git a/test/test_client_shared.py b/test/test_client_shared.py new file mode 100644 index 0000000..ee86b4d --- /dev/null +++ b/test/test_client_shared.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from urllib.parse import urlencode + +from pyqwest import Headers as HTTPHeaders + +from connectrpc._client_shared import prepare_get_params +from connectrpc.codec import proto_binary_codec + + +def test_prepare_get_params_order_without_compression() -> None: + params = prepare_get_params(proto_binary_codec(), b"hello", HTTPHeaders([])) + assert list(params.keys()) == ["connect", "base64", "encoding", "message"] + + +def test_prepare_get_params_order_with_compression() -> None: + headers = HTTPHeaders([("content-encoding", "gzip")]) + params = prepare_get_params(proto_binary_codec(), b"hello", headers) + assert list(params.keys()) == [ + "connect", + "base64", + "compression", + "encoding", + "message", + ] + assert "content-encoding" not in headers + + +def test_prepare_get_params_urlencode_order() -> None: + headers = HTTPHeaders([("content-encoding", "gzip")]) + params = prepare_get_params(proto_binary_codec(), b"hello", headers) + query = urlencode(params) + expected_prefix = "connect=v1&base64=1&compression=gzip&encoding=proto&message=" + assert query.startswith(expected_prefix)