Skip to content

Conversation

matdev83
Copy link
Owner

@matdev83 matdev83 commented Oct 12, 2025

Summary

  • normalize HTTPException detail payloads so structured information is preserved in responses
  • apply the normalized message/type/details for both standard and chat completion error formats
  • expand unit coverage for the HTTP exception handler with structured detail scenarios

Testing

  • python -m pytest --override-ini addopts= tests/unit/core/app/test_error_handlers.py
  • python -m pytest --override-ini addopts=
    • failing because pytest_asyncio, respx, pytest_httpx, and other optional test dependencies are not installed in the environment

https://chatgpt.com/codex/tasks/task_e_68ec25e4aee08333a1a09066d47a3acf

Summary by CodeRabbit

  • New Features
    • Consistent, structured error responses across endpoints with stable message, type, status_code and preserved extra details.
    • Chat completion errors now include structured details and status codes while keeping nested error metadata intact.
  • Bug Fixes
    • Stabilized error payloads to correctly merge and surface contextual fields from nested or mapped error details.
  • Refactor
    • Centralized normalization of HTTP error details to ensure uniform response formatting.
  • Tests
    • Added unit tests covering mapped/structured HTTP errors and chat-completion error handling.

Copy link

coderabbitai bot commented Oct 12, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Added a helper to normalize various HTTPException detail shapes into (message, type, details); refactored http_exception_handler to use it for consistent chat and non-chat error payloads; added unit tests covering normalization, merging, and preservation of structured detail and outer metadata.

Changes

Cohort / File(s) Change Summary
Error handling normalization
src/core/app/error_handlers.py
Added _normalize_http_exception_detail(detail) to extract a stable (message, type, details) triple from varied exc.detail shapes; refactored http_exception_handler to call it; adjusted chat vs non-chat payload construction to include normalized message/type and conditionally attach extra details; imported Mapping.
Unit tests for error handling
tests/unit/core/app/test_error_handlers.py
Added tests asserting: mapping-provided details are flattened/merged into the error payload, structured error details are preserved for chat-completions, outer metadata is preserved/merged, and retained chat-completions behavior test placements.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant C as Client
    participant A as App/Router
    participant H as http_exception_handler
    participant N as _normalize_http_exception_detail
    participant R as Response

    C->>A: Request triggers HTTPException
    A->>H: Pass HTTPException (exc)
    H->>N: Normalize exc.detail
    N-->>H: (message, type, extra_details)

    alt Chat completions endpoint
        H->>H: Build chat error payload using message/type
        H->>H: Attach extra_details under `error` when present
    else Standard endpoint
        H->>H: Build non-chat error payload with status_code
        H->>H: Merge/preserve extra_details into `detail.error`
    end

    H-->>R: Return JSON response
    R-->>C: Error payload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paws at errors’ lair,
Sorting messages with gentle care—
Type and details, folded neat,
Merged and kept for safe repeat.
Tests guard the warren, snug and bright. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the main change to normalize HTTP exception handler details, matching the pull request’s focus on fixing detail normalization in error responses. It is concise, clear, and directly related to the modifications in the error_handlers logic and associated tests. The absence of extraneous language makes it easy for reviewers to understand the purpose at a glance.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/fix-one-bug-in-error-handling-ifkf1x

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Comment on lines 69 to 80
default_type = "HttpError"
if isinstance(detail, Mapping):
# Handle FastAPI-style payloads where the detail already contains an
# ``error`` structure with message/type/details fields.
nested_error = detail.get("error")
if isinstance(nested_error, Mapping):
message = nested_error.get("message")
error_type = nested_error.get("type", default_type)
details = nested_error.get("details")
if message is None:
message = str({k: v for k, v in detail.items() if k != "error"})
return str(message), str(error_type), details

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve outer metadata when normalizing nested HTTPException details

When the detail payload already contains an error object, _normalize_http_exception_detail returns only the nested message/type/details and ignores any sibling keys in the original mapping. For a payload like {"error": {...}, "trace_id": "abc"} the returned tuple drops trace_id, so the standard and chat error responses no longer contain that metadata at all. Prior to this change the full dictionary was stringified and the metadata remained visible, so this regression strips useful context (request IDs, trace data, etc.) from error responses. Consider merging keys outside of error into the returned details just like the generic mapping branch does so structured extras are preserved.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/core/app/error_handlers.py (1)

64-110: LGTM! Solid normalization logic with good fallback handling.

The _normalize_http_exception_detail function effectively handles three distinct payload shapes:

  1. Nested error structure (lines 70-80): Extracts from detail.error.{message,type,details}
  2. Generic mapping (lines 83-106): Extracts common keys and merges extra fields into details
  3. Fallback (lines 108-109): Treats non-mapping details as strings

The merge strategy at line 99 ({**extras, **details}) correctly prioritizes explicit details fields over top-level extras, ensuring structured context is preserved without loss.

Edge cases are handled gracefully:

  • None message triggers stringification of remaining fields (lines 78-79, 103-104)
  • Non-mapping details with extras wraps both (line 101)
  • String/primitive details fall through to string conversion (line 109)

Consider enhancing the docstring to document the three input formats and the merge precedence rules for better maintainability:

 def _normalize_http_exception_detail(
     detail: Any,
 ) -> tuple[str, str, Any | None]:
-    """Extract a stable message/type/details triple from HTTPException detail."""
+    """Extract a stable message/type/details triple from HTTPException detail.
+    
+    Handles three payload formats:
+    1. Nested: {"error": {"message": str, "type": str, "details": dict}}
+    2. Flat mapping: {"message": str, "type": str, "details": dict, ...extras}
+       - Extra fields are merged into details (details takes precedence)
+    3. Fallback: Any non-mapping value is stringified
+    
+    Returns:
+        Tuple of (message, error_type, details_or_none)
+    """
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 326702b and 7c0de8a.

📒 Files selected for processing (2)
  • src/core/app/error_handlers.py (4 hunks)
  • tests/unit/core/app/test_error_handlers.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Avoid using emojis in Python code comments or docstrings
Follow PEP 8 and use type hints for all functions
Import order must be: standard library, third-party, then local imports, separated by blank lines
Naming conventions: snake_case for variables and functions; PascalCase for classes
Error handling: use specific exceptions and include meaningful error messages
Prefer f-strings for string formatting

Files:

  • src/core/app/error_handlers.py
  • tests/unit/core/app/test_error_handlers.py
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Catch specific exceptions; avoid broad except Exception blocks
If a broad exception must be caught, log with exc_info=True and re-raise a specific custom exception
Use the most specific exception class from src.core.common.exceptions that accurately describes the error
Provide clear, helpful error messages and include relevant details in the details dictionary of custom exceptions

Files:

  • src/core/app/error_handlers.py
🧬 Code graph analysis (1)
tests/unit/core/app/test_error_handlers.py (1)
src/core/app/error_handlers.py (1)
  • http_exception_handler (112-180)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10)
  • GitHub Check: Analyze (python)
🔇 Additional comments (4)
tests/unit/core/app/test_error_handlers.py (2)

142-171: LGTM! Comprehensive test for detail normalization and merging.

The test correctly validates that http_exception_handler normalizes structured detail payloads by:

  • Extracting message and type from the top level
  • Merging extra fields (like hint) into the details dict
  • Preserving the original details field contents

The assertion correctly expects that hint and field are both present in the merged details payload, with details values taking precedence in case of conflicts.


193-220: LGTM! Well-structured test for nested error payloads.

The test validates that the handler correctly normalizes nested error structures (where detail.error contains the actual error fields) and preserves the structured information in the chat completion response format. The assertions properly verify both the error message in the chat content and the complete error payload structure.

src/core/app/error_handlers.py (2)

5-5: LGTM! Appropriate import for type checking.

The Mapping import from collections.abc is correctly placed in the standard library import section and is used appropriately for type checking in the normalization function.


130-168: LGTM! Clean refactor with consistent error payload construction.

The handler correctly uses the normalization helper to derive message, error_type, and extra_details, then applies them consistently for both chat and non-chat responses:

  • Chat completions (lines 146, 153-154, 158-159): Uses normalized values in both the error message content and the structured error payload
  • Standard responses (lines 162-168): Builds a unified error payload structure with conditional details inclusion

The refactor eliminates duplication and ensures structured detail information is preserved across both response formats.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/core/app/error_handlers.py (2)

64-118: Consider extracting duplicate merging logic.

The function correctly handles three cases: nested error structures, generic mappings, and fallback. However, the merging logic (lines 79-85 and lines 102-109) is duplicated. While the duplication is minimal, extracting it to a helper function would improve maintainability.

Apply this diff to extract the merging logic:

+def _merge_details_with_extras(
+    details: Any | None,
+    extras: dict[str, Any],
+) -> Any | None:
+    """Merge extras into details, handling various detail types."""
+    if not extras:
+        return details
+    if details is None:
+        return extras
+    elif isinstance(details, Mapping):
+        return {**extras, **details}
+    else:
+        return {"value": details, "extras": extras}
+
+
 def _normalize_http_exception_detail(
     detail: Any,
 ) -> tuple[str, str, Any | None]:
     """Extract a stable message/type/details triple from HTTPException detail."""
 
     default_type = "HttpError"
     if isinstance(detail, Mapping):
         # Handle FastAPI-style payloads where the detail already contains an
         # ``error`` structure with message/type/details fields.
         nested_error = detail.get("error")
         if isinstance(nested_error, Mapping):
             message = nested_error.get("message")
             error_type = nested_error.get("type", default_type)
             details = nested_error.get("details")
             extras = {k: v for k, v in detail.items() if k != "error"}
-            if extras:
-                if details is None:
-                    details = extras
-                elif isinstance(details, Mapping):
-                    details = {**extras, **details}
-                else:
-                    details = {"value": details, "extras": extras}
+            details = _merge_details_with_extras(details, extras)
             if message is None:
                 message = str({k: v for k, v in detail.items() if k != "error"})
             return str(message), str(error_type), details
 
         # Generic mapping: look for common keys first.
         message = detail.get("message")
         error_type = detail.get("type", default_type)
         details = detail.get("details")
 
         # Preserve any remaining fields as part of the details payload so that
         # callers do not lose structured context.
         extras = {
             key: value
             for key, value in detail.items()
             if key not in {"message", "type", "details"}
         }
-        if extras:
-            if details is None:
-                details = extras
-            elif isinstance(details, Mapping):
-                # Merge extras with details when both are mapping-like.
-                details = {**extras, **details}
-            else:
-                details = {"value": details, "extras": extras}
+        details = _merge_details_with_extras(details, extras)
 
         if message is None:
             message = str({k: v for k, v in detail.items() if k != "details"})
 
         return str(message), str(error_type), details
 
     # Fallback: treat the detail as a simple string payload.
     return str(detail), default_type, None

64-118: Consider enhancing the docstring.

The one-line docstring is functional but could be expanded to document the three cases the function handles: nested error structures, generic mappings, and fallback for non-mapping details. This would improve code maintainability.

Apply this diff:

 def _normalize_http_exception_detail(
     detail: Any,
 ) -> tuple[str, str, Any | None]:
-    """Extract a stable message/type/details triple from HTTPException detail."""
+    """Extract a stable message/type/details triple from HTTPException detail.
+    
+    Handles three cases:
+    1. FastAPI-style payloads with nested "error" structure
+    2. Generic mappings with message/type/details at top level
+    3. Fallback for non-mapping details (returns string representation)
+    
+    Args:
+        detail: The detail payload from HTTPException
+        
+    Returns:
+        Tuple of (message, type, details) where extras are merged into details
+    """
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c0de8a and 33bcb2c.

📒 Files selected for processing (2)
  • src/core/app/error_handlers.py (4 hunks)
  • tests/unit/core/app/test_error_handlers.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Avoid using emojis in Python code comments or docstrings
Follow PEP 8 and use type hints for all functions
Import order must be: standard library, third-party, then local imports, separated by blank lines
Naming conventions: snake_case for variables and functions; PascalCase for classes
Error handling: use specific exceptions and include meaningful error messages
Prefer f-strings for string formatting

Files:

  • src/core/app/error_handlers.py
  • tests/unit/core/app/test_error_handlers.py
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Catch specific exceptions; avoid broad except Exception blocks
If a broad exception must be caught, log with exc_info=True and re-raise a specific custom exception
Use the most specific exception class from src.core.common.exceptions that accurately describes the error
Provide clear, helpful error messages and include relevant details in the details dictionary of custom exceptions

Files:

  • src/core/app/error_handlers.py
🔇 Additional comments (5)
tests/unit/core/app/test_error_handlers.py (3)

142-171: LGTM! Comprehensive test coverage for mapping normalization.

The test correctly verifies that when HTTPException detail is a mapping with message, type, details, and extra fields, they are properly normalized and merged. The assertion confirms that "hint" is merged into details alongside "field", and status_code is added.


193-220: LGTM! Validates chat completions with nested error structure.

The test properly verifies that structured detail payloads with nested error objects are correctly extracted and normalized for chat completion endpoints. The assertions confirm that message content, error payload, and details are all preserved.


222-258: LGTM! Confirms outer metadata preservation.

This test validates the fix for the past review comment, ensuring that outer metadata (trace_id, correlation) is preserved and merged into details alongside the nested error's details. The assertions confirm all fields are properly merged.

src/core/app/error_handlers.py (2)

5-5: LGTM! Appropriate import for type checking.

The import of Mapping from collections.abc is correct for using isinstance(detail, Mapping) checks in the normalization logic.


138-188: LGTM! Normalization properly integrated.

The handler correctly uses the normalized message, error_type, and extra_details for both chat completion and non-chat endpoints. The logic properly constructs error payloads with the normalized values, and conditionally includes details when present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant