-
Notifications
You must be signed in to change notification settings - Fork 285
Include Exception Properties at FailureDetails via Custom Provider #3215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
aab6c60
646fafd
fd80fbb
b9221af
d11dfe2
c13e054
2c1c8b9
2ec8525
2c15159
433b061
f5a9925
5308af8
78aa185
6444741
0619cbc
e34b063
233fae7
6a7893d
eedf76f
b119a26
8a5f6f3
8437b45
786d0fa
bea2daf
3680ce0
e241200
a4e4047
7fbf7cd
acb4ac8
f645744
56894a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| # The following files were downloaded from branch main at 2025-09-17 01:59:32 UTC | ||
| https://raw.githubusercontent.com/microsoft/durabletask-protobuf/f5745e0d83f608d77871c1894d9260ceaae08967/protos/orchestrator_service.proto | ||
| # The following files were downloaded from branch main at 2025-10-13 23:40:09 UTC | ||
| https://raw.githubusercontent.com/microsoft/durabletask-protobuf/97cf9cf6ac44107b883b0f4ab1dd62ee2332cfd9/protos/orchestrator_service.proto |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,10 @@ | |
| #nullable enable | ||
| using System; | ||
| using System.Buffers; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Globalization; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
|
|
@@ -18,6 +20,8 @@ | |
| using Google.Protobuf; | ||
| using Google.Protobuf.Collections; | ||
| using Google.Protobuf.WellKnownTypes; | ||
| using Newtonsoft.Json; | ||
| using Newtonsoft.Json.Linq; | ||
| using P = Microsoft.DurableTask.Protobuf; | ||
|
|
||
| namespace Microsoft.Azure.WebJobs.Extensions.DurableTask | ||
|
|
@@ -354,7 +358,8 @@ public static string Base64Encode(IMessage message) | |
| failureDetails.ErrorMessage, | ||
| failureDetails.StackTrace, | ||
| GetFailureDetails(failureDetails.InnerFailure), | ||
| failureDetails.IsNonRetriable); | ||
| failureDetails.IsNonRetriable, | ||
| properties: ConvertProperties(failureDetails.Properties)); | ||
| } | ||
|
|
||
| internal static P.TaskFailureDetails? GetFailureDetails(FailureDetails? failureDetails) | ||
|
|
@@ -364,14 +369,24 @@ public static string Base64Encode(IMessage message) | |
| return null; | ||
| } | ||
|
|
||
| return new P.TaskFailureDetails | ||
| var taskFailure = new P.TaskFailureDetails | ||
| { | ||
| ErrorType = failureDetails.ErrorType, | ||
| ErrorMessage = failureDetails.ErrorMessage, | ||
| StackTrace = failureDetails.StackTrace, | ||
| InnerFailure = GetFailureDetails(failureDetails.InnerFailure), | ||
| IsNonRetriable = failureDetails.IsNonRetriable, | ||
| }; | ||
|
|
||
| if (failureDetails.Properties != null) | ||
| { | ||
| foreach (var kvp in failureDetails.Properties) | ||
| { | ||
| taskFailure.Properties[kvp.Key] = ConvertObjectToValue(kvp.Value); | ||
| } | ||
| } | ||
|
|
||
| return taskFailure; | ||
| } | ||
|
|
||
| internal static OrchestrationQuery ToOrchestrationQuery(P.QueryInstancesRequest request) | ||
|
|
@@ -506,7 +521,7 @@ internal static P.PurgeInstancesResponse CreatePurgeInstancesResponse(PurgeResul | |
| { | ||
| TraceParent = operationRequest.TraceContext.TraceParent, | ||
| TraceState = operationRequest.TraceContext.TraceState, | ||
| } | ||
| } | ||
| : null, | ||
| }; | ||
| } | ||
|
|
@@ -571,9 +586,9 @@ internal static P.PurgeInstancesResponse CreatePurgeInstancesResponse(PurgeResul | |
| Name = operationAction.StartNewOrchestration.Name, | ||
| Input = operationAction.StartNewOrchestration.Input, | ||
| InstanceId = operationAction.StartNewOrchestration.InstanceId, | ||
| Version = operationAction.StartNewOrchestration.Version, | ||
| RequestTime = operationAction.StartNewOrchestration.RequestTime?.ToDateTimeOffset(), | ||
| ScheduledStartTime = operationAction.StartNewOrchestration.ScheduledTime?.ToDateTime(), | ||
| Version = operationAction.StartNewOrchestration.Version, | ||
| RequestTime = operationAction.StartNewOrchestration.RequestTime?.ToDateTimeOffset(), | ||
| ScheduledStartTime = operationAction.StartNewOrchestration.ScheduledTime?.ToDateTime(), | ||
| ParentTraceContext = operationAction.StartNewOrchestration.ParentTraceContext != null ? | ||
| new DistributedTraceContext( | ||
| operationAction.StartNewOrchestration.ParentTraceContext.TraceParent, | ||
|
|
@@ -621,46 +636,107 @@ internal static P.PurgeInstancesResponse CreatePurgeInstancesResponse(PurgeResul | |
| } | ||
| } | ||
|
|
||
| internal static MapField<string, Value> ConvertPocoToProtoMap(object? configurations) | ||
| { | ||
| var map = new MapField<string, Value>(); | ||
|
|
||
| if (configurations == null) | ||
| { | ||
| return map; | ||
| } | ||
|
|
||
| System.Type type = configurations.GetType(); | ||
| PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); | ||
|
|
||
| foreach (PropertyInfo property in properties) | ||
| { | ||
| string propertyName = property.Name; | ||
| object? propertyValue = property.GetValue(configurations); | ||
|
|
||
| map[propertyName] = ConvertToProtoValue(propertyValue); | ||
| } | ||
|
|
||
| return map; | ||
| } | ||
|
|
||
| private static Value ConvertToProtoValue(object? value) | ||
| { | ||
| if (value is null) | ||
| { | ||
| return Value.ForNull(); | ||
| } | ||
|
|
||
| return value switch | ||
| { | ||
| string s => Value.ForString(s), | ||
| bool b => Value.ForBool(b), | ||
| int i => Value.ForNumber(i), | ||
| long l => Value.ForNumber(l), | ||
| float f => Value.ForNumber(f), | ||
| double d => Value.ForNumber(d), | ||
| _ => throw new InvalidOperationException($"Unsupported type: {value.GetType()} at durable ProtobufUtils.") | ||
| }; | ||
| internal static IDictionary<string, object?> ConvertProperties(MapField<string, Value> properties) | ||
| { | ||
| return properties.ToDictionary( | ||
| kvp => kvp.Key, | ||
| kvp => ConvertValueToObject(kvp.Value)); | ||
| } | ||
|
|
||
| internal static MapField<string, Value> ConvertPocoToProtoMap(object? configurations) | ||
| { | ||
| var map = new MapField<string, Value>(); | ||
|
|
||
| if (configurations == null) | ||
| { | ||
| return map; | ||
| } | ||
|
|
||
| System.Type type = configurations.GetType(); | ||
| PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); | ||
|
|
||
| foreach (PropertyInfo property in properties) | ||
| { | ||
| string propertyName = property.Name; | ||
| object? propertyValue = property.GetValue(configurations); | ||
|
|
||
| map[propertyName] = ConvertObjectToValue(propertyValue); | ||
| } | ||
|
|
||
| return map; | ||
| } | ||
|
|
||
| internal static Value ConvertObjectToValue(object? obj) | ||
| { | ||
| return obj switch | ||
| { | ||
| null => Value.ForNull(), | ||
| string str => Value.ForString(str), | ||
| bool b => Value.ForBool(b), | ||
| int i => Value.ForNumber(i), | ||
| long l => Value.ForNumber(l), | ||
| float f => Value.ForNumber(f), | ||
| double d => Value.ForNumber(d), | ||
| decimal dec => Value.ForNumber((double)dec), | ||
|
|
||
| // Handle Newtonsoft.Json types (JValue, JArray, JObject) from deserialized protobuf | ||
nytian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| JValue jv => ConvertJValueToValue(jv), | ||
| JArray ja => Value.ForList(ja.Select(ConvertObjectToValue).ToArray()), | ||
| JObject jo => Value.ForStruct(new Struct | ||
| { | ||
| Fields = { jo.Properties().ToDictionary(p => p.Name, p => ConvertObjectToValue(p.Value)) }, | ||
| }), | ||
|
Comment on lines
+683
to
+688
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couple of questions about this
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added these because, in the test case, some nested objects such as lists and dictionaries are serialized and deserialized as JSON values when passed from Worker.Extensions to WebJobs.Extensions. Therefore, we need to include this JSON block here. For Yeah, if they specifically expect a JValue, we might be violating that here — but are there really many such cases? It seems that handling nested structures is more important overall. I’m fine with either approach, though. leave it as it-is for other folks to join the discussion.. |
||
|
|
||
| // For DateTime and DateTimeOffset, serialize to string directly. | ||
| DateTime dt => Value.ForString(dt.ToString("O")), | ||
| DateTimeOffset dto => Value.ForString(dto.ToString("O")), | ||
| IDictionary<string, object?> dict => Value.ForStruct(new Struct | ||
| { | ||
| Fields = { dict.ToDictionary(kvp => kvp.Key, kvp => ConvertObjectToValue(kvp.Value)) }, | ||
| }), | ||
| IEnumerable e => Value.ForList(e.Cast<object?>().Select(ConvertObjectToValue).ToArray()), | ||
|
|
||
| // Fallback: convert unlisted type to string. | ||
| _ => Value.ForString(obj.ToString() ?? string.Empty), | ||
| }; | ||
| } | ||
|
|
||
| internal static Value ConvertJValueToValue(JValue jv) | ||
| { | ||
| return jv.Type switch | ||
| { | ||
| JTokenType.Null => Value.ForNull(), | ||
| JTokenType.String => Value.ForString(jv.Value<string>() ?? string.Empty), | ||
| JTokenType.Boolean => Value.ForBool(jv.Value<bool>()), | ||
| JTokenType.Integer => Value.ForNumber(jv.Value<long>()), | ||
| JTokenType.Float => Value.ForNumber(jv.Value<double>()), | ||
| JTokenType.Date => Value.ForString($"dt:{jv.Value<DateTime>().ToString("O")}"), | ||
| _ => Value.ForString(jv.ToString()), | ||
| }; | ||
| } | ||
|
|
||
| internal static object? ConvertValueToObject(Google.Protobuf.WellKnownTypes.Value value) | ||
| { | ||
| switch (value.KindCase) | ||
| { | ||
| case Google.Protobuf.WellKnownTypes.Value.KindOneofCase.NullValue: | ||
| return null; | ||
| case Google.Protobuf.WellKnownTypes.Value.KindOneofCase.NumberValue: | ||
| return value.NumberValue; | ||
| case Google.Protobuf.WellKnownTypes.Value.KindOneofCase.StringValue: | ||
| return value.StringValue; | ||
| case Google.Protobuf.WellKnownTypes.Value.KindOneofCase.BoolValue: | ||
| return value.BoolValue; | ||
| case Google.Protobuf.WellKnownTypes.Value.KindOneofCase.StructValue: | ||
| return value.StructValue.Fields.ToDictionary( | ||
| pair => pair.Key, | ||
| pair => ConvertValueToObject(pair.Value)); | ||
| case Google.Protobuf.WellKnownTypes.Value.KindOneofCase.ListValue: | ||
| return value.ListValue.Values.Select(ConvertValueToObject).ToList(); | ||
| default: | ||
| // Fallback: serialize the whole value to JSON string | ||
| return JsonConvert.SerializeObject(value); | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.