diff --git a/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs b/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs index d136e74cc..5313c1f3b 100644 --- a/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs +++ b/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs @@ -39,7 +39,7 @@ public override async Task RunAsync(WorkflowActivityContext context, Pay this.logger.LogInformation( "Payment for request ID '{requestId}' could not be processed. Insufficient inventory.", req.RequestId); - throw new InvalidOperationException(); + throw new InvalidOperationException("Not enough inventory!"); } // Update the statestore with the new amount of the item diff --git a/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs b/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs index faed132e3..4974cb997 100644 --- a/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs +++ b/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs @@ -1,5 +1,4 @@ using Dapr.Workflow; -using DurableTask.Core.Exceptions; using WorkflowConsoleApp.Activities; using WorkflowConsoleApp.Models; @@ -44,12 +43,12 @@ await context.CallActivityAsync( nameof(UpdateInventoryActivity), new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost)); } - catch (TaskFailedException) + catch (WorkflowTaskFailedException e) { - // Let them know their payment was processed + // Let them know their payment processing failed await context.CallActivityAsync( nameof(NotifyActivity), - new Notification($"Order {orderId} Failed! You are now getting a refund")); + new Notification($"Order {orderId} Failed! Details: {e.FailureDetails.ErrorMessage}")); return new OrderResult(Processed: false); } diff --git a/src/Dapr.Workflow/DaprWorkflowContext.cs b/src/Dapr.Workflow/DaprWorkflowContext.cs index d78b0e2df..3faa6c024 100644 --- a/src/Dapr.Workflow/DaprWorkflowContext.cs +++ b/src/Dapr.Workflow/DaprWorkflowContext.cs @@ -37,12 +37,12 @@ internal DaprWorkflowContext(TaskOrchestrationContext innerContext) public override Task CallActivityAsync(string name, object? input = null, TaskOptions? options = null) { - return this.innerContext.CallActivityAsync(name, input, options); + return WrapExceptions(this.innerContext.CallActivityAsync(name, input, options)); } public override Task CallActivityAsync(string name, object? input = null, TaskOptions? options = null) { - return this.innerContext.CallActivityAsync(name, input, options); + return WrapExceptions(this.innerContext.CallActivityAsync(name, input, options)); } public override Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = default) @@ -77,12 +77,12 @@ public override void SetCustomStatus(object? customStatus) public override Task CallChildWorkflowAsync(string workflowName, object? input = null, TaskOptions? options = null) { - return this.innerContext.CallSubOrchestratorAsync(workflowName, input, options); + return WrapExceptions(this.innerContext.CallSubOrchestratorAsync(workflowName, input, options)); } public override Task CallChildWorkflowAsync(string workflowName, object? input = null, TaskOptions? options = null) { - return this.innerContext.CallSubOrchestratorAsync(workflowName, input, options); + return WrapExceptions(this.innerContext.CallSubOrchestratorAsync(workflowName, input, options)); } public override void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true) @@ -94,5 +94,31 @@ public override Guid NewGuid() { return this.innerContext.NewGuid(); } + + static async Task WrapExceptions(Task task) + { + try + { + await task; + } + catch (TaskFailedException ex) + { + var details = new WorkflowTaskFailureDetails(ex.FailureDetails); + throw new WorkflowTaskFailedException(ex.Message, details); + } + } + + static async Task WrapExceptions(Task task) + { + try + { + return await task; + } + catch (TaskFailedException ex) + { + var details = new WorkflowTaskFailureDetails(ex.FailureDetails); + throw new WorkflowTaskFailedException(ex.Message, details); + } + } } } diff --git a/src/Dapr.Workflow/WorkflowContext.cs b/src/Dapr.Workflow/WorkflowContext.cs index f6b7836b8..3928a2a7d 100644 --- a/src/Dapr.Workflow/WorkflowContext.cs +++ b/src/Dapr.Workflow/WorkflowContext.cs @@ -97,9 +97,9 @@ public abstract class WorkflowContext /// /// Thrown if the calling thread is not the workflow dispatch thread. /// - /// + /// /// The activity failed with an unhandled exception. The details of the failure can be found in the - /// property. + /// property. /// public virtual Task CallActivityAsync(string name, object? input = null, TaskOptions? options = null) { @@ -252,9 +252,9 @@ public virtual Task CreateTimer(TimeSpan delay, CancellationToken cancellationTo /// /// Thrown if the calling thread is not the workflow dispatch thread. /// - /// + /// /// The child workflow failed with an unhandled exception. The details of the failure can be found in the - /// property. + /// property. /// public virtual Task CallChildWorkflowAsync(string workflowName, object? input = null, TaskOptions? options = null) { diff --git a/src/Dapr.Workflow/WorkflowTaskFailedException.cs b/src/Dapr.Workflow/WorkflowTaskFailedException.cs new file mode 100644 index 000000000..575947a3a --- /dev/null +++ b/src/Dapr.Workflow/WorkflowTaskFailedException.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + + /// + /// Exception type for Dapr Workflow task failures. + /// + public class WorkflowTaskFailedException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The exception message. + /// Details about the failure. + public WorkflowTaskFailedException(string message, WorkflowTaskFailureDetails failureDetails) + : base(message) + { + this.FailureDetails = failureDetails ?? throw new ArgumentNullException(nameof(failureDetails)); + } + + /// + /// Gets more information about the underlying workflow task failure. + /// + public WorkflowTaskFailureDetails FailureDetails { get; } + } +}