22// Licensed under the MIT License.
33package com .microsoft .durabletask ;
44
5+ import com .google .protobuf .NullValue ;
56import com .google .protobuf .StringValue ;
7+ import com .google .protobuf .Value ;
68import com .microsoft .durabletask .implementation .protobuf .OrchestratorService .TaskFailureDetails ;
79
810import javax .annotation .Nonnull ;
911import javax .annotation .Nullable ;
12+ import java .util .Collections ;
13+ import java .util .HashMap ;
14+ import java .util .Map ;
1015
1116/**
1217 * Class that represents the details of a task failure.
@@ -20,29 +25,50 @@ public final class FailureDetails {
2025 private final String errorMessage ;
2126 private final String stackTrace ;
2227 private final boolean isNonRetriable ;
28+ private final FailureDetails innerFailure ;
29+ private final Map <String , Object > properties ;
2330
2431 FailureDetails (
2532 String errorType ,
2633 @ Nullable String errorMessage ,
2734 @ Nullable String errorDetails ,
2835 boolean isNonRetriable ) {
36+ this (errorType , errorMessage , errorDetails , isNonRetriable , null , null );
37+ }
38+
39+ FailureDetails (
40+ String errorType ,
41+ @ Nullable String errorMessage ,
42+ @ Nullable String errorDetails ,
43+ boolean isNonRetriable ,
44+ @ Nullable FailureDetails innerFailure ,
45+ @ Nullable Map <String , Object > properties ) {
2946 this .errorType = errorType ;
3047 this .stackTrace = errorDetails ;
3148
3249 // Error message can be null for things like NullPointerException but the gRPC contract doesn't allow null
3350 this .errorMessage = errorMessage != null ? errorMessage : "" ;
3451 this .isNonRetriable = isNonRetriable ;
52+ this .innerFailure = innerFailure ;
53+ this .properties = properties != null ? Collections .unmodifiableMap (new HashMap <>(properties )) : null ;
3554 }
3655
3756 FailureDetails (Exception exception ) {
38- this (exception .getClass ().getName (), exception .getMessage (), getFullStackTrace (exception ), false );
57+ this (exception .getClass ().getName (),
58+ exception .getMessage (),
59+ getFullStackTrace (exception ),
60+ false ,
61+ exception .getCause () != null ? fromExceptionRecursive (exception .getCause ()) : null ,
62+ null );
3963 }
4064
4165 FailureDetails (TaskFailureDetails proto ) {
4266 this (proto .getErrorType (),
4367 proto .getErrorMessage (),
4468 proto .getStackTrace ().getValue (),
45- proto .getIsNonRetriable ());
69+ proto .getIsNonRetriable (),
70+ proto .hasInnerFailure () ? new FailureDetails (proto .getInnerFailure ()) : null ,
71+ convertProtoProperties (proto .getPropertiesMap ()));
4672 }
4773
4874 /**
@@ -86,6 +112,28 @@ public boolean isNonRetriable() {
86112 return this .isNonRetriable ;
87113 }
88114
115+ /**
116+ * Gets the inner failure that caused this failure, or {@code null} if there is no inner cause.
117+ *
118+ * @return the inner {@code FailureDetails} or {@code null}
119+ */
120+ @ Nullable
121+ public FailureDetails getInnerFailure () {
122+ return this .innerFailure ;
123+ }
124+
125+ /**
126+ * Gets additional properties associated with the exception, or {@code null} if no properties are available.
127+ * <p>
128+ * The returned map is unmodifiable.
129+ *
130+ * @return an unmodifiable map of property names to values, or {@code null}
131+ */
132+ @ Nullable
133+ public Map <String , Object > getProperties () {
134+ return this .properties ;
135+ }
136+
89137 /**
90138 * Returns {@code true} if the task failure was provided by the specified exception type, otherwise {@code false}.
91139 * <p>
@@ -112,6 +160,11 @@ public boolean isCausedBy(Class<? extends Exception> exceptionClass) {
112160 }
113161 }
114162
163+ @ Override
164+ public String toString () {
165+ return this .errorType + ": " + this .errorMessage ;
166+ }
167+
115168 static String getFullStackTrace (Throwable e ) {
116169 StackTraceElement [] elements = e .getStackTrace ();
117170
@@ -124,10 +177,88 @@ static String getFullStackTrace(Throwable e) {
124177 }
125178
126179 TaskFailureDetails toProto () {
127- return TaskFailureDetails .newBuilder ()
180+ TaskFailureDetails . Builder builder = TaskFailureDetails .newBuilder ()
128181 .setErrorType (this .getErrorType ())
129182 .setErrorMessage (this .getErrorMessage ())
130183 .setStackTrace (StringValue .of (this .getStackTrace () != null ? this .getStackTrace () : "" ))
131- .build ();
184+ .setIsNonRetriable (this .isNonRetriable );
185+
186+ if (this .innerFailure != null ) {
187+ builder .setInnerFailure (this .innerFailure .toProto ());
188+ }
189+
190+ if (this .properties != null ) {
191+ builder .putAllProperties (convertToProtoProperties (this .properties ));
192+ }
193+
194+ return builder .build ();
195+ }
196+
197+ @ Nullable
198+ private static FailureDetails fromExceptionRecursive (@ Nullable Throwable exception ) {
199+ if (exception == null ) {
200+ return null ;
201+ }
202+ return new FailureDetails (
203+ exception .getClass ().getName (),
204+ exception .getMessage (),
205+ getFullStackTrace (exception ),
206+ false ,
207+ exception .getCause () != null ? fromExceptionRecursive (exception .getCause ()) : null ,
208+ null );
209+ }
210+
211+ @ Nullable
212+ private static Map <String , Object > convertProtoProperties (Map <String , Value > protoProperties ) {
213+ if (protoProperties == null || protoProperties .isEmpty ()) {
214+ return null ;
215+ }
216+
217+ Map <String , Object > result = new HashMap <>();
218+ for (Map .Entry <String , Value > entry : protoProperties .entrySet ()) {
219+ result .put (entry .getKey (), convertProtoValue (entry .getValue ()));
220+ }
221+ return result ;
222+ }
223+
224+ @ Nullable
225+ private static Object convertProtoValue (Value value ) {
226+ if (value == null ) {
227+ return null ;
228+ }
229+ switch (value .getKindCase ()) {
230+ case NULL_VALUE :
231+ return null ;
232+ case NUMBER_VALUE :
233+ return value .getNumberValue ();
234+ case STRING_VALUE :
235+ return value .getStringValue ();
236+ case BOOL_VALUE :
237+ return value .getBoolValue ();
238+ default :
239+ return value .toString ();
240+ }
241+ }
242+
243+ private static Map <String , Value > convertToProtoProperties (Map <String , Object > properties ) {
244+ Map <String , Value > result = new HashMap <>();
245+ for (Map .Entry <String , Object > entry : properties .entrySet ()) {
246+ result .put (entry .getKey (), convertToProtoValue (entry .getValue ()));
247+ }
248+ return result ;
249+ }
250+
251+ private static Value convertToProtoValue (@ Nullable Object obj ) {
252+ if (obj == null ) {
253+ return Value .newBuilder ().setNullValue (NullValue .NULL_VALUE ).build ();
254+ } else if (obj instanceof Number ) {
255+ return Value .newBuilder ().setNumberValue (((Number ) obj ).doubleValue ()).build ();
256+ } else if (obj instanceof Boolean ) {
257+ return Value .newBuilder ().setBoolValue ((Boolean ) obj ).build ();
258+ } else if (obj instanceof String ) {
259+ return Value .newBuilder ().setStringValue ((String ) obj ).build ();
260+ } else {
261+ return Value .newBuilder ().setStringValue (obj .toString ()).build ();
262+ }
132263 }
133- }
264+ }
0 commit comments