Skip to content

Commit

Permalink
Merge pull request #65 from serverlessworkflow/feat-catalog-component
Browse files Browse the repository at this point in the history
Added a new `CatalogDefinition` model
  • Loading branch information
cdavernas authored Oct 22, 2024
2 parents 01af5d0 + 873c61b commit 5a4a68b
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha4.1</VersionSuffix>
<VersionSuffix>alpha5</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha4.1</VersionSuffix>
<VersionSuffix>alpha5</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
55 changes: 55 additions & 0 deletions src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Represents the definition of a workflow component catalog
/// </summary>
[DataContract]
public record CatalogDefinition
{

/// <summary>
/// Gets the name of the default catalog
/// </summary>
public const string DefaultCatalogName = "default";

/// <summary>
/// Gets/sets the endpoint that defines the root URL at which the catalog is located
/// </summary>
[IgnoreDataMember, JsonIgnore, YamlIgnore]
public virtual EndpointDefinition Endpoint
{
get => this.EndpointValue.T1Value ?? new() { Uri = this.EndpointUri };
set => this.EndpointValue = value;
}

/// <summary>
/// Gets/sets the endpoint that defines the root URL at which the catalog is located
/// </summary>
[IgnoreDataMember, JsonIgnore, YamlIgnore]
public virtual Uri EndpointUri
{
get => this.EndpointValue.T1Value?.Uri ?? this.EndpointValue.T2Value!;
set => this.EndpointValue = value;
}

/// <summary>
/// Gets/sets the endpoint that defines the root URL at which the catalog is located
/// </summary>
[Required]
[DataMember(Name = "endpoint", Order = 1), JsonInclude, JsonPropertyName("endpoint"), JsonPropertyOrder(1), YamlMember(Alias = "endpoint", Order = 1)]
protected virtual OneOf<EndpointDefinition, Uri> EndpointValue { get; set; } = null!;

}
18 changes: 12 additions & 6 deletions src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,40 +26,46 @@ public record ComponentDefinitionCollection
[DataMember(Name = "authentications", Order = 1), JsonPropertyName("authentications"), JsonPropertyOrder(1), YamlMember(Alias = "authentications", Order = 1)]
public virtual EquatableDictionary<string, AuthenticationPolicyDefinition>? Authentications { get; set; }

/// <summary>
/// Gets/sets a name/value mapping of the catalogs, if any, from which to import reusable components used within the workflow
/// </summary>
[DataMember(Name = "catalogs", Order = 2), JsonPropertyName("catalogs"), JsonPropertyOrder(2), YamlMember(Alias = "catalogs", Order = 2)]
public virtual EquatableDictionary<string, CatalogDefinition>? Catalogs { get; set; }

/// <summary>
/// Gets/sets a name/value mapping of the workflow's errors, if any
/// </summary>
[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<string, ErrorDefinition>? Errors { get; set; }

/// <summary>
/// Gets/sets a name/value mapping of the workflow's extensions, if any
/// </summary>
[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<string, ExtensionDefinition>? Extensions { get; set; }

/// <summary>
/// Gets/sets a name/value mapping of the workflow's reusable functions
/// </summary>
[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<string, TaskDefinition>? Functions { get; set; }

/// <summary>
/// Gets/sets a name/value mapping of the workflow's reusable retry policies
/// </summary>
[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<string, RetryPolicyDefinition>? Retries { get; set; }

/// <summary>
/// Gets/sets a list containing the workflow's secrets
/// </summary>
[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<string>? Secrets { get; set; }

/// <summary>
/// Gets/sets a name/value mapping of the workflow's reusable timeouts
/// </summary>
[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<string, TimeoutDefinition>? Timeouts { get; set; }

}
18 changes: 18 additions & 0 deletions src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,10 @@
<data name="UndefinedTimeout" xml:space="preserve">
<value>Undefined timeout</value>
</data>
<data name="UndefinedCatalog" xml:space="preserve">
<value>Undefined catalog</value>
</data>
<data name="InvalidCatalogedFunctionCallFormat" xml:space="preserve">
<value>Invalid cataloged function call format. Expected format '{functionName}:{functionSemanticVersion}@{catalogName}'</value>
</data>
</root>
2 changes: 1 addition & 1 deletion src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha4.1</VersionSuffix>
<VersionSuffix>alpha5</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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))!)
Expand Down Expand Up @@ -81,9 +90,43 @@ public CallTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDe
/// <returns>A boolean indicating whether or not the specified function exists</returns>
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;
}

/// <summary>
/// Determines whether or not the format of the call is a valid cataloged function call
/// </summary>
/// <param name="name">The name of the function to check</param>
/// <returns>A boolean indicatingwhether or not the format of the call is a valid cataloged function call</returns>
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;
}

/// <summary>
/// Determines whether or not the catalog from which the specified function is imported exists
/// </summary>
/// <param name="name">The name of the function to check</param>
/// <returns>A boolean indicating whether or not the catalog from which the specified function is imported exists</returns>
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;
}

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

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

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

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

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

/// <summary>
/// Represents the <see cref="IValidator"/> used to validate <see cref="CatalogDefinition"/> key/value pairs
/// </summary>
public class CatalogKeyValuePairValidator
: AbstractValidator<KeyValuePair<string, CatalogDefinition>>
{

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

using FluentValidation;
using ServerlessWorkflow.Sdk.Models;
using ServerlessWorkflow.Sdk.Properties;

namespace ServerlessWorkflow.Sdk.Validation;

Expand All @@ -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));
}
Expand All @@ -38,4 +41,4 @@ public ComponentDefinitionCollectionValidator(IServiceProvider serviceProvider)
/// </summary>
protected IServiceProvider ServiceProvider { get; }

}
}

0 comments on commit 5a4a68b

Please sign in to comment.