Skip to content

feat: allow catch on any task #80

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ public interface ITaskDefinitionBuilder<TBuilder>
/// <returns>The configured <see cref="ITaskDefinitionBuilder{TBuilder}"/></returns>
TBuilder Then(string directive);

/// <summary>
/// Configures the task to catch defined errors
/// </summary>
/// <param name="setup">An <see cref="Action{T}"/> used to setup the <see cref="ErrorCatcherDefinition"/> to use</param>
/// <returns>The configured <see cref="ITaskDefinitionBuilder{TBuilder}"/></returns>
TBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,4 @@ public interface ITryTaskDefinitionBuilder
/// <returns>The configured <see cref="ITryTaskDefinitionBuilder"/></returns>
ITryTaskDefinitionBuilder Do(Action<ITaskDefinitionMapBuilder> setup);

/// <summary>
/// Configures the task to catch defined errors
/// </summary>
/// <param name="setup">An <see cref="Action{T}"/> used to setup the <see cref="ErrorCatcherDefinition"/> to use</param>
/// <returns>The configured <see cref="ITryTaskDefinitionBuilder"/></returns>
ITryTaskDefinitionBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup);

}
16 changes: 16 additions & 0 deletions src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public abstract class TaskDefinitionBuilder<TBuilder, TDefinition>
/// </summary>
protected string? ThenDirective { get; set; }

/// <summary>
/// Gets/sets the definition of the catch node, if any
/// </summary>
protected ErrorCatcherDefinition? ErrorCatcher { get; set; }

/// <inheritdoc/>
public virtual TBuilder If(string condition)
{
Expand Down Expand Up @@ -126,6 +131,16 @@ public virtual TBuilder Then(string directive)
return (TBuilder)(object)this;
}

/// <inheritdoc/>
public virtual TBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup)
{
ArgumentNullException.ThrowIfNull(setup);
var builder = new ErrorCatcherDefinitionBuilder();
setup(builder);
this.ErrorCatcher = builder.Build();
return (TBuilder)(object)this;
}

/// <summary>
/// Applies the configuration common to all types of tasks
/// </summary>
Expand All @@ -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;
}

Expand Down
14 changes: 0 additions & 14 deletions src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ public class TryTaskDefinitionBuilder
/// </summary>
protected Map<string, TaskDefinition>? TryTasks { get; set; }

/// <summary>
/// Gets/sets the definition of the error catcher to use
/// </summary>
protected ErrorCatcherDefinition? ErrorCatcher { get; set; }

/// <inheritdoc/>
public virtual ITryTaskDefinitionBuilder Do(Action<ITaskDefinitionMapBuilder> setup)
{
Expand All @@ -40,15 +35,6 @@ public virtual ITryTaskDefinitionBuilder Do(Action<ITaskDefinitionMapBuilder> se
return this;
}

/// <inheritdoc/>
public virtual ITryTaskDefinitionBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup)
{
ArgumentNullException.ThrowIfNull(setup);
var builder = new ErrorCatcherDefinitionBuilder();
this.ErrorCatcher = builder.Build();
return this;
}

/// <inheritdoc/>
public override TryTaskDefinition Build()
{
Expand Down
5 changes: 5 additions & 0 deletions src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object>? Metadata { get; set; }

/// <summary>
/// Gets/sets the object used to define the errors to catch
/// </summary>
[DataMember(Name = "catch", Order = 16), JsonPropertyName("catch"), JsonPropertyOrder(16), YamlMember(Alias = "catch", Order = 16)]
public virtual ErrorCatcherDefinition? Catch { get; set; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ public record TryTaskDefinition
/// </summary>
[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; }

}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Represents the <see cref="IValidator"/> used to validate <see cref="ErrorCatcherDefinition"/>s
/// </summary>
public class ErrorCatcherDefinitionValidator
: AbstractValidator<ErrorCatcherDefinition>
{

/// <inheritdoc/>
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));
}

/// <summary>
/// Gets the current <see cref="IServiceProvider"/>
/// </summary>
protected IServiceProvider ServiceProvider { get; }

/// <summary>
/// Gets the configured reusable components
/// </summary>
protected ComponentDefinitionCollection? Components { get; }

/// <summary>
/// Determines whether the error catcher has valid handlers (either 'do' tasks or a retry policy)
/// </summary>
/// <param name="catcher">The error catcher to validate</param>
/// <returns>A boolean indicating whether the error catcher has valid handlers</returns>
protected virtual bool HaveValidHandlers(ErrorCatcherDefinition catcher)
{
return (catcher.Do != null && catcher.Do.Count > 0) || catcher.Retry != null || !string.IsNullOrWhiteSpace(catcher.RetryReference);
}

/// <summary>
/// Determines whether or not the specified retry policy is defined
/// </summary>
/// <param name="name">The name of the retry policy to check</param>
/// <returns>A boolean indicating whether or not the specified retry policy is defined</returns>
protected virtual bool ReferenceAnExistingRetryPolicy(string name) => this.Components?.Retries?.ContainsKey(name) == true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading