From 26037a7e5ae8b7879cd1f70acf7a824ac11bd659 Mon Sep 17 00:00:00 2001 From: Yeongseon Choe Date: Tue, 27 Oct 2026 00:13:09 +0900 Subject: [PATCH] refactor: consolidate equivalent response exception handlers --- src/azure_functions_validation/pipeline.py | 7 ---- tests/test_response_exception_parity.py | 49 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 tests/test_response_exception_parity.py diff --git a/src/azure_functions_validation/pipeline.py b/src/azure_functions_validation/pipeline.py index e71deef..2665db6 100644 --- a/src/azure_functions_validation/pipeline.py +++ b/src/azure_functions_validation/pipeline.py @@ -24,7 +24,6 @@ MalformedRequestError, PayloadTooLargeError, RequestBindingError, - SerializationError, UnsupportedMediaTypeError, format_error_response, ) @@ -343,8 +342,6 @@ def _build_response(result: Any, config: PipelineConfig) -> HttpResponse: validated_result = config.adapter.validate_response( result, config.response_model, type_adapter=config.response_type_adapter ) - except AdapterValidationError as e: - return _format_error(e, 500, config) except Exception as e: return _format_error(e, 500, config) @@ -360,8 +357,6 @@ def _build_response(result: Any, config: PipelineConfig) -> HttpResponse: exclude_none=config.response_exclude_none, exclude_unset=config.response_exclude_unset, ) - except (SerializationError, TypeError, ValueError) as e: - return _format_error(e, 500, config) except Exception as e: return _format_error(e, 500, config) @@ -374,8 +369,6 @@ def _build_response(result: Any, config: PipelineConfig) -> HttpResponse: # No response model, serialize directly try: content, content_type = config.adapter.serialize(result) - except (SerializationError, TypeError) as e: - return _format_error(e, 500, config) except Exception as e: return _format_error(e, 500, config) diff --git a/tests/test_response_exception_parity.py b/tests/test_response_exception_parity.py new file mode 100644 index 0000000..2f7d6db --- /dev/null +++ b/tests/test_response_exception_parity.py @@ -0,0 +1,49 @@ +import json +import logging +from unittest.mock import Mock + +from azure.functions import HttpRequest +from pydantic import BaseModel +import pytest + +from azure_functions_validation import PydanticAdapter, validate_http + + +class Output(BaseModel): + value: int + + +@pytest.mark.parametrize("seam", ["validate_response", "serialize_response", "serialize"]) +@pytest.mark.parametrize("error_type", [TypeError, ValueError, RuntimeError]) +@pytest.mark.parametrize("expose", [False, True]) +def test_response_faults_preserve_formatter_status_and_logging( + seam: str, error_type: type[Exception], expose: bool, caplog: pytest.LogCaptureFixture +) -> None: + original = error_type("private-canary") + adapter = PydanticAdapter() + setattr(adapter, seam, Mock(side_effect=original)) + captured: list[tuple[Exception, int]] = [] + + def formatter(exc: Exception, status: int) -> dict[str, str]: + captured.append((exc, status)) + return {"error": "safe"} + + @validate_http( + adapter=adapter, + response_model=Output if seam != "serialize" else None, + error_formatter=formatter, + expose_internal_errors=expose, + ) + def handler(req: HttpRequest) -> dict[str, int]: + return {"value": 1} + + with caplog.at_level(logging.ERROR): + response = handler(HttpRequest("GET", "https://example.test", body=b"")) + assert response.status_code == 500 + assert json.loads(response.get_body()) == {"error": "safe"} + assert captured[0][1] == 500 + assert (captured[0][0] is original) == expose + errors = [record for record in caplog.records if record.exc_info] + assert len(errors) == 1 + assert errors[0].exc_info is not None + assert errors[0].exc_info[1] is original