Skip to content

Commit 42f29b7

Browse files
committed
feat: allow catch on any task
1 parent bba3566 commit 42f29b7

File tree

8 files changed

+100
-22
lines changed

8 files changed

+100
-22
lines changed

src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITaskDefinitionBuilder.cs

+6
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ public interface ITaskDefinitionBuilder<TBuilder>
9292
/// <returns>The configured <see cref="ITaskDefinitionBuilder{TBuilder}"/></returns>
9393
TBuilder Then(string directive);
9494

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

97103
/// <summary>

src/ServerlessWorkflow.Sdk.Builders/Interfaces/ITryTaskDefinitionBuilder.cs

-7
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,4 @@ public interface ITryTaskDefinitionBuilder
2727
/// <returns>The configured <see cref="ITryTaskDefinitionBuilder"/></returns>
2828
ITryTaskDefinitionBuilder Do(Action<ITaskDefinitionMapBuilder> setup);
2929

30-
/// <summary>
31-
/// Configures the task to catch defined errors
32-
/// </summary>
33-
/// <param name="setup">An <see cref="Action{T}"/> used to setup the <see cref="ErrorCatcherDefinition"/> to use</param>
34-
/// <returns>The configured <see cref="ITryTaskDefinitionBuilder"/></returns>
35-
ITryTaskDefinitionBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup);
36-
3730
}

src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs

+16
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ public abstract class TaskDefinitionBuilder<TBuilder, TDefinition>
5454
/// </summary>
5555
protected string? ThenDirective { get; set; }
5656

57+
/// <summary>
58+
/// Gets/sets the definition of the catch node, if any
59+
/// </summary>
60+
protected ErrorCatcherDefinition? ErrorCatcher { get; set; }
61+
5762
/// <inheritdoc/>
5863
public virtual TBuilder If(string condition)
5964
{
@@ -126,6 +131,16 @@ public virtual TBuilder Then(string directive)
126131
return (TBuilder)(object)this;
127132
}
128133

134+
/// <inheritdoc/>
135+
public virtual TBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup)
136+
{
137+
ArgumentNullException.ThrowIfNull(setup);
138+
var builder = new ErrorCatcherDefinitionBuilder();
139+
setup(builder);
140+
this.ErrorCatcher = builder.Build();
141+
return (TBuilder)(object)this;
142+
}
143+
129144
/// <summary>
130145
/// Applies the configuration common to all types of tasks
131146
/// </summary>
@@ -143,6 +158,7 @@ protected virtual TDefinition Configure(TDefinition definition)
143158
definition.Input = this.Input;
144159
definition.Output = this.Output;
145160
definition.Export = this.Export;
161+
definition.Catch = this.ErrorCatcher;
146162
return definition;
147163
}
148164

src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs

-14
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ public class TryTaskDefinitionBuilder
2525
/// </summary>
2626
protected Map<string, TaskDefinition>? TryTasks { get; set; }
2727

28-
/// <summary>
29-
/// Gets/sets the definition of the error catcher to use
30-
/// </summary>
31-
protected ErrorCatcherDefinition? ErrorCatcher { get; set; }
32-
3328
/// <inheritdoc/>
3429
public virtual ITryTaskDefinitionBuilder Do(Action<ITaskDefinitionMapBuilder> setup)
3530
{
@@ -40,15 +35,6 @@ public virtual ITryTaskDefinitionBuilder Do(Action<ITaskDefinitionMapBuilder> se
4035
return this;
4136
}
4237

43-
/// <inheritdoc/>
44-
public virtual ITryTaskDefinitionBuilder Catch(Action<IErrorCatcherDefinitionBuilder> setup)
45-
{
46-
ArgumentNullException.ThrowIfNull(setup);
47-
var builder = new ErrorCatcherDefinitionBuilder();
48-
this.ErrorCatcher = builder.Build();
49-
return this;
50-
}
51-
5238
/// <inheritdoc/>
5339
public override TryTaskDefinition Build()
5440
{

src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs

+5
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,10 @@ public virtual string? TimeoutReference
9999
[DataMember(Name = "metadata", Order = 15), JsonPropertyName("metadata"), JsonPropertyOrder(15), YamlMember(Alias = "metadata", Order = 15)]
100100
public virtual EquatableDictionary<string, object>? Metadata { get; set; }
101101

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

src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ public record TryTaskDefinition
3737
/// </summary>
3838
[Required]
3939
[DataMember(Name = "catch", Order = 2), JsonPropertyName("catch"), JsonPropertyOrder(2), YamlMember(Alias = "catch", Order = 2)]
40-
public required virtual ErrorCatcherDefinition Catch { get; set; }
40+
public override ErrorCatcherDefinition? Catch { get; set; }
4141

4242
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright © 2024-Present The Serverless Workflow Specification Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
using FluentValidation;
15+
using ServerlessWorkflow.Sdk.Models;
16+
using ServerlessWorkflow.Sdk.Properties;
17+
18+
namespace ServerlessWorkflow.Sdk.Validation;
19+
20+
/// <summary>
21+
/// Represents the <see cref="IValidator"/> used to validate <see cref="ErrorCatcherDefinition"/>s
22+
/// </summary>
23+
public class ErrorCatcherDefinitionValidator
24+
: AbstractValidator<ErrorCatcherDefinition>
25+
{
26+
27+
/// <inheritdoc/>
28+
public ErrorCatcherDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components)
29+
{
30+
this.ServiceProvider = serviceProvider;
31+
this.Components = components;
32+
this.RuleFor(e => e)
33+
.Must(HaveValidHandlers)
34+
.WithMessage("The catch node must define either a 'do' section or a retry policy");
35+
this.RuleForEach(e => e.Do)
36+
.SetValidator(e => new TaskMapEntryValidator(this.ServiceProvider, this.Components, e.Do?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)))
37+
.When(e => e.Do != null);
38+
this.RuleFor(e => e.RetryReference!)
39+
.Must(ReferenceAnExistingRetryPolicy)
40+
.When(e => !string.IsNullOrWhiteSpace(e.RetryReference));
41+
}
42+
43+
/// <summary>
44+
/// Gets the current <see cref="IServiceProvider"/>
45+
/// </summary>
46+
protected IServiceProvider ServiceProvider { get; }
47+
48+
/// <summary>
49+
/// Gets the configured reusable components
50+
/// </summary>
51+
protected ComponentDefinitionCollection? Components { get; }
52+
53+
/// <summary>
54+
/// Determines whether the error catcher has valid handlers (either 'do' tasks or a retry policy)
55+
/// </summary>
56+
/// <param name="catcher">The error catcher to validate</param>
57+
/// <returns>A boolean indicating whether the error catcher has valid handlers</returns>
58+
protected virtual bool HaveValidHandlers(ErrorCatcherDefinition catcher)
59+
{
60+
return (catcher.Do != null && catcher.Do.Count > 0) || catcher.Retry != null || !string.IsNullOrWhiteSpace(catcher.RetryReference);
61+
}
62+
63+
/// <summary>
64+
/// Determines whether or not the specified retry policy is defined
65+
/// </summary>
66+
/// <param name="name">The name of the retry policy to check</param>
67+
/// <returns>A boolean indicating whether or not the specified retry policy is defined</returns>
68+
protected virtual bool ReferenceAnExistingRetryPolicy(string name) => this.Components?.Retries?.ContainsKey(name) == true;
69+
}

src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public TaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefini
3939
.Must(ReferenceAnExistingTimeout)
4040
.When(t => !string.IsNullOrWhiteSpace(t.TimeoutReference))
4141
.WithMessage(ValidationErrors.UndefinedTimeout);
42+
this.RuleFor(t => t.Catch!)
43+
.SetValidator(t => new ErrorCatcherDefinitionValidator(this.ServiceProvider, this.Components))
44+
.When(t => t.Catch != null);
4245
this.When(t => t is CallTaskDefinition, () =>
4346
{
4447
this.RuleFor(t => (CallTaskDefinition)t)

0 commit comments

Comments
 (0)