jason810496 commented on code in PR #63801:
URL: https://github.com/apache/airflow/pull/63801#discussion_r3038055626


##########
airflow-core/src/airflow/api_fastapi/common/exceptions.py:
##########
@@ -122,4 +123,101 @@ def exception_handler(self, request: Request, exc: 
DeserializationError):
         )
 
 
+class ExecutionHTTPException(HTTPException):
+    """
+    HTTPException subclass used by Execution API.
+
+    Enforces consistent error response format containing `reason` and 
`message` keys.
+    """
+
+    def __init__(
+        self,
+        status_code: int,
+        *,
+        reason: str,
+        message: str,
+        extra: dict[str, Any] | None = None,
+        headers: dict[str, str] | None = None,
+    ) -> None:
+        """
+        Initialize with explicit reason/message and optional extra fields.
+
+        detail will be constructed as a dict: {"reason": reason, "message": 
message, **extra}
+        """
+        detail: dict[str, Any] = {"reason": reason, "message": message}
+        if extra:
+            # Do not allow overriding reason/message through extra 
accidentally.
+            for k, v in extra.items():
+                if k not in ("reason", "message"):
+                    detail[k] = v
+        super().__init__(status_code=status_code, detail=detail, 
headers=headers)
+
+
 ERROR_HANDLERS: list[BaseErrorHandler] = [_UniqueConstraintErrorHandler(), 
DagErrorHandler()]
+
+
+# --------------------
+# Global exception normalization utilities and registration
+# --------------------
+
+
+def _ensure_detail_dict(detail: dict[str, Any] | str | None) -> dict[str, Any]:
+    """Normalize detail into a dict with at least reason/message keys."""
+    if isinstance(detail, str) or detail is None:
+        return {"reason": "error", "message": detail or "An error occurred"}
+    if isinstance(detail, dict):
+        # Flatten legacy/nested payloads: {"detail": "..."} -> {"reason": 
"error", "message": "..."}
+        if set(detail.keys()) == {"detail"}:
+            nested_detail = detail["detail"]
+            if isinstance(nested_detail, dict):
+                normalized = dict(nested_detail)
+                normalized.setdefault("reason", "error")
+                normalized.setdefault("message", "An error occurred")
+                return normalized
+            return {"reason": "error", "message": str(nested_detail)}
+
+        # Idempotent path: keep already structured details intact.
+        normalized = dict(detail)
+        normalized.setdefault("reason", "error")
+        normalized.setdefault("message", "An error occurred")
+        return normalized
+    return {"reason": "error", "message": str(detail)}
+
+
+def register_exception_handlers(app: FastAPI) -> None:
+    """
+    Register global and specific exception handlers on the FastAPI app.
+
+    Guarantees JSON error responses carry a `detail` object with `reason` and 
`message`.
+    """
+    # Specific handlers remain in place (e.g., DB unique constraint, Dag 
errors)
+    for handler in ERROR_HANDLERS:
+        app.add_exception_handler(handler.exception_cls, 
handler.exception_handler)  # type: ignore[arg-type]
+
+    # Normalize any HTTPException to have dict detail with reason/message
+    @app.exception_handler(HTTPException)
+    async def _http_exception_handler(request: Request, exc: HTTPException):
+        detail = _ensure_detail_dict(getattr(exc, "detail", None))
+        headers = getattr(exc, "headers", None)
+        return JSONResponse(status_code=exc.status_code, content={"detail": 
detail}, headers=headers)

Review Comment:
   Is it acceptable to introduce RestAPI breaking change for `3.3.0`?
   cc @pierrejeambrun 
   
   Since the normalization for the `HTTPException` is breaking change for users 
who rely on the response body.



##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/variables.py:
##########
@@ -95,6 +100,13 @@ def put_variable(
     team_name: Annotated[str | None, Depends(get_team_name_dep)],
 ):
     """Set an Airflow Variable."""
+    if not variable_key:

Review Comment:
   Same here for rest of the `if not variable_key:` block. 



##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/variables.py:
##########
@@ -67,15 +68,19 @@ def get_variable(
     team_name: Annotated[str | None, Depends(get_team_name_dep)],
 ) -> VariableResponse:
     """Get an Airflow Variable."""
+    if not variable_key:
+        raise ExecutionHTTPException(
+            status.HTTP_404_NOT_FOUND,
+            reason="not_found",
+            message="Not Found",
+        )

Review Comment:
   We already have `variable_key: Annotated[str, Path(min_length=1)]`, is `if 
not variable_key` block necessary?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to