Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new CatalogDefinition model #65

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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!;

}
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; }

}

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; }

}
}
Loading