Skip to content

Commit 5a4a68b

Browse files
authored
Merge pull request #65 from serverlessworkflow/feat-catalog-component
Added a new `CatalogDefinition` model
2 parents 01af5d0 + 873c61b commit 5a4a68b

11 files changed

+229
-10
lines changed

src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<VersionPrefix>1.0.0</VersionPrefix>
8-
<VersionSuffix>alpha4.1</VersionSuffix>
8+
<VersionSuffix>alpha5</VersionSuffix>
99
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1010
<FileVersion>$(VersionPrefix)</FileVersion>
1111
<NeutralLanguage>en</NeutralLanguage>

src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<VersionPrefix>1.0.0</VersionPrefix>
8-
<VersionSuffix>alpha4.1</VersionSuffix>
8+
<VersionSuffix>alpha5</VersionSuffix>
99
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1010
<FileVersion>$(VersionPrefix)</FileVersion>
1111
<NeutralLanguage>en</NeutralLanguage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
namespace ServerlessWorkflow.Sdk.Models;
15+
16+
/// <summary>
17+
/// Represents the definition of a workflow component catalog
18+
/// </summary>
19+
[DataContract]
20+
public record CatalogDefinition
21+
{
22+
23+
/// <summary>
24+
/// Gets the name of the default catalog
25+
/// </summary>
26+
public const string DefaultCatalogName = "default";
27+
28+
/// <summary>
29+
/// Gets/sets the endpoint that defines the root URL at which the catalog is located
30+
/// </summary>
31+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
32+
public virtual EndpointDefinition Endpoint
33+
{
34+
get => this.EndpointValue.T1Value ?? new() { Uri = this.EndpointUri };
35+
set => this.EndpointValue = value;
36+
}
37+
38+
/// <summary>
39+
/// Gets/sets the endpoint that defines the root URL at which the catalog is located
40+
/// </summary>
41+
[IgnoreDataMember, JsonIgnore, YamlIgnore]
42+
public virtual Uri EndpointUri
43+
{
44+
get => this.EndpointValue.T1Value?.Uri ?? this.EndpointValue.T2Value!;
45+
set => this.EndpointValue = value;
46+
}
47+
48+
/// <summary>
49+
/// Gets/sets the endpoint that defines the root URL at which the catalog is located
50+
/// </summary>
51+
[Required]
52+
[DataMember(Name = "endpoint", Order = 1), JsonInclude, JsonPropertyName("endpoint"), JsonPropertyOrder(1), YamlMember(Alias = "endpoint", Order = 1)]
53+
protected virtual OneOf<EndpointDefinition, Uri> EndpointValue { get; set; } = null!;
54+
55+
}

src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -26,40 +26,46 @@ public record ComponentDefinitionCollection
2626
[DataMember(Name = "authentications", Order = 1), JsonPropertyName("authentications"), JsonPropertyOrder(1), YamlMember(Alias = "authentications", Order = 1)]
2727
public virtual EquatableDictionary<string, AuthenticationPolicyDefinition>? Authentications { get; set; }
2828

29+
/// <summary>
30+
/// Gets/sets a name/value mapping of the catalogs, if any, from which to import reusable components used within the workflow
31+
/// </summary>
32+
[DataMember(Name = "catalogs", Order = 2), JsonPropertyName("catalogs"), JsonPropertyOrder(2), YamlMember(Alias = "catalogs", Order = 2)]
33+
public virtual EquatableDictionary<string, CatalogDefinition>? Catalogs { get; set; }
34+
2935
/// <summary>
3036
/// Gets/sets a name/value mapping of the workflow's errors, if any
3137
/// </summary>
32-
[DataMember(Name = "errors", Order = 2), JsonPropertyName("errors"), JsonPropertyOrder(2), YamlMember(Alias = "errors", Order = 2)]
38+
[DataMember(Name = "errors", Order = 3), JsonPropertyName("errors"), JsonPropertyOrder(3), YamlMember(Alias = "errors", Order = 3)]
3339
public virtual EquatableDictionary<string, ErrorDefinition>? Errors { get; set; }
3440

3541
/// <summary>
3642
/// Gets/sets a name/value mapping of the workflow's extensions, if any
3743
/// </summary>
38-
[DataMember(Name = "extensions", Order = 3), JsonPropertyName("extensions"), JsonPropertyOrder(3), YamlMember(Alias = "extensions", Order = 3)]
44+
[DataMember(Name = "extensions", Order = 4), JsonPropertyName("extensions"), JsonPropertyOrder(4), YamlMember(Alias = "extensions", Order = 4)]
3945
public virtual EquatableDictionary<string, ExtensionDefinition>? Extensions { get; set; }
4046

4147
/// <summary>
4248
/// Gets/sets a name/value mapping of the workflow's reusable functions
4349
/// </summary>
44-
[DataMember(Name = "functions", Order = 4), JsonPropertyName("functions"), JsonPropertyOrder(4), YamlMember(Alias = "functions", Order = 4)]
50+
[DataMember(Name = "functions", Order = 5), JsonPropertyName("functions"), JsonPropertyOrder(5), YamlMember(Alias = "functions", Order = 5)]
4551
public virtual EquatableDictionary<string, TaskDefinition>? Functions { get; set; }
4652

4753
/// <summary>
4854
/// Gets/sets a name/value mapping of the workflow's reusable retry policies
4955
/// </summary>
50-
[DataMember(Name = "retries", Order = 5), JsonPropertyName("retries"), JsonPropertyOrder(5), YamlMember(Alias = "retries", Order = 5)]
56+
[DataMember(Name = "retries", Order = 6), JsonPropertyName("retries"), JsonPropertyOrder(6), YamlMember(Alias = "retries", Order = 6)]
5157
public virtual EquatableDictionary<string, RetryPolicyDefinition>? Retries { get; set; }
5258

5359
/// <summary>
5460
/// Gets/sets a list containing the workflow's secrets
5561
/// </summary>
56-
[DataMember(Name = "secrets", Order = 6), JsonPropertyName("secrets"), JsonPropertyOrder(6), YamlMember(Alias = "secrets", Order = 6)]
62+
[DataMember(Name = "secrets", Order = 7), JsonPropertyName("secrets"), JsonPropertyOrder(7), YamlMember(Alias = "secrets", Order = 7)]
5763
public virtual EquatableList<string>? Secrets { get; set; }
5864

5965
/// <summary>
6066
/// Gets/sets a name/value mapping of the workflow's reusable timeouts
6167
/// </summary>
62-
[DataMember(Name = "timeouts", Order = 7), JsonPropertyName("timeouts"), JsonPropertyOrder(7), YamlMember(Alias = "timeouts", Order = 7)]
68+
[DataMember(Name = "timeouts", Order = 7), JsonPropertyName("timeouts"), JsonPropertyOrder(8), YamlMember(Alias = "timeouts", Order = 8)]
6369
public virtual EquatableDictionary<string, TimeoutDefinition>? Timeouts { get; set; }
6470

6571
}

src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx

+6
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,10 @@
132132
<data name="UndefinedTimeout" xml:space="preserve">
133133
<value>Undefined timeout</value>
134134
</data>
135+
<data name="UndefinedCatalog" xml:space="preserve">
136+
<value>Undefined catalog</value>
137+
</data>
138+
<data name="InvalidCatalogedFunctionCallFormat" xml:space="preserve">
139+
<value>Invalid cataloged function call format. Expected format '{functionName}:{functionSemanticVersion}@{catalogName}'</value>
140+
</data>
135141
</root>

src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<VersionPrefix>1.0.0</VersionPrefix>
8-
<VersionSuffix>alpha4.1</VersionSuffix>
8+
<VersionSuffix>alpha5</VersionSuffix>
99
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1010
<FileVersion>$(VersionPrefix)</FileVersion>
1111
<NeutralLanguage>en</NeutralLanguage>

src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs

+43
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using FluentValidation;
1515
using Microsoft.Extensions.DependencyInjection;
1616
using Neuroglia.Serialization;
17+
using Semver;
1718
using ServerlessWorkflow.Sdk.Models;
1819
using ServerlessWorkflow.Sdk.Models.Calls;
1920
using ServerlessWorkflow.Sdk.Models.Tasks;
@@ -37,6 +38,14 @@ public CallTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDe
3738
.Must(ReferenceAnExistingFunction)
3839
.When(c => !Uri.TryCreate(c.Call, UriKind.Absolute, out _) && !c.Call.Contains('@'))
3940
.WithMessage(ValidationErrors.UndefinedFunction);
41+
this.RuleFor(c => c.Call)
42+
.Must(BeWellFormedCatalogedFunctionCall)
43+
.When(c => c.Call.Contains('@') && (!Uri.TryCreate(c.Call, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Host)))
44+
.WithMessage(ValidationErrors.InvalidCatalogedFunctionCallFormat);
45+
this.RuleFor(c => c.Call)
46+
.Must(ReferenceAnExistingCatalog)
47+
.When(c => c.Call.Contains('@') && (!Uri.TryCreate(c.Call, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Host)))
48+
.WithMessage(ValidationErrors.UndefinedCatalog);
4049
this.When(c => c.Call == Function.AsyncApi, () =>
4150
{
4251
this.RuleFor(c => (AsyncApiCallDefinition)this.JsonSerializer.Convert(c.With, typeof(AsyncApiCallDefinition))!)
@@ -81,9 +90,43 @@ public CallTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDe
8190
/// <returns>A boolean indicating whether or not the specified function exists</returns>
8291
protected virtual bool ReferenceAnExistingFunction(string name)
8392
{
93+
if (string.IsNullOrWhiteSpace(name)) return false;
8494
if (Function.AsEnumerable().Contains(name)) return true;
8595
else if (this.Components?.Functions?.ContainsKey(name) == true) return true;
8696
else return false;
8797
}
8898

99+
/// <summary>
100+
/// Determines whether or not the format of the call is a valid cataloged function call
101+
/// </summary>
102+
/// <param name="name">The name of the function to check</param>
103+
/// <returns>A boolean indicatingwhether or not the format of the call is a valid cataloged function call</returns>
104+
protected virtual bool BeWellFormedCatalogedFunctionCall(string name)
105+
{
106+
if (string.IsNullOrWhiteSpace(name)) return false;
107+
var components = name.Split('@', StringSplitOptions.RemoveEmptyEntries);
108+
if (components.Length != 2) return false;
109+
var qualifiedName = components[0];
110+
components = qualifiedName.Split(':');
111+
if (components.Length != 2) return false;
112+
var version = components[1];
113+
if (!SemVersion.TryParse(version, SemVersionStyles.Strict, out var semver)) return false;
114+
return true;
115+
}
116+
117+
/// <summary>
118+
/// Determines whether or not the catalog from which the specified function is imported exists
119+
/// </summary>
120+
/// <param name="name">The name of the function to check</param>
121+
/// <returns>A boolean indicating whether or not the catalog from which the specified function is imported exists</returns>
122+
protected virtual bool ReferenceAnExistingCatalog(string name)
123+
{
124+
if (string.IsNullOrWhiteSpace(name)) return false;
125+
var components = name.Split('@', StringSplitOptions.RemoveEmptyEntries);
126+
var catalogName = components[1];
127+
if (catalogName == CatalogDefinition.DefaultCatalogName) return true;
128+
else if(this.Components?.Catalogs?.ContainsKey(catalogName) == true) return true;
129+
else return false;
130+
}
131+
89132
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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="CatalogDefinition"/>s
21+
/// </summary>
22+
public class CatalogDefinitionValidator
23+
: AbstractValidator<CatalogDefinition>
24+
{
25+
26+
/// <inheritdoc/>
27+
public CatalogDefinitionValidator(IServiceProvider serviceProvider)
28+
{
29+
this.ServiceProvider = serviceProvider;
30+
this.RuleFor(c => c.Endpoint)
31+
.NotNull()
32+
.When(c => c.EndpointUri == null);
33+
this.RuleFor(c => c.EndpointUri)
34+
.NotNull()
35+
.When(c => c.Endpoint == null);
36+
}
37+
38+
/// <summary>
39+
/// Gets the current <see cref="IServiceProvider"/>
40+
/// </summary>
41+
protected IServiceProvider ServiceProvider { get; }
42+
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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="CatalogDefinition"/> key/value pairs
21+
/// </summary>
22+
public class CatalogKeyValuePairValidator
23+
: AbstractValidator<KeyValuePair<string, CatalogDefinition>>
24+
{
25+
26+
/// <inheritdoc/>
27+
public CatalogKeyValuePairValidator(IServiceProvider serviceProvider)
28+
{
29+
this.ServiceProvider = serviceProvider;
30+
this.RuleFor(t => t.Value)
31+
.Custom((value, context) =>
32+
{
33+
var key = context.InstanceToValidate.Key;
34+
var validator = new CatalogDefinitionValidator(serviceProvider);
35+
var validationResult = validator.Validate(value);
36+
foreach (var error in validationResult.Errors) context.AddFailure($"{key}.{error.PropertyName}", error.ErrorMessage);
37+
});
38+
}
39+
40+
/// <summary>
41+
/// Gets the current <see cref="IServiceProvider"/>
42+
/// </summary>
43+
protected IServiceProvider ServiceProvider { get; }
44+
45+
}

src/ServerlessWorkflow.Sdk/Validation/ComponentDefinitionCollectionValidator.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
using FluentValidation;
1515
using ServerlessWorkflow.Sdk.Models;
16+
using ServerlessWorkflow.Sdk.Properties;
1617

1718
namespace ServerlessWorkflow.Sdk.Validation;
1819

@@ -29,6 +30,8 @@ public ComponentDefinitionCollectionValidator(IServiceProvider serviceProvider)
2930
this.ServiceProvider = serviceProvider;
3031
this.RuleForEach(c => c.Authentications)
3132
.SetValidator(c => new AuthenticationPolicyKeyValuePairValidator(this.ServiceProvider, c));
33+
this.RuleForEach(c => c.Catalogs)
34+
.SetValidator(c => new CatalogKeyValuePairValidator(this.ServiceProvider));
3235
this.RuleForEach(c => c.Functions)
3336
.SetValidator(c => new TaskKeyValuePairValidator(this.ServiceProvider, c, c.Functions));
3437
}
@@ -38,4 +41,4 @@ public ComponentDefinitionCollectionValidator(IServiceProvider serviceProvider)
3841
/// </summary>
3942
protected IServiceProvider ServiceProvider { get; }
4043

41-
}
44+
}

0 commit comments

Comments
 (0)