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
7 changes: 0 additions & 7 deletions src/azure_functions_validation/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
MalformedRequestError,
PayloadTooLargeError,
RequestBindingError,
SerializationError,
UnsupportedMediaTypeError,
format_error_response,
)
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand Down
49 changes: 49 additions & 0 deletions tests/test_response_exception_parity.py
Original file line number Diff line number Diff line change
@@ -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
Loading