From d25b5191b1051281bb82e5cbd8fe0fd8c88d1bfc Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:58:20 -0700 Subject: [PATCH] fix: name the missing required headers on binary HTTP requests A binary-mode HTTP request that is missing ce-id, ce-source or ce-type (but carries at least one ce- header) fails the binary converter's can_read check, so from_http falls through to the structured branch and reports "Failed to find specversion in HTTP request" even when ce-specversion is present. from_http now checks which of the four required binary headers are absent when at least one of them is present, and names them in the MissingRequiredFields message. A request with no binary headers at all is genuinely structured and keeps the existing message. Adds a regression test beside the existing binary missing-field tests asserting each invalid header set names its own missing header. Closes #139 Signed-off-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ src/cloudevents/v1/conversion.py | 27 ++++++++++++++++++++++++ tests/test_v1_compat/test_http_events.py | 13 ++++++++++++ 3 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa654e2a..ffd81618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- `from_http` now names the required binary headers that are actually missing, + instead of always reporting a missing `specversion`. ([#139]) + ## [2.2.0] ### Changed @@ -368,3 +373,4 @@ CloudEvents v2 is a rewrite with ongoing development ([#271]) [#279]: https://github.com/cloudevents/sdk-python/pull/279 [#284]: https://github.com/cloudevents/sdk-python/pull/284 [#291]: https://github.com/cloudevents/sdk-python/pull/291 +[#139]: https://github.com/cloudevents/sdk-python/issues/139 diff --git a/src/cloudevents/v1/conversion.py b/src/cloudevents/v1/conversion.py index 51cb3fa6..a18e31f7 100644 --- a/src/cloudevents/v1/conversion.py +++ b/src/cloudevents/v1/conversion.py @@ -22,6 +22,27 @@ from cloudevents.v1.sdk.converters import is_binary from cloudevents.v1.sdk.event import v03, v1 +_REQUIRED_BINARY_HEADERS = ("ce-specversion", "ce-id", "ce-source", "ce-type") + + +def _missing_binary_headers( + headers: typing.Mapping[str, str], +) -> typing.List[str]: + """ + Lists the required binary-mode headers missing from a partially populated + binary HTTP request. + + Returns an empty list when no binary header is present at all, since such a + request is not an incomplete binary one. + + :param headers: The lower-cased HTTP request headers. + :returns: The missing required binary headers. + """ + missing = [header for header in _REQUIRED_BINARY_HEADERS if header not in headers] + if len(missing) == len(_REQUIRED_BINARY_HEADERS): + return [] + return missing + def _best_effort_serialize_to_json( # type: ignore[no-untyped-def] value: typing.Any, *args, **kwargs @@ -146,6 +167,12 @@ def from_http( ) if specversion is None: + missing_binary_headers = _missing_binary_headers(headers) + if missing_binary_headers: + raise cloud_exceptions.MissingRequiredFields( + "Failed to find the following required headers in the binary " + "HTTP request: {}".format(", ".join(missing_binary_headers)) + ) raise cloud_exceptions.MissingRequiredFields( "Failed to find specversion in HTTP request" ) diff --git a/tests/test_v1_compat/test_http_events.py b/tests/test_v1_compat/test_http_events.py index ed6acb17..98bc35bf 100644 --- a/tests/test_v1_compat/test_http_events.py +++ b/tests/test_v1_compat/test_http_events.py @@ -103,6 +103,19 @@ def test_missing_required_fields_empty_data_binary(headers): _ = from_http(headers, None) +@pytest.mark.parametrize( + "headers, missing_header", + list( + zip(invalid_test_headers, ["ce-id", "ce-source", "ce-type", "ce-specversion"]) + ), +) +def test_missing_required_fields_binary_names_missing_header(headers, missing_header): + # Test for issue #139 + with pytest.raises(cloud_exceptions.MissingRequiredFields) as e: + _ = from_http(headers, json.dumps(test_data)) + assert missing_header in str(e.value) + + @pytest.mark.parametrize("specversion", ["1.0", "0.3"]) def test_emit_binary_event(specversion): headers = {