diff --git a/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj b/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj
index fe79026..24ea486 100644
--- a/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj
+++ b/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj
@@ -5,7 +5,7 @@
enable
enable
1.0.0
- alpha4.1
+ alpha5
$(VersionPrefix)
$(VersionPrefix)
en
diff --git a/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj b/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj
index 201d021..2e9bba6 100644
--- a/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj
+++ b/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj
@@ -5,7 +5,7 @@
enable
enable
1.0.0
- alpha4.1
+ alpha5
$(VersionPrefix)
$(VersionPrefix)
en
diff --git a/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs
new file mode 100644
index 0000000..b9d22b5
--- /dev/null
+++ b/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs
@@ -0,0 +1,55 @@
+// 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.
+
+namespace ServerlessWorkflow.Sdk.Models;
+
+///
+/// Represents the definition of a workflow component catalog
+///
+[DataContract]
+public record CatalogDefinition
+{
+
+ ///
+ /// Gets the name of the default catalog
+ ///
+ public const string DefaultCatalogName = "default";
+
+ ///
+ /// Gets/sets the endpoint that defines the root URL at which the catalog is located
+ ///
+ [IgnoreDataMember, JsonIgnore, YamlIgnore]
+ public virtual EndpointDefinition Endpoint
+ {
+ get => this.EndpointValue.T1Value ?? new() { Uri = this.EndpointUri };
+ set => this.EndpointValue = value;
+ }
+
+ ///
+ /// Gets/sets the endpoint that defines the root URL at which the catalog is located
+ ///
+ [IgnoreDataMember, JsonIgnore, YamlIgnore]
+ public virtual Uri EndpointUri
+ {
+ get => this.EndpointValue.T1Value?.Uri ?? this.EndpointValue.T2Value!;
+ set => this.EndpointValue = value;
+ }
+
+ ///
+ /// Gets/sets the endpoint that defines the root URL at which the catalog is located
+ ///
+ [Required]
+ [DataMember(Name = "endpoint", Order = 1), JsonInclude, JsonPropertyName("endpoint"), JsonPropertyOrder(1), YamlMember(Alias = "endpoint", Order = 1)]
+ protected virtual OneOf EndpointValue { get; set; } = null!;
+
+}
diff --git a/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs b/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs
index e6f7053..a170be0 100644
--- a/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs
+++ b/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs
@@ -26,40 +26,46 @@ public record ComponentDefinitionCollection
[DataMember(Name = "authentications", Order = 1), JsonPropertyName("authentications"), JsonPropertyOrder(1), YamlMember(Alias = "authentications", Order = 1)]
public virtual EquatableDictionary? Authentications { get; set; }
+ ///
+ /// Gets/sets a name/value mapping of the catalogs, if any, from which to import reusable components used within the workflow
+ ///
+ [DataMember(Name = "catalogs", Order = 2), JsonPropertyName("catalogs"), JsonPropertyOrder(2), YamlMember(Alias = "catalogs", Order = 2)]
+ public virtual EquatableDictionary? Catalogs { get; set; }
+
///
/// Gets/sets a name/value mapping of the workflow's errors, if any
///
- [DataMember(Name = "errors", Order = 2), JsonPropertyName("errors"), JsonPropertyOrder(2), YamlMember(Alias = "errors", Order = 2)]
+ [DataMember(Name = "errors", Order = 3), JsonPropertyName("errors"), JsonPropertyOrder(3), YamlMember(Alias = "errors", Order = 3)]
public virtual EquatableDictionary? Errors { get; set; }
///
/// Gets/sets a name/value mapping of the workflow's extensions, if any
///
- [DataMember(Name = "extensions", Order = 3), JsonPropertyName("extensions"), JsonPropertyOrder(3), YamlMember(Alias = "extensions", Order = 3)]
+ [DataMember(Name = "extensions", Order = 4), JsonPropertyName("extensions"), JsonPropertyOrder(4), YamlMember(Alias = "extensions", Order = 4)]
public virtual EquatableDictionary? Extensions { get; set; }
///
/// Gets/sets a name/value mapping of the workflow's reusable functions
///
- [DataMember(Name = "functions", Order = 4), JsonPropertyName("functions"), JsonPropertyOrder(4), YamlMember(Alias = "functions", Order = 4)]
+ [DataMember(Name = "functions", Order = 5), JsonPropertyName("functions"), JsonPropertyOrder(5), YamlMember(Alias = "functions", Order = 5)]
public virtual EquatableDictionary? Functions { get; set; }
///
/// Gets/sets a name/value mapping of the workflow's reusable retry policies
///
- [DataMember(Name = "retries", Order = 5), JsonPropertyName("retries"), JsonPropertyOrder(5), YamlMember(Alias = "retries", Order = 5)]
+ [DataMember(Name = "retries", Order = 6), JsonPropertyName("retries"), JsonPropertyOrder(6), YamlMember(Alias = "retries", Order = 6)]
public virtual EquatableDictionary? Retries { get; set; }
///
/// Gets/sets a list containing the workflow's secrets
///
- [DataMember(Name = "secrets", Order = 6), JsonPropertyName("secrets"), JsonPropertyOrder(6), YamlMember(Alias = "secrets", Order = 6)]
+ [DataMember(Name = "secrets", Order = 7), JsonPropertyName("secrets"), JsonPropertyOrder(7), YamlMember(Alias = "secrets", Order = 7)]
public virtual EquatableList? Secrets { get; set; }
///
/// Gets/sets a name/value mapping of the workflow's reusable timeouts
///
- [DataMember(Name = "timeouts", Order = 7), JsonPropertyName("timeouts"), JsonPropertyOrder(7), YamlMember(Alias = "timeouts", Order = 7)]
+ [DataMember(Name = "timeouts", Order = 7), JsonPropertyName("timeouts"), JsonPropertyOrder(8), YamlMember(Alias = "timeouts", Order = 8)]
public virtual EquatableDictionary? Timeouts { get; set; }
}
diff --git a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs b/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs
index faf0b98..814a7bb 100644
--- a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs
+++ b/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs
@@ -60,6 +60,15 @@ internal ValidationErrors() {
}
}
+ ///
+ /// Looks up a localized string similar to Invalid cataloged function call format. Expected format '{functionName}:{functionSemanticVersion}@{catalogName}'.
+ ///
+ internal static string InvalidCatalogedFunctionCallFormat {
+ get {
+ return ResourceManager.GetString("InvalidCatalogedFunctionCallFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Undefined authentication policy.
///
@@ -69,6 +78,15 @@ internal static string UndefinedAuthenticationPolicy {
}
}
+ ///
+ /// Looks up a localized string similar to Undefined catalog.
+ ///
+ internal static string UndefinedCatalog {
+ get {
+ return ResourceManager.GetString("UndefinedCatalog", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Undefined function.
///
diff --git a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx b/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx
index 5946975..d1b7d5a 100644
--- a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx
+++ b/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx
@@ -132,4 +132,10 @@
Undefined timeout
+
+ Undefined catalog
+
+
+ Invalid cataloged function call format. Expected format '{functionName}:{functionSemanticVersion}@{catalogName}'
+
\ No newline at end of file
diff --git a/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj b/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj
index 5d8ecc0..8697ada 100644
--- a/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj
+++ b/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj
@@ -5,7 +5,7 @@
enable
enable
1.0.0
- alpha4.1
+ alpha5
$(VersionPrefix)
$(VersionPrefix)
en
diff --git a/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs
index 2f21bea..3304469 100644
--- a/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs
+++ b/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs
@@ -14,6 +14,7 @@
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Neuroglia.Serialization;
+using Semver;
using ServerlessWorkflow.Sdk.Models;
using ServerlessWorkflow.Sdk.Models.Calls;
using ServerlessWorkflow.Sdk.Models.Tasks;
@@ -37,6 +38,14 @@ public CallTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDe
.Must(ReferenceAnExistingFunction)
.When(c => !Uri.TryCreate(c.Call, UriKind.Absolute, out _) && !c.Call.Contains('@'))
.WithMessage(ValidationErrors.UndefinedFunction);
+ this.RuleFor(c => c.Call)
+ .Must(BeWellFormedCatalogedFunctionCall)
+ .When(c => c.Call.Contains('@') && (!Uri.TryCreate(c.Call, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Host)))
+ .WithMessage(ValidationErrors.InvalidCatalogedFunctionCallFormat);
+ this.RuleFor(c => c.Call)
+ .Must(ReferenceAnExistingCatalog)
+ .When(c => c.Call.Contains('@') && (!Uri.TryCreate(c.Call, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Host)))
+ .WithMessage(ValidationErrors.UndefinedCatalog);
this.When(c => c.Call == Function.AsyncApi, () =>
{
this.RuleFor(c => (AsyncApiCallDefinition)this.JsonSerializer.Convert(c.With, typeof(AsyncApiCallDefinition))!)
@@ -81,9 +90,43 @@ public CallTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDe
/// A boolean indicating whether or not the specified function exists
protected virtual bool ReferenceAnExistingFunction(string name)
{
+ if (string.IsNullOrWhiteSpace(name)) return false;
if (Function.AsEnumerable().Contains(name)) return true;
else if (this.Components?.Functions?.ContainsKey(name) == true) return true;
else return false;
}
+ ///
+ /// Determines whether or not the format of the call is a valid cataloged function call
+ ///
+ /// The name of the function to check
+ /// A boolean indicatingwhether or not the format of the call is a valid cataloged function call
+ protected virtual bool BeWellFormedCatalogedFunctionCall(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name)) return false;
+ var components = name.Split('@', StringSplitOptions.RemoveEmptyEntries);
+ if (components.Length != 2) return false;
+ var qualifiedName = components[0];
+ components = qualifiedName.Split(':');
+ if (components.Length != 2) return false;
+ var version = components[1];
+ if (!SemVersion.TryParse(version, SemVersionStyles.Strict, out var semver)) return false;
+ return true;
+ }
+
+ ///
+ /// Determines whether or not the catalog from which the specified function is imported exists
+ ///
+ /// The name of the function to check
+ /// A boolean indicating whether or not the catalog from which the specified function is imported exists
+ protected virtual bool ReferenceAnExistingCatalog(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name)) return false;
+ var components = name.Split('@', StringSplitOptions.RemoveEmptyEntries);
+ var catalogName = components[1];
+ if (catalogName == CatalogDefinition.DefaultCatalogName) return true;
+ else if(this.Components?.Catalogs?.ContainsKey(catalogName) == true) return true;
+ else return false;
+ }
+
}
diff --git a/src/ServerlessWorkflow.Sdk/Validation/CatalogDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/CatalogDefinitionValidator.cs
new file mode 100644
index 0000000..e37eca0
--- /dev/null
+++ b/src/ServerlessWorkflow.Sdk/Validation/CatalogDefinitionValidator.cs
@@ -0,0 +1,43 @@
+// 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;
+
+namespace ServerlessWorkflow.Sdk.Validation;
+
+///
+/// Represents the used to validate s
+///
+public class CatalogDefinitionValidator
+ : AbstractValidator
+{
+
+ ///
+ public CatalogDefinitionValidator(IServiceProvider serviceProvider)
+ {
+ this.ServiceProvider = serviceProvider;
+ this.RuleFor(c => c.Endpoint)
+ .NotNull()
+ .When(c => c.EndpointUri == null);
+ this.RuleFor(c => c.EndpointUri)
+ .NotNull()
+ .When(c => c.Endpoint == null);
+ }
+
+ ///
+ /// Gets the current
+ ///
+ protected IServiceProvider ServiceProvider { get; }
+
+}
\ No newline at end of file
diff --git a/src/ServerlessWorkflow.Sdk/Validation/CatalogKeyValuePairValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/CatalogKeyValuePairValidator.cs
new file mode 100644
index 0000000..99a726f
--- /dev/null
+++ b/src/ServerlessWorkflow.Sdk/Validation/CatalogKeyValuePairValidator.cs
@@ -0,0 +1,45 @@
+// 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;
+
+namespace ServerlessWorkflow.Sdk.Validation;
+
+///
+/// Represents the used to validate key/value pairs
+///
+public class CatalogKeyValuePairValidator
+ : AbstractValidator>
+{
+
+ ///
+ public CatalogKeyValuePairValidator(IServiceProvider serviceProvider)
+ {
+ this.ServiceProvider = serviceProvider;
+ this.RuleFor(t => t.Value)
+ .Custom((value, context) =>
+ {
+ var key = context.InstanceToValidate.Key;
+ var validator = new CatalogDefinitionValidator(serviceProvider);
+ var validationResult = validator.Validate(value);
+ foreach (var error in validationResult.Errors) context.AddFailure($"{key}.{error.PropertyName}", error.ErrorMessage);
+ });
+ }
+
+ ///
+ /// Gets the current
+ ///
+ protected IServiceProvider ServiceProvider { get; }
+
+}
diff --git a/src/ServerlessWorkflow.Sdk/Validation/ComponentDefinitionCollectionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/ComponentDefinitionCollectionValidator.cs
index 1e2045d..2ff5d49 100644
--- a/src/ServerlessWorkflow.Sdk/Validation/ComponentDefinitionCollectionValidator.cs
+++ b/src/ServerlessWorkflow.Sdk/Validation/ComponentDefinitionCollectionValidator.cs
@@ -13,6 +13,7 @@
using FluentValidation;
using ServerlessWorkflow.Sdk.Models;
+using ServerlessWorkflow.Sdk.Properties;
namespace ServerlessWorkflow.Sdk.Validation;
@@ -29,6 +30,8 @@ public ComponentDefinitionCollectionValidator(IServiceProvider serviceProvider)
this.ServiceProvider = serviceProvider;
this.RuleForEach(c => c.Authentications)
.SetValidator(c => new AuthenticationPolicyKeyValuePairValidator(this.ServiceProvider, c));
+ this.RuleForEach(c => c.Catalogs)
+ .SetValidator(c => new CatalogKeyValuePairValidator(this.ServiceProvider));
this.RuleForEach(c => c.Functions)
.SetValidator(c => new TaskKeyValuePairValidator(this.ServiceProvider, c, c.Functions));
}
@@ -38,4 +41,4 @@ public ComponentDefinitionCollectionValidator(IServiceProvider serviceProvider)
///
protected IServiceProvider ServiceProvider { get; }
-}
+}
\ No newline at end of file