|
3 | 3 |
|
4 | 4 | import traceback |
5 | 5 | from datetime import datetime, timezone |
| 6 | +from typing import Any, cast |
6 | 7 |
|
7 | 8 | from google.protobuf import timestamp_pb2, wrappers_pb2 |
8 | 9 |
|
@@ -136,6 +137,59 @@ def new_failure_details(ex: Exception, _visited: set[int] | None = None) -> pb.T |
136 | 137 | ) |
137 | 138 |
|
138 | 139 |
|
| 140 | +def _failure_details_from_core_dict(fd: dict[str, Any]) -> pb.TaskFailureDetails: |
| 141 | + """Convert a serialized DurableTask.Core ``FailureDetails`` dict to protobuf.""" |
| 142 | + inner = fd.get("InnerFailure") |
| 143 | + stack_trace = fd.get("StackTrace") |
| 144 | + return pb.TaskFailureDetails( |
| 145 | + errorType=str(fd.get("ErrorType") or ""), |
| 146 | + errorMessage=str(fd.get("ErrorMessage") or ""), |
| 147 | + stackTrace=get_string_value(str(stack_trace) if stack_trace is not None else None), |
| 148 | + innerFailure=_failure_details_from_core_dict(cast(dict[str, Any], inner)) if isinstance(inner, dict) else None, |
| 149 | + isNonRetriable=bool(fd.get("IsNonRetriable", False)), |
| 150 | + ) |
| 151 | + |
| 152 | + |
| 153 | +def entity_response_failure_details( |
| 154 | + response: dict[str, Any], |
| 155 | + error_content: Any = None) -> pb.TaskFailureDetails: |
| 156 | + """Build failure details from a failed legacy-protocol entity ``ResponseMessage``. |
| 157 | +
|
| 158 | + Call this only for responses that :func:`is_entity_error_response` reports as |
| 159 | + failures. In the WebJobs "old protocol" ``ResponseMessage`` (see |
| 160 | + ``EntityScheduler/ResponseMessage.cs``), a failed operation serializes the |
| 161 | + human-readable content into ``result`` while ``exceptionType`` carries only |
| 162 | + the exception's type name (a presence marker) -- it is *not* the message. |
| 163 | + This mirrors ``azure-functions-durable-python`` / ``-js``, which read the |
| 164 | + message from ``result`` and ignore ``exceptionType``'s value. |
| 165 | +
|
| 166 | + Parameters |
| 167 | + ---------- |
| 168 | + response: |
| 169 | + The deserialized ``ResponseMessage`` dict. |
| 170 | + error_content: |
| 171 | + The already-deserialized ``result`` payload, used as the failure |
| 172 | + message. A structured ``failureDetails`` object, if present, takes |
| 173 | + precedence (current-protocol shape). |
| 174 | + """ |
| 175 | + failure_details = response.get("failureDetails") |
| 176 | + if isinstance(failure_details, dict): |
| 177 | + return _failure_details_from_core_dict(cast(dict[str, Any], failure_details)) |
| 178 | + error_type = str(response.get("exceptionType") or "") |
| 179 | + error_message = "" if error_content is None else str(error_content) |
| 180 | + return pb.TaskFailureDetails(errorType=error_type, errorMessage=error_message) |
| 181 | + |
| 182 | + |
| 183 | +def is_entity_error_response(response: dict[str, Any]) -> bool: |
| 184 | + """Return ``True`` if a legacy-protocol entity ``ResponseMessage`` is a failure. |
| 185 | +
|
| 186 | + In the WebJobs "old protocol" a failed operation is marked by the presence of |
| 187 | + an ``exceptionType`` field (successful responses omit it). Current-protocol |
| 188 | + payloads may instead carry a structured ``failureDetails`` object. |
| 189 | + """ |
| 190 | + return "exceptionType" in response or isinstance(response.get("failureDetails"), dict) |
| 191 | + |
| 192 | + |
139 | 193 | def new_event_sent_event(event_id: int, instance_id: str, input: str): |
140 | 194 | return pb.HistoryEvent( |
141 | 195 | eventId=event_id, |
|
0 commit comments