diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITaskDefinitionBuilder.cs index b7dac41..c38f542 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITaskDefinitionBuilder.cs @@ -92,6 +92,12 @@ public interface ITaskDefinitionBuilder /// The configured TBuilder Then(string directive); + /// + /// Configures the task to catch defined errors + /// + /// An used to setup the to use + /// The configured + TBuilder Catch(Action setup); } /// diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITryTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITryTaskDefinitionBuilder.cs index 7105507..89c9372 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITryTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITryTaskDefinitionBuilder.cs @@ -27,11 +27,4 @@ public interface ITryTaskDefinitionBuilder /// The configured ITryTaskDefinitionBuilder Do(Action setup); - /// - /// Configures the task to catch defined errors - /// - /// An used to setup the to use - /// The configured - ITryTaskDefinitionBuilder Catch(Action setup); - } diff --git a/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs index 697e67b..5c75346 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs @@ -54,6 +54,11 @@ public abstract class TaskDefinitionBuilder /// protected string? ThenDirective { get; set; } + /// + /// Gets/sets the definition of the catch node, if any + /// + protected ErrorCatcherDefinition? ErrorCatcher { get; set; } + /// public virtual TBuilder If(string condition) { @@ -126,6 +131,16 @@ public virtual TBuilder Then(string directive) return (TBuilder)(object)this; } + /// + public virtual TBuilder Catch(Action setup) + { + ArgumentNullException.ThrowIfNull(setup); + var builder = new ErrorCatcherDefinitionBuilder(); + setup(builder); + this.ErrorCatcher = builder.Build(); + return (TBuilder)(object)this; + } + /// /// Applies the configuration common to all types of tasks /// @@ -143,6 +158,7 @@ protected virtual TDefinition Configure(TDefinition definition) definition.Input = this.Input; definition.Output = this.Output; definition.Export = this.Export; + definition.Catch = this.ErrorCatcher; return definition; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs index 2eb9566..9f96034 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs @@ -25,11 +25,6 @@ public class TryTaskDefinitionBuilder /// protected Map? TryTasks { get; set; } - /// - /// Gets/sets the definition of the error catcher to use - /// - protected ErrorCatcherDefinition? ErrorCatcher { get; set; } - /// public virtual ITryTaskDefinitionBuilder Do(Action setup) { @@ -40,15 +35,6 @@ public virtual ITryTaskDefinitionBuilder Do(Action se return this; } - /// - public virtual ITryTaskDefinitionBuilder Catch(Action setup) - { - ArgumentNullException.ThrowIfNull(setup); - var builder = new ErrorCatcherDefinitionBuilder(); - this.ErrorCatcher = builder.Build(); - return this; - } - /// public override TryTaskDefinition Build() { diff --git a/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs index f92e142..ce8f834 100644 --- a/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs @@ -99,5 +99,10 @@ public virtual string? TimeoutReference [DataMember(Name = "metadata", Order = 15), JsonPropertyName("metadata"), JsonPropertyOrder(15), YamlMember(Alias = "metadata", Order = 15)] public virtual EquatableDictionary? Metadata { get; set; } + /// + /// Gets/sets the object used to define the errors to catch + /// + [DataMember(Name = "catch", Order = 16), JsonPropertyName("catch"), JsonPropertyOrder(16), YamlMember(Alias = "catch", Order = 16)] + public virtual ErrorCatcherDefinition? Catch { get; set; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs index fc12c88..33a14ce 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs @@ -37,6 +37,6 @@ public record TryTaskDefinition /// [Required] [DataMember(Name = "catch", Order = 2), JsonPropertyName("catch"), JsonPropertyOrder(2), YamlMember(Alias = "catch", Order = 2)] - public required virtual ErrorCatcherDefinition Catch { get; set; } + public override ErrorCatcherDefinition? Catch { get; set; } } diff --git a/src/ServerlessWorkflow.Sdk/Validation/ErrorCatcherDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/ErrorCatcherDefinitionValidator.cs new file mode 100644 index 0000000..234c90f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Validation/ErrorCatcherDefinitionValidator.cs @@ -0,0 +1,69 @@ +// Copyright © 2024-Present The Serverless Workflow Specification 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. + +using FluentValidation; +using ServerlessWorkflow.Sdk.Models; +using ServerlessWorkflow.Sdk.Properties; + +namespace ServerlessWorkflow.Sdk.Validation; + +/// +/// Represents the used to validate s +/// +public class ErrorCatcherDefinitionValidator + : AbstractValidator +{ + + /// + public ErrorCatcherDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) + { + this.ServiceProvider = serviceProvider; + this.Components = components; + this.RuleFor(e => e) + .Must(HaveValidHandlers) + .WithMessage("The catch node must define either a 'do' section or a retry policy"); + this.RuleForEach(e => e.Do) + .SetValidator(e => new TaskMapEntryValidator(this.ServiceProvider, this.Components, e.Do?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))) + .When(e => e.Do != null); + this.RuleFor(e => e.RetryReference!) + .Must(ReferenceAnExistingRetryPolicy) + .When(e => !string.IsNullOrWhiteSpace(e.RetryReference)); + } + + /// + /// Gets the current + /// + protected IServiceProvider ServiceProvider { get; } + + /// + /// Gets the configured reusable components + /// + protected ComponentDefinitionCollection? Components { get; } + + /// + /// Determines whether the error catcher has valid handlers (either 'do' tasks or a retry policy) + /// + /// The error catcher to validate + /// A boolean indicating whether the error catcher has valid handlers + protected virtual bool HaveValidHandlers(ErrorCatcherDefinition catcher) + { + return (catcher.Do != null && catcher.Do.Count > 0) || catcher.Retry != null || !string.IsNullOrWhiteSpace(catcher.RetryReference); + } + + /// + /// Determines whether or not the specified retry policy is defined + /// + /// The name of the retry policy to check + /// A boolean indicating whether or not the specified retry policy is defined + protected virtual bool ReferenceAnExistingRetryPolicy(string name) => this.Components?.Retries?.ContainsKey(name) == true; +} diff --git a/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs index 1d93519..12b68ef 100644 --- a/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs +++ b/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs @@ -39,6 +39,9 @@ public TaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefini .Must(ReferenceAnExistingTimeout) .When(t => !string.IsNullOrWhiteSpace(t.TimeoutReference)) .WithMessage(ValidationErrors.UndefinedTimeout); + this.RuleFor(t => t.Catch!) + .SetValidator(t => new ErrorCatcherDefinitionValidator(this.ServiceProvider, this.Components)) + .When(t => t.Catch != null); this.When(t => t is CallTaskDefinition, () => { this.RuleFor(t => (CallTaskDefinition)t)