Skip to content

Commit 213e696

Browse files
committed
feat(Sdk): Added services to fluently validate the DSL
fix(Sdk): Added referenceable timeouts and retries, thus addressing serverlessworkflow/specification#1002 Signed-off-by: Charles d'Avernas <[email protected]>
1 parent 36abdaa commit 213e696

27 files changed

+1191
-15
lines changed

Diff for: src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs

+6
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,10 @@ public record ComponentDefinitionCollection
5656
[DataMember(Name = "secrets", Order = 6), JsonPropertyName("secrets"), JsonPropertyOrder(6), YamlMember(Alias = "secrets", Order = 6)]
5757
public virtual EquatableList<string>? Secrets { get; set; }
5858

59+
/// <summary>
60+
/// Gets/sets a name/value mapping of the workflow's reusable timeouts
61+
/// </summary>
62+
[DataMember(Name = "timeouts", Order = 7), JsonPropertyName("timeouts"), JsonPropertyOrder(7), YamlMember(Alias = "timeouts", Order = 7)]
63+
public virtual EquatableDictionary<string, TimeoutDefinition>? Timeouts { get; set; }
64+
5965
}

Diff for: src/ServerlessWorkflow.Sdk/Models/ErrorCatcherDefinition.cs

+32-3
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,39 @@ public record ErrorCatcherDefinition
4545
public virtual string? ExceptWhen { get; set; }
4646

4747
/// <summary>
48-
/// Gets/sets the endpoint's retry policy, if any
48+
/// Gets/sets the retry policy to use, if any
4949
/// </summary>
50-
[DataMember(Name = "retry", Order = 5), JsonPropertyName("retry"), JsonPropertyOrder(5), YamlMember(Alias = "retry", Order = 5)]
51-
public virtual RetryPolicyDefinition? Retry { get; set; }
50+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
51+
public virtual RetryPolicyDefinition? Retry
52+
{
53+
get => this.RetryValue.T1Value;
54+
set
55+
{
56+
ArgumentNullException.ThrowIfNull(value);
57+
this.RetryValue = value;
58+
}
59+
}
60+
61+
/// <summary>
62+
/// Gets/sets the reference of the retry policy to use, if any
63+
/// </summary>
64+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
65+
public virtual string? RetryReference
66+
{
67+
get => this.RetryValue.T2Value;
68+
set
69+
{
70+
ArgumentException.ThrowIfNullOrWhiteSpace(value);
71+
this.RetryValue = value;
72+
}
73+
}
74+
75+
/// <summary>
76+
/// Gets/sets the retry policy to use, if any
77+
/// </summary>
78+
[Required]
79+
[DataMember(Name = "retry", Order = 5), JsonInclude, JsonPropertyName("retry"), JsonPropertyOrder(5), YamlMember(Alias = "retry", Order = 5)]
80+
protected virtual OneOf<RetryPolicyDefinition, string> RetryValue { get; set; } = null!;
5281

5382
/// <summary>
5483
/// Gets/sets a name/definition map of the tasks to run when catching an error

Diff for: src/ServerlessWorkflow.Sdk/Models/ErrorDefinition.cs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace ServerlessWorkflow.Sdk.Models;
1818
/// </summary>
1919
[DataContract]
2020
public record ErrorDefinition
21+
: ReferenceableComponentDefinition
2122
{
2223

2324
/// <summary>

Diff for: src/ServerlessWorkflow.Sdk/Models/RaiseErrorDefinition.cs

+30-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,36 @@ public record RaiseErrorDefinition
2323
/// <summary>
2424
/// Gets/sets the definition of the error to raise
2525
/// </summary>
26+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
27+
public virtual ErrorDefinition? Error
28+
{
29+
get => this.ErrorValue.T1Value;
30+
set
31+
{
32+
ArgumentNullException.ThrowIfNull(value);
33+
this.ErrorValue = value;
34+
}
35+
}
36+
37+
/// <summary>
38+
/// Gets/sets the reference of the error to raise
39+
/// </summary>
40+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
41+
public virtual string? ErrorReference
42+
{
43+
get => this.ErrorValue.T2Value;
44+
set
45+
{
46+
ArgumentException.ThrowIfNullOrWhiteSpace(value);
47+
this.ErrorValue = value;
48+
}
49+
}
50+
51+
/// <summary>
52+
/// Gets/sets the endpoint at which to get the defined resource
53+
/// </summary>
2654
[Required]
27-
[DataMember(Name = "error", Order = 1), JsonPropertyName("error"), JsonPropertyOrder(1), YamlMember(Alias = "error", Order = 1)]
28-
public required virtual ErrorDefinition Error { get; set; }
55+
[DataMember(Name = "error", Order = 1), JsonInclude, JsonPropertyName("error"), JsonPropertyOrder(1), YamlMember(Alias = "error", Order = 1)]
56+
protected virtual OneOf<ErrorDefinition, string> ErrorValue { get; set; } = null!;
2957

3058
}

Diff for: src/ServerlessWorkflow.Sdk/Models/ReferenceableComponentDefinition.cs

-6
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,4 @@ public abstract record ReferenceableComponentDefinition
2727
[DataMember(Order = 1, Name = "$ref"), JsonPropertyOrder(1), JsonPropertyName("$ref"), YamlMember(Order = 1, Alias = "$ref")]
2828
public virtual Uri? Ref { get; set; }
2929

30-
/// <summary>
31-
/// Gets/sets the endpoint's authentication policy, if any
32-
/// </summary>
33-
[DataMember(Name = "authentication", Order = 2), JsonPropertyName("authentication"), JsonPropertyOrder(2), YamlMember(Alias = "authentication", Order = 2)]
34-
public virtual AuthenticationPolicyDefinition? Authentication { get; set; }
35-
3630
}

Diff for: src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs

+30-2
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,38 @@ public abstract record TaskDefinition
5454
public virtual OutputDataModelDefinition? Export { get; set; }
5555

5656
/// <summary>
57-
/// Gets/sets a boolean indicating whether or not to return the result, if any, of the defined task
57+
/// Gets/sets the task's timeout, if any
58+
/// </summary>
59+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
60+
public virtual TimeoutDefinition? Timeout
61+
{
62+
get => this.TimeoutValue.T1Value;
63+
set
64+
{
65+
ArgumentNullException.ThrowIfNull(value);
66+
this.TimeoutValue = value;
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Gets/sets the reference to the task's timeout, if any
72+
/// </summary>
73+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
74+
public virtual string? TimeoutReference
75+
{
76+
get => this.TimeoutValue.T2Value;
77+
set
78+
{
79+
ArgumentException.ThrowIfNullOrWhiteSpace(value);
80+
this.TimeoutValue = value;
81+
}
82+
}
83+
84+
/// <summary>
85+
/// Gets/sets the task's timeout, if any
5886
/// </summary>
5987
[DataMember(Name = "timeout", Order = 13), JsonPropertyName("timeout"), JsonPropertyOrder(13), YamlMember(Alias = "timeout", Order = 13)]
60-
public virtual TimeoutDefinition? Timeout { get; set; }
88+
protected virtual OneOf<TimeoutDefinition, string> TimeoutValue { get; set; } = null!;
6189

6290
/// <summary>
6391
/// Gets/sets the flow directive to be performed upon completion of the task

Diff for: src/ServerlessWorkflow.Sdk/Models/WorkflowDefinition.cs

+30-2
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,38 @@ public record WorkflowDefinition
4040
public virtual ComponentDefinitionCollection? Use { get; set; }
4141

4242
/// <summary>
43-
/// Gets/sets a name/value mapping of the tasks to perform
43+
/// Gets/sets the workflow's timeout, if any
44+
/// </summary>
45+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
46+
public virtual TimeoutDefinition? Timeout
47+
{
48+
get => this.TimeoutValue.T1Value;
49+
set
50+
{
51+
ArgumentNullException.ThrowIfNull(value);
52+
this.TimeoutValue = value;
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Gets/sets the reference to the workflow's timeout, if any
58+
/// </summary>
59+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
60+
public virtual string? TimeoutReference
61+
{
62+
get => this.TimeoutValue.T2Value;
63+
set
64+
{
65+
ArgumentException.ThrowIfNullOrWhiteSpace(value);
66+
this.TimeoutValue = value;
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Gets/sets the workflow's timeout, if any
4472
/// </summary>
4573
[DataMember(Name = "timeout", Order = 4), JsonPropertyName("timeout"), JsonPropertyOrder(4), YamlMember(Alias = "timeout", Order = 4)]
46-
public virtual TimeoutDefinition? Timeout { get; set; }
74+
protected virtual OneOf<TimeoutDefinition, string> TimeoutValue { get; set; } = null!;
4775

4876
/// <summary>
4977
/// Gets/sets the workflow's output definition, if any

Diff for: src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
</ItemGroup>
3434

3535
<ItemGroup>
36+
<PackageReference Include="FluentValidation" Version="11.9.2" />
3637
<PackageReference Include="Neuroglia.Serialization.YamlDotNet" Version="4.15.0" />
3738
<PackageReference Include="Semver" Version="2.3.0" />
3839
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.Models.Calls;
17+
18+
namespace ServerlessWorkflow.Sdk.Validation;
19+
20+
/// <summary>
21+
/// Represents the <see cref="IValidator"/> used to validate <see cref="AsyncApiCallDefinition"/>s
22+
/// </summary>
23+
public class AsyncApiCallDefinitionValidator
24+
: AbstractValidator<AsyncApiCallDefinition>
25+
{
26+
27+
/// <inheritdoc/>
28+
public AsyncApiCallDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components)
29+
{
30+
this.ServiceProvider = serviceProvider;
31+
this.Components = components;
32+
this.RuleFor(c => c.Authentication!)
33+
.SetValidator(c => new AuthenticationPolicyDefinitionValidator(this.ServiceProvider, this.Components))
34+
.When(c => c.Authentication != null);
35+
}
36+
37+
/// <summary>
38+
/// Gets the current <see cref="IServiceProvider"/>
39+
/// </summary>
40+
protected IServiceProvider ServiceProvider { get; }
41+
42+
/// <summary>
43+
/// Gets the configured reusable components
44+
/// </summary>
45+
protected ComponentDefinitionCollection? Components { get; }
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
17+
namespace ServerlessWorkflow.Sdk.Validation;
18+
19+
/// <summary>
20+
/// Represents the <see cref="IValidator"/> used to validate <see cref="AuthenticationPolicyDefinition"/>s
21+
/// </summary>
22+
public class AuthenticationPolicyDefinitionValidator
23+
: AbstractValidator<AuthenticationPolicyDefinition>
24+
{
25+
26+
/// <inheritdoc/>
27+
public AuthenticationPolicyDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components)
28+
{
29+
this.ServiceProvider = serviceProvider;
30+
this.Components = components;
31+
this.RuleFor(auth => auth.Use!)
32+
.Must(ReferenceAnExistingAuthentication)
33+
.When(auth => !string.IsNullOrWhiteSpace(auth.Use));
34+
this.RuleFor(auth => auth.Basic!.Use!)
35+
.Must(ReferenceAnExistingSecret)
36+
.When(auth => !string.IsNullOrWhiteSpace(auth.Basic?.Use));
37+
this.RuleFor(auth => auth.Bearer!.Use!)
38+
.Must(ReferenceAnExistingSecret)
39+
.When(auth => !string.IsNullOrWhiteSpace(auth.Bearer?.Use));
40+
this.RuleFor(auth => auth.Certificate!.Use!)
41+
.Must(ReferenceAnExistingSecret)
42+
.When(auth => !string.IsNullOrWhiteSpace(auth.Certificate?.Use));
43+
this.RuleFor(auth => auth.Digest!.Use!)
44+
.Must(ReferenceAnExistingSecret)
45+
.When(auth => !string.IsNullOrWhiteSpace(auth.Digest?.Use));
46+
this.RuleFor(auth => auth.OAuth2!.Use!)
47+
.Must(ReferenceAnExistingSecret)
48+
.When(auth => !string.IsNullOrWhiteSpace(auth.OAuth2?.Use));
49+
this.RuleFor(auth => auth.Oidc!.Use!)
50+
.Must(ReferenceAnExistingSecret)
51+
.When(auth => !string.IsNullOrWhiteSpace(auth.Oidc?.Use));
52+
}
53+
54+
/// <summary>
55+
/// Gets the current <see cref="IServiceProvider"/>
56+
/// </summary>
57+
protected IServiceProvider ServiceProvider { get; }
58+
59+
/// <summary>
60+
/// Gets the configured reusable components
61+
/// </summary>
62+
protected ComponentDefinitionCollection? Components { get; }
63+
64+
/// <summary>
65+
/// Determines whether or not the specified authentication exists
66+
/// </summary>
67+
/// <param name="name">The name of the authentication to check</param>
68+
/// <returns>A boolean indicating whether or not the specified authentication exists</returns>
69+
protected virtual bool ReferenceAnExistingAuthentication(string name)
70+
{
71+
if (this.Components?.Authentications?.ContainsKey(name) == true) return true;
72+
else return false;
73+
}
74+
75+
/// <summary>
76+
/// Determines whether or not the specified secret exists
77+
/// </summary>
78+
/// <param name="name">The name of the secret to check</param>
79+
/// <returns>A boolean indicating whether or not the specified secret exists</returns>
80+
protected virtual bool ReferenceAnExistingSecret(string name)
81+
{
82+
if (this.Components?.Secrets?.Contains(name) == true) return true;
83+
else return false;
84+
}
85+
86+
}

0 commit comments

Comments
 (0)