diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..566f08b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,54 @@ +{ + "permissions": { + "allow": [ + "Bash(xargs grep:*)", + "Bash(xargs cat:*)", + "Bash(grep -E \"\\\\.cs$\")", + "Bash(dotnet build:*)", + "Bash(dotnet test:*)", + "Bash(ls -la src/ServerlessWorkflow.Sdk/Models/*.cs)", + "Bash(grep -h \"^namespace\" \"C:\\\\Users\\\\User\\\\source\\\\repos\\\\sdk-net/src/ServerlessWorkflow.Sdk/Models\"/*.cs)", + "Bash(find \"C:\\\\Users\\\\User\\\\source\\\\repos\\\\sdk-net/src/ServerlessWorkflow.Sdk/Models\" -name \"*.cs\" -type f -exec grep -h \"^namespace\" {})", + "Bash(find \"C:\\\\Users\\\\User\\\\source\\\\repos\\\\sdk-net/src/\" -name \"*.cs\" -type f -exec grep -l \"JsonConverter\\\\|JsonSerializable\" {})", + "Bash(ls /c/Users/User/source/repos/sdk-net/src/ServerlessWorkflow.Sdk/bin/*/net*/)", + "Bash(ls /c/Users/User/source/repos/sdk-net/src/ServerlessWorkflow.Sdk/bin/Release/net9.0/*.dll)", + "Bash(ls /c/Users/User/source/repos/sdk-net/src/ServerlessWorkflow.Sdk/bin/Release/net8.0/*.dll)", + "Bash(dotnet nuget:*)", + "Bash(dotnet package:*)", + "Bash(find \"C:\\\\Users\\\\User\\\\source\\\\repos\\\\sdk-net\\\\src\\\\ServerlessWorkflow.Sdk\" -name \"*.cs\" -type f -exec grep -l \"EquatableDictionary\" {})", + "Bash(xargs -I {} head -50 {})", + "Bash(dotnet run:*)", + "Bash(dotnet list:*)", + "Bash(find:*)", + "Bash(grep -r \"public.*class\" /c/Dev/ServerlessWorkflow/sdk-net/src/ServerlessWorkflow.Sdk.Runtime/Services/*.cs)", + "Bash(cp -r /c/Users/User/source/repos/sdk-net/src/ServerlessWorkflow.Sdk.Builders/Interfaces /c/Dev/ServerlessWorkflow/sdk-net/src/ServerlessWorkflow.Sdk.Builders/Interfaces)", + "Bash(cp /c/Users/User/source/repos/sdk-net/src/ServerlessWorkflow.Sdk.Builders/*.cs /c/Dev/ServerlessWorkflow/sdk-net/src/ServerlessWorkflow.Sdk.Builders/)", + "Bash(grep -oP '[^\\\\\\\\]+\\\\.cs')", + "Bash(dotnet add:*)", + "Bash(taskkill //F //PID 8608)", + "Bash(taskkill //F //IM ServerlessWorkflow.Sdk.UnitTests.exe)", + "Bash(ls src/ServerlessWorkflow.Sdk.Builders/*.cs)", + "Bash(ls tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/*.cs)", + "Bash(grep -v '//')", + "Bash(grep -v '^\\\\s*//')", + "Bash(ls tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/*.cs)", + "Bash(sed 's|.*/||')", + "Bash(ls src/ServerlessWorkflow.Sdk.IO/*.cs)", + "Bash(ls src/ServerlessWorkflow.Sdk.Runtime.Abstractions/*.cs src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/*.cs)", + "Bash(taskkill /F /PID 14212)", + "Bash(taskkill /F /IM \"ServerlessWorkflow.Sdk.UnitTests.exe\")", + "Bash(cmd.exe:*)", + "Bash(grep -i \"\\\\.cs$\")", + "Bash(taskkill //F //IM \"ServerlessWorkflow.Sdk.UnitTests.exe\")", + "Bash(taskkill //F //IM \"testhost.exe\")", + "Bash(grep -n \"class.*Expressions\\\\|EvaluateAsync\\\\|EvaluateConditionAsync\" /c/Dev/ServerlessWorkflow/sdk-net/src/ServerlessWorkflow.Sdk.Runtime/Services/*.cs)", + "Bash(grep -rn \"JsonPointer\\\\.\" C:/Dev/ServerlessWorkflow/sdk-net/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/*.cs)", + "Bash(taskkill //f //im \"ServerlessWorkflow.Sdk.UnitTests.exe\")", + "Bash(taskkill //f //im dotnet.exe)", + "Bash(git -C \"C:/Dev/CNCF/ServerlessWorkflow/sdk-net\" status -s)", + "Bash(git:*)", + "Bash(sed:*)", + "Bash(grep -l \"IWorkflowRuntime\\\\|IWorkflowDefinitionStore\\\\|IWorkflowProcess\" \"C:\\\\Dev\\\\ServerlessWorkflow\\\\sdk-net\\\\src\\\\ServerlessWorkflow.Sdk.Runtime\"/**/*.cs)" + ] + } +} diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 4ad7306..852709d 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -11,7 +11,7 @@ on: workflow_call: env: - SOLUTION: ./ServerlessWorkflow.Sdk.sln + SOLUTION: ./ServerlessWorkflow.Sdk.slnx jobs: build: @@ -32,8 +32,7 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: | - 8.0.x - 9.0.x + 10.0.x - name: Restore dependencies run: dotnet restore "${{ env.SOLUTION }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 92fcc69..1c22c8f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: env: - SOLUTION: ./ServerlessWorkflow.Sdk.sln + SOLUTION: ./ServerlessWorkflow.Sdk.slnx REGISTRY: ghcr.io jobs: @@ -18,8 +18,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: | - 8.0.x - 9.0.x + 10.0.x - name: Restore dependencies run: dotnet restore "${{ env.SOLUTION }}" - name: Build diff --git a/.github/workflows/test-dotnet.yml b/.github/workflows/test-dotnet.yml index 88a2a30..ee82637 100644 --- a/.github/workflows/test-dotnet.yml +++ b/.github/workflows/test-dotnet.yml @@ -11,7 +11,7 @@ on: workflow_call: env: - SOLUTION: ./ServerlessWorkflow.Sdk.sln + SOLUTION: ./ServerlessWorkflow.Sdk.slnx jobs: build: @@ -24,8 +24,7 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: | - 8.0.x - 9.0.x + 10.0.x - name: Restore dependencies run: dotnet restore "${{ env.SOLUTION }}" diff --git a/code-of-conduct.md b/CODE_OF_CONDUCT.md similarity index 100% rename from code-of-conduct.md rename to CODE_OF_CONDUCT.md diff --git a/ServerlessWorkflow.Sdk.sln b/ServerlessWorkflow.Sdk.sln deleted file mode 100644 index efd9280..0000000 --- a/ServerlessWorkflow.Sdk.sln +++ /dev/null @@ -1,87 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.4.33213.308 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9016CF88-4100-425F-9E1A-B6099F55A35B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{60FE2678-84CF-492C-950D-3485582F6712}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0CD38EC5-C4AB-491A-B6EA-D6C608F68157}" - ProjectSection(SolutionItems) = preProject - code-of-conduct.md = code-of-conduct.md - CONTRIBUTING.md = CONTRIBUTING.md - LICENSE = LICENSE - MAINTAINERS.md = MAINTAINERS.md - maintainer_guidelines.md = maintainer_guidelines.md - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{F3B6D944-46DA-4CAF-A8BE-0C8F230869F9}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerlessWorkflow.Sdk", "src\ServerlessWorkflow.Sdk\ServerlessWorkflow.Sdk.csproj", "{8D4E5FB8-79BC-442B-852F-5891B39A5A1C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerlessWorkflow.Sdk.Builders", "src\ServerlessWorkflow.Sdk.Builders\ServerlessWorkflow.Sdk.Builders.csproj", "{53A4A0D8-E2F4-43BC-808F-37B1EB7837DE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerlessWorkflow.Sdk.IO", "src\ServerlessWorkflow.Sdk.IO\ServerlessWorkflow.Sdk.IO.csproj", "{9993989F-B8D6-481C-A59C-A76070CA32F4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerlessWorkflow.Sdk.UnitTests", "tests\ServerlessWorkflow.Sdk.UnitTests\ServerlessWorkflow.Sdk.UnitTests.csproj", "{7BFC0DDB-7864-4C5A-AC91-EB7B3E93242E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A8E842C3-60C4-46A7-A2D7-57C28BA151E6}" - ProjectSection(SolutionItems) = preProject - .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{3327DDB8-87C1-4A08-9221-A8CD1450DD12}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{8BA94EDE-CE8B-4476-A58F-50162B06EE71}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build-dotnet.yml = .github\workflows\build-dotnet.yml - .github\workflows\ci-pipeline.yml = .github\workflows\ci-pipeline.yml - .github\workflows\publish.yml = .github\workflows\publish.yml - .github\workflows\release.yml = .github\workflows\release.yml - .github\workflows\test-dotnet.yml = .github\workflows\test-dotnet.yml - .github\workflows\versioning.yml = .github\workflows\versioning.yml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8D4E5FB8-79BC-442B-852F-5891B39A5A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D4E5FB8-79BC-442B-852F-5891B39A5A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D4E5FB8-79BC-442B-852F-5891B39A5A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D4E5FB8-79BC-442B-852F-5891B39A5A1C}.Release|Any CPU.Build.0 = Release|Any CPU - {53A4A0D8-E2F4-43BC-808F-37B1EB7837DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {53A4A0D8-E2F4-43BC-808F-37B1EB7837DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53A4A0D8-E2F4-43BC-808F-37B1EB7837DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53A4A0D8-E2F4-43BC-808F-37B1EB7837DE}.Release|Any CPU.Build.0 = Release|Any CPU - {9993989F-B8D6-481C-A59C-A76070CA32F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9993989F-B8D6-481C-A59C-A76070CA32F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9993989F-B8D6-481C-A59C-A76070CA32F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9993989F-B8D6-481C-A59C-A76070CA32F4}.Release|Any CPU.Build.0 = Release|Any CPU - {7BFC0DDB-7864-4C5A-AC91-EB7B3E93242E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7BFC0DDB-7864-4C5A-AC91-EB7B3E93242E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7BFC0DDB-7864-4C5A-AC91-EB7B3E93242E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7BFC0DDB-7864-4C5A-AC91-EB7B3E93242E}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {8D4E5FB8-79BC-442B-852F-5891B39A5A1C} = {9016CF88-4100-425F-9E1A-B6099F55A35B} - {53A4A0D8-E2F4-43BC-808F-37B1EB7837DE} = {9016CF88-4100-425F-9E1A-B6099F55A35B} - {9993989F-B8D6-481C-A59C-A76070CA32F4} = {9016CF88-4100-425F-9E1A-B6099F55A35B} - {7BFC0DDB-7864-4C5A-AC91-EB7B3E93242E} = {60FE2678-84CF-492C-950D-3485582F6712} - {3327DDB8-87C1-4A08-9221-A8CD1450DD12} = {A8E842C3-60C4-46A7-A2D7-57C28BA151E6} - {8BA94EDE-CE8B-4476-A58F-50162B06EE71} = {A8E842C3-60C4-46A7-A2D7-57C28BA151E6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1402FB0B-4169-41A6-A372-DA260E79481B} - EndGlobalSection -EndGlobal diff --git a/ServerlessWorkflow.Sdk.slnx b/ServerlessWorkflow.Sdk.slnx new file mode 100644 index 0000000..e12f554 --- /dev/null +++ b/ServerlessWorkflow.Sdk.slnx @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.editorconfig b/editorconfig similarity index 100% rename from .editorconfig rename to editorconfig diff --git a/src/ServerlessWorkflow.Sdk.Builders/AuthenticationPolicyDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/AuthenticationPolicyDefinitionBuilder.cs index 78eb715..275a183 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/AuthenticationPolicyDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/AuthenticationPolicyDefinitionBuilder.cs @@ -16,83 +16,76 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class AuthenticationPolicyDefinitionBuilder +public sealed class AuthenticationPolicyDefinitionBuilder : IAuthenticationPolicyDefinitionBuilder { - /// - /// Gets/sets the name of the to use, if any - /// - protected string? Policy { get; set; } - - /// - /// Gets/sets the to use - /// - protected IAuthenticationSchemeDefinitionBuilder? SchemeBuilder { get; set; } + string? policy; + IAuthenticationSchemeDefinitionBuilder? schemeBuilder; /// - public virtual void Use(string policy) + public void Use(string policy) { ArgumentException.ThrowIfNullOrWhiteSpace(policy); - this.Policy = policy; + this.policy = policy; } /// - public virtual IBasicAuthenticationSchemeDefinitionBuilder Basic() + public IBasicAuthenticationSchemeDefinitionBuilder Basic() { var builder = new BasicAuthenticationSchemeDefinitionBuilder(); - this.SchemeBuilder = builder; + schemeBuilder = builder; return builder; } /// - public virtual IBearerAuthenticationSchemeDefinitionBuilder Bearer() + public IBearerAuthenticationSchemeDefinitionBuilder Bearer() { var builder = new BearerAuthenticationSchemeDefinitionBuilder(); - this.SchemeBuilder = builder; + schemeBuilder = builder; return builder; } /// - public virtual ICertificateAuthenticationSchemeDefinitionBuilder Certificate() + public ICertificateAuthenticationSchemeDefinitionBuilder Certificate() { var builder = new CertificateAuthenticationSchemeDefinitionBuilder(); - this.SchemeBuilder = builder; + schemeBuilder = builder; return builder; } /// - public virtual IDigestAuthenticationSchemeDefinitionBuilder Digest() + public IDigestAuthenticationSchemeDefinitionBuilder Digest() { var builder = new DigestAuthenticationSchemeDefinitionBuilder(); - this.SchemeBuilder = builder; + schemeBuilder = builder; return builder; } /// - public virtual IOAuth2AuthenticationSchemeDefinitionBuilder OAuth2() + public IOAuth2AuthenticationSchemeDefinitionBuilder OAuth2() { var builder = new OAuth2AuthenticationSchemeDefinitionBuilder(); - this.SchemeBuilder = builder; + schemeBuilder = builder; return builder; } /// - public virtual IOpenIDConnectAuthenticationSchemeDefinitionBuilder OpenIDConnect() + public IOpenIDConnectAuthenticationSchemeDefinitionBuilder OpenIDConnect() { var builder = new OpenIDConnectAuthenticationSchemeDefinitionBuilder(); - this.SchemeBuilder = builder; + schemeBuilder = builder; return builder; } /// - public virtual AuthenticationPolicyDefinition Build() + public AuthenticationPolicyDefinition Build() { - if (this.SchemeBuilder == null) throw new NullReferenceException("The authentication scheme must be set"); - var scheme = this.SchemeBuilder.Build(); + if (schemeBuilder == null) throw new NullReferenceException("The authentication scheme must be set"); + var scheme = schemeBuilder.Build(); return new() { - Use = this.Policy, + Use = policy, Basic = scheme is BasicAuthenticationSchemeDefinition basic ? basic : null, Bearer = scheme is BearerAuthenticationSchemeDefinition bearer ? bearer : null, OAuth2 = scheme is OAuth2AuthenticationSchemeDefinition oauth2 ? oauth2 : null, diff --git a/src/ServerlessWorkflow.Sdk.Builders/AuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/AuthenticationSchemeDefinitionBuilder.cs index 8a423ff..fb5d302 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/AuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/AuthenticationSchemeDefinitionBuilder.cs @@ -31,12 +31,12 @@ public abstract class AuthenticationSchemeDefinitionBuilder public virtual void Use(string secret) { ArgumentException.ThrowIfNullOrWhiteSpace(secret); - this.Secret = secret; + Secret = secret; } /// public abstract TDefinition Build(); - AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => this.Build(); + AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/BackoffStrategyDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/BackoffStrategyDefinitionBuilder.cs index d117801..f7a33fb 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/BackoffStrategyDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/BackoffStrategyDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,49 +16,47 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class BackoffStrategyDefinitionBuilder +public sealed class BackoffStrategyDefinitionBuilder : IBackoffStrategyDefinitionBuilder { - /// - /// Gets the underlying service used to build the to use - /// - protected IBackoffDefinitionBuilder? Backoff { get; set; } + IBackoffDefinitionBuilder? backoff; /// - public virtual IConstantBackoffDefinitionBuilder Constant() + public IConstantBackoffDefinitionBuilder Constant() { var builder = new ConstantBackoffDefinitionBuilder(); - this.Backoff = builder; + backoff = builder; return builder; } /// - public virtual IExponentialBackoffDefinitionBuilder Exponential() + public IExponentialBackoffDefinitionBuilder Exponential() { var builder = new ExponentialBackoffDefinitionBuilder(); - this.Backoff = builder; + backoff = builder; return builder; } /// - public virtual ILinearBackoffDefinitionBuilder Linear(Duration? increment = null) + public ILinearBackoffDefinitionBuilder Linear(Duration? increment = null) { - var builder = new LinearBackoffDefinitionBuilder(); - this.Backoff = builder; + var builder = new LinearBackoffDefinitionBuilder(increment); + backoff = builder; return builder; } /// - public virtual BackoffStrategyDefinition Build() + public BackoffStrategyDefinition Build() { - if (this.Backoff == null) throw new NullReferenceException("The backoff strategy must be set"); + if (backoff == null) throw new NullReferenceException("The backoff strategy must be set"); + var definition = backoff.Build(); return new() { - Constant = this.Backoff is ConstantBackoffDefinition constant ? constant : null, - Exponential = this.Backoff is ExponentialBackoffDefinition exponential ? exponential : null, - Linear = this.Backoff is LinearBackoffDefinition linear ? linear : null, + Constant = definition is ConstantBackoffDefinition constant ? constant : null, + Exponential = definition is ExponentialBackoffDefinition exponential ? exponential : null, + Linear = definition is LinearBackoffDefinition linear ? linear : null, }; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/BasicAuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/BasicAuthenticationSchemeDefinitionBuilder.cs index f022aa3..b371a45 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/BasicAuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/BasicAuthenticationSchemeDefinitionBuilder.cs @@ -16,49 +16,42 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class BasicAuthenticationSchemeDefinitionBuilder +public sealed class BasicAuthenticationSchemeDefinitionBuilder : AuthenticationSchemeDefinitionBuilder, IBasicAuthenticationSchemeDefinitionBuilder { - /// - /// Gets/sets the username to use - /// - protected string? Username { get; set; } - - /// - /// Gets/sets the password to use - /// - protected string? Password { get; set; } + string? username; + string? password; /// - public virtual IBasicAuthenticationSchemeDefinitionBuilder WithUsername(string username) + public IBasicAuthenticationSchemeDefinitionBuilder WithUsername(string username) { ArgumentException.ThrowIfNullOrWhiteSpace(username); - this.Username = username; + this.username = username; return this; } /// - public virtual IBasicAuthenticationSchemeDefinitionBuilder WithPassword(string password) + public IBasicAuthenticationSchemeDefinitionBuilder WithPassword(string password) { ArgumentException.ThrowIfNullOrWhiteSpace(password); - this.Password = password; + this.password = password; return this; } /// public override BasicAuthenticationSchemeDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Username)) throw new NullReferenceException("The username must be set"); - if (string.IsNullOrWhiteSpace(this.Password)) throw new NullReferenceException("The password must be set"); + if (string.IsNullOrWhiteSpace(username)) throw new NullReferenceException("The username must be set"); + if (string.IsNullOrWhiteSpace(password)) throw new NullReferenceException("The password must be set"); return new() { - Use = this.Secret, - Username = this.Username, - Password = this.Password + Use = Secret, + Username = username, + Password = password }; } - AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => this.Build(); + AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/BearerAuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/BearerAuthenticationSchemeDefinitionBuilder.cs index 1975575..1393c94 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/BearerAuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/BearerAuthenticationSchemeDefinitionBuilder.cs @@ -16,34 +16,31 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class BearerAuthenticationSchemeDefinitionBuilder +public sealed class BearerAuthenticationSchemeDefinitionBuilder : AuthenticationSchemeDefinitionBuilder, IBearerAuthenticationSchemeDefinitionBuilder { - /// - /// Gets/sets the bearer token to use - /// - protected string? Token { get; set; } + string? token; /// - public virtual IBearerAuthenticationSchemeDefinitionBuilder WithToken(string token) + public IBearerAuthenticationSchemeDefinitionBuilder WithToken(string token) { ArgumentException.ThrowIfNullOrWhiteSpace(token); - this.Token = token; + this.token = token; return this; } /// public override BearerAuthenticationSchemeDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Token)) throw new NullReferenceException("The token must be set"); + if (string.IsNullOrWhiteSpace(token)) throw new NullReferenceException("The token must be set"); return new() { - Use = this.Secret, - Token = this.Token + Use = Secret, + Token = token }; } - AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => this.Build(); + AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/CallTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/CallTaskDefinitionBuilder.cs index c16f7cf..f4295b6 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/CallTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/CallTaskDefinitionBuilder.cs @@ -11,61 +11,52 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// /// The name of the function to call -public class CallTaskDefinitionBuilder(string? functionName = null) +public sealed class CallTaskDefinitionBuilder(string? functionName = null) : TaskDefinitionBuilder, ICallTaskDefinitionBuilder { - /// - /// Gets the name of the function to call - /// - protected virtual string? FunctionName { get; set; } = functionName; - - /// - /// Gets a name/value mapping of the function's arguments, if any - /// - protected virtual EquatableDictionary? FunctionArguments { get; set; } + string? functionName = functionName; + JsonObject? functionArguments; /// - public virtual ICallTaskDefinitionBuilder Function(string name) + public ICallTaskDefinitionBuilder Function(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.FunctionName = name; + functionName = name; return this; } /// - public virtual ICallTaskDefinitionBuilder With(string name, object value) + public ICallTaskDefinitionBuilder With(string name, JsonNode value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.FunctionArguments ??= []; - this.FunctionArguments[name] = value; + functionArguments ??= []; + functionArguments[name] = value; return this; } /// - public virtual ICallTaskDefinitionBuilder With(IDictionary arguments) + public ICallTaskDefinitionBuilder With(JsonObject arguments) { ArgumentNullException.ThrowIfNull(arguments); - this.FunctionArguments = new(arguments); + functionArguments = arguments; return this; } /// public override CallTaskDefinition Build() { - if (string.IsNullOrWhiteSpace(this.FunctionName)) throw new NullReferenceException("The function to call is required"); - return this.Configure(new() + if (string.IsNullOrWhiteSpace(functionName)) throw new NullReferenceException("The function to call is required"); + return Configure(new() { - Call = this.FunctionName, - With = this.FunctionArguments, + Call = functionName, + With = functionArguments, }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/CertificateAuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/CertificateAuthenticationSchemeDefinitionBuilder.cs index 688dc4a..5662f62 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/CertificateAuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/CertificateAuthenticationSchemeDefinitionBuilder.cs @@ -16,7 +16,7 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class CertificateAuthenticationSchemeDefinitionBuilder +public sealed class CertificateAuthenticationSchemeDefinitionBuilder : AuthenticationSchemeDefinitionBuilder, ICertificateAuthenticationSchemeDefinitionBuilder { @@ -25,10 +25,10 @@ public override CertificateAuthenticationSchemeDefinition Build() { return new() { - Use = this.Secret + Use = Secret }; } - AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => this.Build(); + AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => Build(); } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/ConstantBackoffDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ConstantBackoffDefinitionBuilder.cs index 4c0de1c..2b221c3 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ConstantBackoffDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ConstantBackoffDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,16 +16,13 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ConstantBackoffDefinitionBuilder +public sealed class ConstantBackoffDefinitionBuilder : IConstantBackoffDefinitionBuilder { /// - public virtual ConstantBackoffDefinition Build() => new() - { - - }; + public ConstantBackoffDefinition Build() => new() { }; - BackoffDefinition IBackoffDefinitionBuilder.Build() => this.Build(); + BackoffDefinition IBackoffDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ContainerProcessDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ContainerProcessDefinitionBuilder.cs index 60355d3..4096bf1 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ContainerProcessDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ContainerProcessDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -12,134 +12,110 @@ // limitations under the License. using ServerlessWorkflow.Sdk.Models.Processes; -using Neuroglia; namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ContainerProcessDefinitionBuilder +public sealed class ContainerProcessDefinitionBuilder : ProcessDefinitionBuilder, IContainerProcessDefinitionBuilder { - /// - /// Gets/sets the name of the container image to run - /// - protected virtual string? Image { get; set; } - - /// - /// Gets/sets the name of the container to run - /// - protected virtual string? Name { get; set; } - - /// - /// Gets/sets the command, if any, to execute on the container - /// - protected virtual string? Command { get; set; } - - /// - /// Gets/sets a list containing the container's port mappings, if any - /// - protected virtual EquatableDictionary? Ports { get; set; } - - /// - /// Gets/sets the volumes mapping for the container, if any - /// - protected virtual EquatableDictionary? Volumes { get; set; } - - /// - /// Gets/sets a key/value mapping of the environment variables, if any, to use when running the configured process - /// - protected virtual EquatableDictionary? Environment { get; set; } + string? image; + string? name; + string? command; + EquatableDictionary? ports; + EquatableDictionary? volumes; + EquatableDictionary? environment; /// - public virtual IContainerProcessDefinitionBuilder WithImage(string image) + public IContainerProcessDefinitionBuilder WithImage(string image) { ArgumentException.ThrowIfNullOrWhiteSpace(image); - this.Image = image; + this.image = image; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithName(string name) + public IContainerProcessDefinitionBuilder WithName(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Name = name; + this.name = name; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithCommand(string command) + public IContainerProcessDefinitionBuilder WithCommand(string command) { ArgumentException.ThrowIfNullOrWhiteSpace(command); - this.Command = command; + this.command = command; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithPort(ushort hostPort, ushort containerPort) + public IContainerProcessDefinitionBuilder WithPort(ushort hostPort, ushort containerPort) { - this.Ports ??= []; - this.Ports[hostPort] = containerPort; + ports ??= []; + ports[hostPort] = containerPort; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithPorts(IDictionary portMapping) + public IContainerProcessDefinitionBuilder WithPorts(IDictionary portMapping) { ArgumentNullException.ThrowIfNull(portMapping); - this.Ports = new(portMapping); + ports = [.. portMapping]; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithVolume(string key, string value) + public IContainerProcessDefinitionBuilder WithVolume(string key, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(key); - this.Volumes ??= []; - this.Volumes[key] = value; + volumes ??= []; + volumes[key] = value; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithVolumes(IDictionary volumes) + public IContainerProcessDefinitionBuilder WithVolumes(IDictionary volumes) { ArgumentNullException.ThrowIfNull(volumes); - this.Volumes = new(volumes); + this.volumes = [.. volumes]; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithEnvironment(string name, string value) + public IContainerProcessDefinitionBuilder WithEnvironment(string name, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.Environment ??= []; - this.Environment[name] = value; + environment ??= []; + environment[name] = value; return this; } /// - public virtual IContainerProcessDefinitionBuilder WithEnvironment(IDictionary environment) + public IContainerProcessDefinitionBuilder WithEnvironment(IDictionary environment) { ArgumentNullException.ThrowIfNull(environment); - this.Environment = new(environment); + this.environment = [.. environment]; return this; } /// public override ContainerProcessDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Image)) throw new NullReferenceException("The image of the container to run must be set"); + if (string.IsNullOrWhiteSpace(image)) throw new NullReferenceException("The image of the container to run must be set"); return new() { - Image = this.Image, - Name = this.Name, - Command = this.Command, - Ports = this.Ports, - Volumes = this.Volumes, - Environment = this.Environment + Image = image, + Name = name, + Command = command, + Ports = ports, + Volumes = volumes, + Environment = environment }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/DigestAuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/DigestAuthenticationSchemeDefinitionBuilder.cs index a239377..7a51d9a 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/DigestAuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/DigestAuthenticationSchemeDefinitionBuilder.cs @@ -16,49 +16,42 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class DigestAuthenticationSchemeDefinitionBuilder +public sealed class DigestAuthenticationSchemeDefinitionBuilder : AuthenticationSchemeDefinitionBuilder, IDigestAuthenticationSchemeDefinitionBuilder { - /// - /// Gets/sets the username to use - /// - protected string? Username { get; set; } - - /// - /// Gets/sets the password to use - /// - protected string? Password { get; set; } + string? username; + string? password; /// - public virtual IDigestAuthenticationSchemeDefinitionBuilder WithUsername(string username) + public IDigestAuthenticationSchemeDefinitionBuilder WithUsername(string username) { ArgumentException.ThrowIfNullOrWhiteSpace(username); - this.Username = username; + this.username = username; return this; } /// - public virtual IDigestAuthenticationSchemeDefinitionBuilder WithPassword(string password) + public IDigestAuthenticationSchemeDefinitionBuilder WithPassword(string password) { ArgumentException.ThrowIfNullOrWhiteSpace(password); - this.Password = password; + this.password = password; return this; } /// public override DigestAuthenticationSchemeDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Username)) throw new NullReferenceException("The username must be set"); - if (string.IsNullOrWhiteSpace(this.Password)) throw new NullReferenceException("The password must be set"); + if (string.IsNullOrWhiteSpace(username)) throw new NullReferenceException("The username must be set"); + if (string.IsNullOrWhiteSpace(password)) throw new NullReferenceException("The password must be set"); return new() { - Use = this.Secret, - Username = this.Username, - Password = this.Password + Use = Secret, + Username = username, + Password = password }; } - AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => this.Build(); + AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/DoTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/DoTaskDefinitionBuilder.cs index 858b17a..2d351c6 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/DoTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/DoTaskDefinitionBuilder.cs @@ -16,32 +16,29 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class DoTaskDefinitionBuilder +public sealed class DoTaskDefinitionBuilder : TaskDefinitionBuilder, IDoTaskDefinitionBuilder { - /// - /// Gets/sets a name/definition mapping of the tasks to execute sequentially, if any - /// - protected Map? Tasks { get; set; } + Map? tasks; /// - public virtual IDoTaskDefinitionBuilder Do(Action setup) + public IDoTaskDefinitionBuilder Do(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.Tasks = builder.Build(); + tasks = builder.Build(); return this; } /// public override DoTaskDefinition Build() { - if (this.Tasks == null || this.Tasks.Count < 2) throw new NullReferenceException("The execution strategy must define at least two subtasks"); - return this.Configure(new() + if (tasks == null || tasks.Count < 2) throw new NullReferenceException("The execution strategy must define at least two subtasks"); + return Configure(new() { - Do = this.Tasks + Do = tasks }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/EmitTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/EmitTaskDefinitionBuilder.cs index c686ccc..954b33f 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/EmitTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/EmitTaskDefinitionBuilder.cs @@ -17,42 +17,39 @@ namespace ServerlessWorkflow.Sdk.Builders; /// Represents the default implementation of the interface /// /// The definition of the event to emit -public class EmitTaskDefinitionBuilder(EventDefinition? e = null) +public sealed class EmitTaskDefinitionBuilder(EventDefinition? e = null) : TaskDefinitionBuilder, IEmitTaskDefinitionBuilder { - /// - /// Gets/sets the definition of the event to emit - /// - protected virtual EventDefinition? EventDefinition { get; set; } = e; + EventDefinition? eventDefinition = e; /// - public virtual IEmitTaskDefinitionBuilder Event(EventDefinition e) + public IEmitTaskDefinitionBuilder Event(EventDefinition e) { ArgumentNullException.ThrowIfNull(e); - this.EventDefinition = e; + eventDefinition = e; return this; } /// - public virtual IEmitTaskDefinitionBuilder Event(Action setup) + public IEmitTaskDefinitionBuilder Event(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new EventDefinitionBuilder(); setup(builder); - this.EventDefinition = builder.Build(); + eventDefinition = builder.Build(); return this; } /// public override EmitTaskDefinition Build() { - if (this.EventDefinition == null) throw new NullReferenceException("The event to emit must be defined"); - return this.Configure(new() + if (eventDefinition == null) throw new NullReferenceException("The event to emit must be defined"); + return Configure(new() { Emit = new() { - Event = this.EventDefinition + Event = eventDefinition } }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/EndpointDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/EndpointDefinitionBuilder.cs index fb7bf72..e7ce7b6 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/EndpointDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/EndpointDefinitionBuilder.cs @@ -16,69 +16,67 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class EndpointDefinitionBuilder +public sealed class EndpointDefinitionBuilder : IEndpointDefinitionBuilder { - /// - /// Gets/sets the uri that references the external resource - /// - protected virtual Uri? Uri { get; set; } - - /// - /// Gets/sets a reference to the authentication policy to use - /// - protected virtual Uri? AuthenticationReference { get; set; } - - /// - /// Gets/sets the authentication policy to use - /// - protected virtual AuthenticationPolicyDefinition? Authentication { get; set; } + Uri? uri; + Uri? authenticationReference; + AuthenticationPolicyDefinition? authentication; /// - public virtual IEndpointDefinitionBuilder WithUri(Uri uri) + public IEndpointDefinitionBuilder WithUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); - this.Uri = uri; + this.uri = uri; return this; } /// - public virtual IEndpointDefinitionBuilder UseAuthentication(Uri reference) + public IEndpointDefinitionBuilder UseAuthentication(Uri reference) { ArgumentNullException.ThrowIfNull(reference); - this.AuthenticationReference = reference; + authenticationReference = reference; return this; } /// - public virtual IEndpointDefinitionBuilder UseAuthentication(AuthenticationPolicyDefinition authentication) + public IEndpointDefinitionBuilder UseAuthentication(AuthenticationPolicyDefinition authentication) { ArgumentNullException.ThrowIfNull(authentication); - this.Authentication = authentication; + this.authentication = authentication; return this; } /// - public virtual IEndpointDefinitionBuilder UseAuthentication(Action setup) + public IEndpointDefinitionBuilder UseAuthentication(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new AuthenticationPolicyDefinitionBuilder(); setup(builder); - this.Authentication = builder.Build(); + authentication = builder.Build(); return this; } /// - public virtual EndpointDefinition Build() + public EndpointDefinition Build() { - if (this.Uri == null) throw new NullReferenceException("The uri that references the external resource must be set"); + if (uri == null) throw new NullReferenceException("The uri that references the external resource must be set"); var endpoint = new EndpointDefinition() { - Uri = this.Uri + Uri = uri + }; + if (authenticationReference == null) endpoint = endpoint with + { + Authentication = new() + { + Ref = authenticationReference + } + }; + else if (authentication != null) endpoint = endpoint with + { + Authentication = authentication }; - if (this.AuthenticationReference == null) endpoint.Authentication = new() { Ref = this.AuthenticationReference }; - else if (this.Authentication != null) endpoint.Authentication = this.Authentication; return endpoint; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ErrorCatcherDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ErrorCatcherDefinitionBuilder.cs index bedd085..02c7d4a 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ErrorCatcherDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ErrorCatcherDefinitionBuilder.cs @@ -16,104 +16,79 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ErrorCatcherDefinitionBuilder +public sealed class ErrorCatcherDefinitionBuilder : IErrorCatcherDefinitionBuilder { - /// - /// Gets/sets the definition of the errors to catch - /// - protected ErrorFilterDefinition? CatchErrors { get; set; } - - /// - /// Gets/sets the name of the runtime expression variable to save the error as. Defaults to 'error'. - /// - protected string? CatchAs { get; set; } - - /// - /// Gets/sets a runtime expression used to determine whether or not to catch the filtered error - /// - protected string? CatchWhen { get; set; } - - /// - /// Gets/sets a runtime expression used to determine whether or not to catch the filtered error - /// - protected string? CatchExceptWhen { get; set; } - - /// - /// Gets/sets a reference to the definition of the retry policy to use when catching errors - /// - protected Uri? RetryPolicyReference { get; set; } - - /// - /// Gets/sets the definition of the retry policy to use when catching errors - /// - protected RetryPolicyDefinition? RetryPolicy { get; set; } - - /// - /// Gets/sets the definition of the task to run when catching an error - /// - protected Map? RetryDo { get; set; } + ErrorFilterDefinition? catchErrors; + string? catchAs; + string? catchWhen; + string? catchExceptWhen; + OneOf? retryPolicy; + Map? retryDo; /// - public virtual IErrorCatcherDefinitionBuilder Errors(ErrorFilterDefinition filter) + public IErrorCatcherDefinitionBuilder Errors(ErrorFilterDefinition filter) { ArgumentNullException.ThrowIfNull(filter); - this.CatchErrors = filter; + catchErrors = filter; return this; } /// - public virtual IErrorCatcherDefinitionBuilder Errors(Action setup) + public IErrorCatcherDefinitionBuilder Errors(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ErrorFilterDefinitionBuilder(); setup(builder); - return this.Errors(builder.Build()); + return Errors(builder.Build()); } /// - public virtual IErrorCatcherDefinitionBuilder As(string variableName) + public IErrorCatcherDefinitionBuilder As(string variableName) { - this.CatchAs = variableName; + catchAs = variableName; return this; } /// - public virtual IErrorCatcherDefinitionBuilder When(string expression) + public IErrorCatcherDefinitionBuilder When(string expression) { - this.CatchWhen = expression; + catchWhen = expression; return this; } /// - public virtual IErrorCatcherDefinitionBuilder ExceptWhen(string expression) + public IErrorCatcherDefinitionBuilder ExceptWhen(string expression) { - this.CatchExceptWhen = expression; + catchExceptWhen = expression; return this; } /// - public virtual IErrorCatcherDefinitionBuilder Retry(Uri reference) + public IErrorCatcherDefinitionBuilder Retry(Uri reference) { - this.RetryPolicyReference = reference; + retryPolicy = new RetryPolicyDefinition() + { + Ref = reference + }; return this; } /// - public virtual IErrorCatcherDefinitionBuilder Retry(RetryPolicyDefinition retryPolicy) + public IErrorCatcherDefinitionBuilder Retry(RetryPolicyDefinition retryPolicy) { - this.RetryPolicy = retryPolicy; + this.retryPolicy = retryPolicy; return this; } /// - public virtual IErrorCatcherDefinitionBuilder Retry(Action setup) + public IErrorCatcherDefinitionBuilder Retry(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new RetryPolicyDefinitionBuilder(); setup(builder); - return this.Retry(builder.Build()); + return Retry(builder.Build()); } /// @@ -122,19 +97,19 @@ public IErrorCatcherDefinitionBuilder Do(Action setup ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.RetryDo = builder.Build(); + retryDo = builder.Build(); return this; } /// - public virtual ErrorCatcherDefinition Build() => new() + public ErrorCatcherDefinition Build() => new() { - Errors = this.CatchErrors, - As = this.CatchAs, - When = this.CatchWhen, - ExceptWhen = this.CatchExceptWhen, - Retry = this.RetryPolicyReference == null ? this.RetryPolicy : new() { Ref = this.RetryPolicyReference }, - Do = this.RetryDo + Errors = catchErrors, + As = catchAs, + When = catchWhen, + ExceptWhen = catchExceptWhen, + Retry = retryPolicy, + Do = retryDo }; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/ErrorDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ErrorDefinitionBuilder.cs index 8bc33ee..8f1e436 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ErrorDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ErrorDefinitionBuilder.cs @@ -16,88 +16,69 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ErrorDefinitionBuilder +public sealed class ErrorDefinitionBuilder : IErrorDefinitionBuilder { - /// - /// Gets the type of the error to build - /// - protected string? Type { get; set; } - - /// - /// Gets the status of the error to build - /// - protected string? Status { get; set; } - - /// - /// Gets the title of the error to build - /// - protected string? Title { get; set; } - - /// - /// Gets the detail of the error to build - /// - protected string? Detail { get; set; } - - /// - /// Gets the instance of the error to build - /// - protected string? Instance { get; set; } + string? type; + string? status; + string? title; + string? detail; + string? instance; /// - public virtual IErrorDefinitionBuilder WithType(string type) + public IErrorDefinitionBuilder WithType(string type) { ArgumentException.ThrowIfNullOrWhiteSpace(type); - this.Type = type; + this.type = type; return this; } /// - public virtual IErrorDefinitionBuilder WithStatus(string status) + public IErrorDefinitionBuilder WithStatus(string status) { ArgumentException.ThrowIfNullOrWhiteSpace(status); - this.Status = status; + this.status = status; return this; } /// - public virtual IErrorDefinitionBuilder WithTitle(string title) + public IErrorDefinitionBuilder WithTitle(string title) { ArgumentException.ThrowIfNullOrWhiteSpace(title); - this.Title = title; + this.title = title; return this; } /// - public virtual IErrorDefinitionBuilder WithDetail(string detail) + public IErrorDefinitionBuilder WithDetail(string detail) { ArgumentException.ThrowIfNullOrWhiteSpace(detail); - this.Detail = detail; + this.detail = detail; return this; } /// - public virtual IErrorDefinitionBuilder WithInstance(string instance) + public IErrorDefinitionBuilder WithInstance(string instance) { ArgumentException.ThrowIfNullOrWhiteSpace(instance); - this.Instance = instance; + this.instance = instance; return this; } /// - public virtual ErrorDefinition Build() + public ErrorDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Type)) throw new NullReferenceException("The error type must be set"); - if (string.IsNullOrWhiteSpace(this.Title)) throw new NullReferenceException("The error title must be set"); - if (string.IsNullOrWhiteSpace(this.Status)) throw new NullReferenceException("The error status must be set"); + if (string.IsNullOrWhiteSpace(type)) throw new NullReferenceException("The error type must be set"); + if (string.IsNullOrWhiteSpace(title)) throw new NullReferenceException("The error title must be set"); + if (string.IsNullOrWhiteSpace(status)) throw new NullReferenceException("The error status must be set"); return new() { - Type = this.Type, - Status = this.Status, - Title = this.Title, - Detail = this.Detail, - Instance = this.Instance + Type = type, + Status = status, + Title = title, + Detail = detail, + Instance = instance }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ErrorFilterDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ErrorFilterDefinitionBuilder.cs index 61fe52d..821bdce 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ErrorFilterDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ErrorFilterDefinitionBuilder.cs @@ -11,40 +11,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// /// A name/value mapping of the attributes to filter errors by. Supports runtime expressions -public class ErrorFilterDefinitionBuilder(IDictionary? attributes = null) +public sealed class ErrorFilterDefinitionBuilder(JsonObject? attributes = null) : IErrorFilterDefinitionBuilder { - /// - /// Gets a name/value mapping of the attributes to filter errors by - /// - protected virtual EquatableDictionary Attributes { get; set; } = [.. attributes]; + JsonObject attributes = attributes ?? []; /// - public virtual IErrorFilterDefinitionBuilder With(string name, object value) + public IErrorFilterDefinitionBuilder With(string name, JsonNode value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Attributes[name] = value; + attributes[name] = value; return this; } /// - public virtual IErrorFilterDefinitionBuilder With(IDictionary attributes) + public IErrorFilterDefinitionBuilder With(JsonObject attributes) { ArgumentNullException.ThrowIfNull(attributes); - this.Attributes = new(attributes); + this.attributes = [..attributes]; return this; } /// - public virtual ErrorFilterDefinition Build() => new() { With = this.Attributes }; + public ErrorFilterDefinition Build() => new() + { + With = attributes + }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/EventDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/EventDefinitionBuilder.cs index 5339262..f10d3b8 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/EventDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/EventDefinitionBuilder.cs @@ -11,40 +11,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// /// A name/value mapping of the event's attributes. Supports runtime expressions -public class EventDefinitionBuilder(IDictionary? attributes = null) +public class EventDefinitionBuilder(JsonObject? attributes = null) : IEventDefinitionBuilder { - /// - /// Gets a name/value mapping of the event's attributes - /// - protected virtual EquatableDictionary Attributes { get; set; } = attributes == null ? new() : new(attributes); + JsonObject attributes = attributes ?? []; /// - public virtual IEventDefinitionBuilder With(string name, object value) + public virtual IEventDefinitionBuilder With(string name, JsonNode value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Attributes[name] = value; + attributes[name] = value; return this; } /// - public virtual IEventDefinitionBuilder With(IDictionary attributes) + public virtual IEventDefinitionBuilder With(JsonObject attributes) { ArgumentNullException.ThrowIfNull(attributes); - this.Attributes = new(attributes); + this.attributes = attributes; return this; } /// - public virtual EventDefinition Build() => new() { With = this.Attributes }; + public virtual EventDefinition Build() => new() + { + With = attributes + }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionBuilder.cs index 89aa4df..7589513 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionBuilder.cs @@ -11,40 +11,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// /// A name/value mapping of the attributes to filter events by. Supports runtime expressions -public class EventFilterDefinitionBuilder(IDictionary? attributes = null) +public sealed class EventFilterDefinitionBuilder(JsonObject? attributes = null) : IEventFilterDefinitionBuilder { - /// - /// Gets a name/value mapping of the attributes to filter errors by - /// - protected virtual EquatableDictionary Attributes { get; set; } = attributes == null ? new() : new(attributes); + JsonObject attributes = attributes ?? []; /// - public virtual IEventFilterDefinitionBuilder With(string name, object value) + public IEventFilterDefinitionBuilder With(string name, JsonNode value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Attributes[name] = value; + attributes[name] = value; return this; } /// - public virtual IEventFilterDefinitionBuilder With(IDictionary attributes) + public IEventFilterDefinitionBuilder With(JsonObject attributes) { ArgumentNullException.ThrowIfNull(attributes); - this.Attributes = new(attributes); + this.attributes = attributes; return this; } /// - public virtual EventFilterDefinition Build() => new() { With = this.Attributes }; + public EventFilterDefinition Build() => new() + { + With = attributes + }; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionCollectionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionCollectionBuilder.cs index 210481e..144a1ea 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionCollectionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/EventFilterDefinitionCollectionBuilder.cs @@ -11,48 +11,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class EventFilterDefinitionCollectionBuilder +public sealed class EventFilterDefinitionCollectionBuilder : IEventFilterDefinitionCollectionBuilder { - /// - /// Gets/sets the filters the collection to build is made out of - /// - protected EquatableList? Filters { get; set; } + EquatableList? filters; /// - public virtual IEventFilterDefinitionCollectionBuilder Event(EventFilterDefinition filter) + public IEventFilterDefinitionCollectionBuilder Event(EventFilterDefinition filter) { ArgumentNullException.ThrowIfNull(filter); - this.Filters ??= []; - this.Filters.Add(filter); + filters ??= []; + filters.Add(filter); return this; } /// - public virtual IEventFilterDefinitionCollectionBuilder Event(Action setup) + public IEventFilterDefinitionCollectionBuilder Event(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new EventFilterDefinitionBuilder(); setup(builder); var filter = builder.Build(); - this.Filters ??= []; - this.Filters.Add(filter); + filters ??= []; + filters.Add(filter); return this; } /// - public virtual EquatableList Build() + public EquatableList Build() { - if (this.Filters == null || this.Filters.Count < 1) throw new NullReferenceException("The collection must contain at least one event filter"); - return this.Filters; + if (filters == null || filters.Count < 1) throw new NullReferenceException("The collection must contain at least one event filter"); + return filters; } } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ExponentialBackoffDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ExponentialBackoffDefinitionBuilder.cs index 21343eb..2fd4350 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ExponentialBackoffDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ExponentialBackoffDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -14,18 +14,15 @@ namespace ServerlessWorkflow.Sdk.Builders; /// -/// Represents the default implementation of the interface +/// Represents the default implementation of the interface /// -public class ExponentialBackoffDefinitionBuilder +public sealed class ExponentialBackoffDefinitionBuilder : IExponentialBackoffDefinitionBuilder { /// - public virtual ExponentialBackoffDefinition Build() => new() - { + public ExponentialBackoffDefinition Build() => new() { }; - }; + BackoffDefinition IBackoffDefinitionBuilder.Build() => Build(); - BackoffDefinition IBackoffDefinitionBuilder.Build() => this.Build(); - -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/ExtensionDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ExtensionDefinitionBuilder.cs index 71a518f..b5ddee9 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ExtensionDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ExtensionDefinitionBuilder.cs @@ -16,76 +16,61 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ExtensionDefinitionBuilder +public sealed class ExtensionDefinitionBuilder : IExtensionDefinitionBuilder { - /// - /// Gets/sets the type of the extended task - /// - protected string? TaskType { get; set; } - - /// - /// Gets/sets the expression used to evaluate whether or not the extension applies - /// - protected string? WhenExpression { get; set; } - - /// - /// Gets/sets the definition of the task to run before the extended one - /// - protected Map? BeforeTasks { get; set; } - - /// - /// Gets/sets the definition of the task to run after the extended one - /// - protected Map? AfterTasks { get; set; } + string? taskType; + string? whenExpression; + Map? beforeTasks; + Map? afterTasks; /// - public virtual IExtensionDefinitionBuilder Extend(string taskType) + public IExtensionDefinitionBuilder Extend(string taskType) { ArgumentException.ThrowIfNullOrWhiteSpace(taskType); - this.TaskType = taskType; + this.taskType = taskType; return this; } /// - public virtual IExtensionDefinitionBuilder When(string when) + public IExtensionDefinitionBuilder When(string when) { ArgumentException.ThrowIfNullOrWhiteSpace(when); - this.WhenExpression = when; + whenExpression = when; return this; } /// - public virtual IExtensionDefinitionBuilder Before(Action setup) + public IExtensionDefinitionBuilder Before(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.BeforeTasks = builder.Build(); + beforeTasks = builder.Build(); return this; } /// - public virtual IExtensionDefinitionBuilder After(Action setup) + public IExtensionDefinitionBuilder After(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.AfterTasks = builder.Build(); + afterTasks = builder.Build(); return this; } /// - public virtual ExtensionDefinition Build() + public ExtensionDefinition Build() { - ArgumentException.ThrowIfNullOrWhiteSpace(this.TaskType); + ArgumentException.ThrowIfNullOrWhiteSpace(taskType); return new() { - Extend = this.TaskType, - When = this.WhenExpression, - Before = this.BeforeTasks, - After = this.AfterTasks + Extend = taskType, + When = whenExpression, + Before = beforeTasks, + After = afterTasks }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ExternalResourceDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ExternalResourceDefinitionBuilder.cs index 138b8dd..6ed2829 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ExternalResourceDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ExternalResourceDefinitionBuilder.cs @@ -16,53 +16,46 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ExternalResourceDefinitionBuilder +public sealed class ExternalResourceDefinitionBuilder : IExternalResourceDefinitionBuilder { - /// - /// Gets/sets the external resource's name - /// - protected virtual string? Name { get; set; } - - /// - /// Gets/sets the endpoint at which to get the defined resource - /// - protected virtual EndpointDefinition? Endpoint { get; set; } + string? name; + OneOf? endpoint; /// - public virtual IExternalResourceDefinitionBuilder WithName(string name) + public IExternalResourceDefinitionBuilder WithName(string name) { - this.Name = name; + this.name = name; return this; } /// - public virtual IExternalResourceDefinitionBuilder WithEndpoint(OneOf endpoint) + public IExternalResourceDefinitionBuilder WithEndpoint(OneOf endpoint) { ArgumentNullException.ThrowIfNull(endpoint); - this.Endpoint = endpoint; + this.endpoint = endpoint; return this; } /// - public virtual IExternalResourceDefinitionBuilder WithEndpoint(Action setup) + public IExternalResourceDefinitionBuilder WithEndpoint(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new EndpointDefinitionBuilder(); setup(builder); - this.Endpoint = builder.Build(); + this.endpoint = builder.Build(); return this; } /// - public virtual ExternalResourceDefinition Build() + public ExternalResourceDefinition Build() { - if (this.Endpoint == null) throw new NullReferenceException("The endpoint at which to get the defined resource must be set"); + if (endpoint == null) throw new NullReferenceException("The endpoint at which to get the defined resource must be set"); var externalResource = new ExternalResourceDefinition() { - Name = this.Name, - Endpoint = this.Endpoint + Name = name, + Endpoint = endpoint }; return externalResource; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ForTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ForTaskDefinitionBuilder.cs index 047d321..7e484f4 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ForTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ForTaskDefinitionBuilder.cs @@ -16,79 +16,64 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ForTaskDefinitionBuilder +public sealed class ForTaskDefinitionBuilder : TaskDefinitionBuilder, IForTaskDefinitionBuilder { - /// - /// Gets/sets the name of the variable that represents each element in the collection during iteration - /// - protected virtual string? EachVariableName { get; set; } - - /// - /// Gets/sets the runtime expression used to get the collection to iterate over - /// - protected virtual string? InExpression { get; set; } - - /// - /// Gets/sets the name of the variable used to hold the index of each element in the collection during iteration - /// - protected virtual string? AtVariableName { get; set; } - - /// - /// Gets/sets a name/definition map of the tasks to perform for each element in the collection to enumerate - /// - protected virtual Map? Tasks { get; set; } + string? eachVariableName; + string? inExpression; + string? atVariableName; + Map? tasks; /// - public virtual IForTaskDefinitionBuilder Each(string variableName) + public IForTaskDefinitionBuilder Each(string variableName) { ArgumentException.ThrowIfNullOrWhiteSpace(variableName); - this.EachVariableName = variableName; + eachVariableName = variableName; return this; } /// - public virtual IForTaskDefinitionBuilder In(string expression) + public IForTaskDefinitionBuilder In(string expression) { ArgumentException.ThrowIfNullOrWhiteSpace(expression); - this.InExpression = expression; + inExpression = expression; return this; } /// - public virtual IForTaskDefinitionBuilder At(string variableName) + public IForTaskDefinitionBuilder At(string variableName) { ArgumentException.ThrowIfNullOrWhiteSpace(variableName); - this.AtVariableName = variableName; + atVariableName = variableName; return this; } /// - public virtual IForTaskDefinitionBuilder Do(Action setup) + public IForTaskDefinitionBuilder Do(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.Tasks = builder.Build(); + tasks = builder.Build(); return this; } /// public override ForTaskDefinition Build() { - if (string.IsNullOrWhiteSpace(this.EachVariableName)) throw new NullReferenceException("The variable name used to store the iterated items must be set"); - if (string.IsNullOrWhiteSpace(this.InExpression)) throw new NullReferenceException("The runtime expression used to resolve the collection to iterate must be set"); - if (this.Tasks == null || this.Tasks.Count < 1) throw new NullReferenceException("The task to perform at each iteration must be set"); - return this.Configure(new() + if (string.IsNullOrWhiteSpace(eachVariableName)) throw new NullReferenceException("The variable name used to store the iterated items must be set"); + if (string.IsNullOrWhiteSpace(inExpression)) throw new NullReferenceException("The runtime expression used to resolve the collection to iterate must be set"); + if (tasks == null || tasks.Count < 1) throw new NullReferenceException("The task to perform at each iteration must be set"); + return Configure(new() { For = new() { - Each = this.EachVariableName, - In = this.InExpression, - At = this.AtVariableName + Each = eachVariableName, + In = inExpression, + At = atVariableName }, - Do = this.Tasks + Do = tasks }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ForkTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ForkTaskDefinitionBuilder.cs index 49063a8..df19d6d 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ForkTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ForkTaskDefinitionBuilder.cs @@ -16,47 +16,40 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ForkTaskDefinitionBuilder +public sealed class ForkTaskDefinitionBuilder : TaskDefinitionBuilder, IForkTaskDefinitionBuilder { - /// - /// Gets/sets a name/definition mapping of the tasks to execute concurrently, if any - /// - protected Map? Tasks { get; set; } - - /// - /// Gets/sets a boolean indicating whether or not the task to execute concurrently should compete each other - /// - protected bool ShouldCompete { get; set; } + Map? tasks; + bool shouldCompete; /// - public virtual IForkTaskDefinitionBuilder Branch(Action setup) + public IForkTaskDefinitionBuilder Branch(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.Tasks = builder.Build(); + tasks = builder.Build(); return this; } /// - public virtual IForkTaskDefinitionBuilder Compete() + public IForkTaskDefinitionBuilder Compete() { - this.ShouldCompete = true; + shouldCompete = true; return this; } /// public override ForkTaskDefinition Build() { - if (this.Tasks == null || this.Tasks.Count < 2) throw new NullReferenceException("The execution strategy must define at least two subtasks"); - return this.Configure(new() + if (tasks == null || tasks.Count < 2) throw new NullReferenceException("The execution strategy must define at least two subtasks"); + return Configure(new() { Fork = new() { - Branches = this.Tasks, - Compete = this.ShouldCompete + Branches = tasks, + Compete = shouldCompete } }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/GenericTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/GenericTaskDefinitionBuilder.cs index 7015245..1ef9ec4 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/GenericTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/GenericTaskDefinitionBuilder.cs @@ -16,150 +16,150 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class GenericTaskDefinitionBuilder +public sealed class GenericTaskDefinitionBuilder : IGenericTaskDefinitionBuilder { - /// - /// Gets the underlying - /// - protected ITaskDefinitionBuilder? Builder { get; set; } + ITaskDefinitionBuilder? builder; /// - public virtual ICallTaskDefinitionBuilder Call(string? function = null) + public ICallTaskDefinitionBuilder Call(string? function = null) { var builder = new CallTaskDefinitionBuilder(function); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IDoTaskDefinitionBuilder Do(Action setup) + public IDoTaskDefinitionBuilder Do(Action setup) { var builder = new DoTaskDefinitionBuilder(); builder.Do(setup); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IEmitTaskDefinitionBuilder Emit(EventDefinition e) + public IEmitTaskDefinitionBuilder Emit(EventDefinition e) { ArgumentNullException.ThrowIfNull(e); var builder = new EmitTaskDefinitionBuilder(e); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IEmitTaskDefinitionBuilder Emit(Action setup) + public IEmitTaskDefinitionBuilder Emit(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new EventDefinitionBuilder(); setup(builder); var e = builder.Build(); - return this.Emit(e); + return Emit(e); } /// - public virtual IForTaskDefinitionBuilder For() + public IForTaskDefinitionBuilder For() { var builder = new ForTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IForkTaskDefinitionBuilder Fork() + public IForkTaskDefinitionBuilder Fork() { var builder = new ForkTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IListenTaskDefinitionBuilder Listen() + public IListenTaskDefinitionBuilder Listen() { var builder = new ListenTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IDoTaskDefinitionBuilder Execute() + public IDoTaskDefinitionBuilder Execute() { var builder = new DoTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IRaiseTaskDefinitionBuilder Raise(ErrorDefinition error) + public IRaiseTaskDefinitionBuilder Raise(ErrorDefinition error) { ArgumentNullException.ThrowIfNull(error); var builder = new RaiseTaskDefinitionBuilder(error); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IRaiseTaskDefinitionBuilder Raise(Action setup) + public IRaiseTaskDefinitionBuilder Raise(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ErrorDefinitionBuilder(); setup(builder); var error = builder.Build(); - return this.Raise(error); + return Raise(error); } /// - public virtual IRunTaskDefinitionBuilder Run() + public IRunTaskDefinitionBuilder Run() { var builder = new RunTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual ISetTaskDefinitionBuilder Set(string name, string value) => this.Set(new Dictionary() { { name, value } }); + public ISetTaskDefinitionBuilder Set(string name, string value) => Set(new() + { + { name, value } + }); /// - public virtual ISetTaskDefinitionBuilder Set(IDictionary? variables = null) + public ISetTaskDefinitionBuilder Set(JsonObject? variables = null) { var builder = new SetTaskDefinitionBuilder(variables); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual ISwitchTaskDefinitionBuilder Switch() + public ISwitchTaskDefinitionBuilder Switch() { var builder = new SwitchTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual ITryTaskDefinitionBuilder Try() + public ITryTaskDefinitionBuilder Try() { var builder = new TryTaskDefinitionBuilder(); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual IWaitTaskDefinitionBuilder Wait(Duration? duration = null) + public IWaitTaskDefinitionBuilder Wait(Duration? duration = null) { var builder = new WaitTaskDefinitionBuilder(duration); - this.Builder = builder; + this.builder = builder; return builder; } /// - public virtual TaskDefinition Build() + public TaskDefinition Build() { - if (this.Builder == null) throw new NullReferenceException(); - return this.Builder.Build(); + if (this.builder == null) throw new NullReferenceException(); + return this.builder.Build(); } } diff --git a/src/ServerlessWorkflow.Sdk.Builders/HttpCallDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/HttpCallDefinitionBuilder.cs index be1b82e..cb5da91 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/HttpCallDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/HttpCallDefinitionBuilder.cs @@ -11,144 +11,116 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Calls; -using Neuroglia; -using System.Runtime.Serialization; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -[DataContract] -public class HttpCallDefinitionBuilder +public sealed class HttpCallDefinitionBuilder : IHttpCallDefinitionBuilder { - /// - /// Gets/sets the HTTP method of the request to perform - /// - protected string? Method { get; set; } - - /// - /// Gets/sets the definition of the endpoint to request - /// - protected EndpointDefinition? Endpoint { get; set; } - - /// - /// Gets/sets a name/value mapping of the headers, if any, of the HTTP request to perform - /// - protected EquatableDictionary? Headers { get; set; } - - /// - /// Gets/sets a name/value mapping of the cookies, if any, of the HTTP request to perform - /// - protected EquatableDictionary? Cookies { get; set; } - - /// - /// Gets/sets the body, if any, of the HTTP request to perform - /// - protected object? Body { get; set; } - - /// - /// Gets/sets the http call output format. Defaults to . - /// - protected string? OutputFormat { get; set; } + string? method; + EndpointDefinition? endpoint; + EquatableDictionary? headers; + EquatableDictionary? cookies; + JsonNode? body; + string? outputFormat; /// - public virtual IHttpCallDefinitionBuilder WithMethod(string method) + public IHttpCallDefinitionBuilder WithMethod(string method) { ArgumentException.ThrowIfNullOrWhiteSpace(method); - this.Method = method; + this.method = method; return this; } /// - public virtual IHttpCallDefinitionBuilder WithUri(Uri uri) + public IHttpCallDefinitionBuilder WithUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); - this.Endpoint = new() { Uri = uri }; + endpoint = new() { Uri = uri }; return this; } /// - public virtual IHttpCallDefinitionBuilder WithEndpoint(EndpointDefinition endpoint) + public IHttpCallDefinitionBuilder WithEndpoint(EndpointDefinition endpoint) { ArgumentNullException.ThrowIfNull(endpoint); - this.Endpoint = endpoint; + this.endpoint = endpoint; return this; } /// - public virtual IHttpCallDefinitionBuilder WithEndpoint(Action setup) + public IHttpCallDefinitionBuilder WithEndpoint(Action setup) { ArgumentNullException.ThrowIfNull(setup); - var builder = (IEndpointDefinitionBuilder)new ExternalResourceDefinitionBuilder(); + var builder = new EndpointDefinitionBuilder(); setup(builder); - return this.WithEndpoint(builder.Build()); + return WithEndpoint(builder.Build()); } /// - public virtual IHttpCallDefinitionBuilder WithHeader(string name, string value) + public IHttpCallDefinitionBuilder WithHeader(string name, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.Headers ??= []; - this.Headers[name] = value; + headers ??= []; + headers[name] = value; return this; } /// - public virtual IHttpCallDefinitionBuilder WithHeaders(IDictionary headers) + public IHttpCallDefinitionBuilder WithHeaders(IDictionary headers) { - this.Headers = headers == null ? null : new(headers); + this.headers = headers == null ? null : new(headers); return this; } /// - public virtual IHttpCallDefinitionBuilder WithCookie(string name, string value) + public IHttpCallDefinitionBuilder WithCookie(string name, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.Cookies ??= []; - this.Cookies[name] = value; + cookies ??= []; + cookies[name] = value; return this; } /// - public virtual IHttpCallDefinitionBuilder WithCookies(IDictionary cookies) + public IHttpCallDefinitionBuilder WithCookies(IDictionary cookies) { - this.Cookies = cookies == null ? null : new(cookies); + this.cookies = cookies == null ? null : new(cookies); return this; } /// - public virtual IHttpCallDefinitionBuilder WithBody(object body) + public IHttpCallDefinitionBuilder WithBody(JsonNode body) { - this.Body = body; + this.body = body; return this; } /// - public virtual IHttpCallDefinitionBuilder WithOutputFormat(string format) + public IHttpCallDefinitionBuilder WithOutputFormat(string format) { ArgumentException.ThrowIfNullOrWhiteSpace(format); - this.OutputFormat = format; + outputFormat = format; return this; } /// - public virtual HttpCallDefinition Build() + public HttpCallDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Method)) throw new NullReferenceException("The HTTP method must be set"); - if (this.Endpoint == null) throw new NullReferenceException("The HTTP endpoint must be set"); + if (string.IsNullOrWhiteSpace(method)) throw new NullReferenceException("The HTTP method must be set"); + if (endpoint == null) throw new NullReferenceException("The HTTP endpoint must be set"); return new() { - Method = this.Method, - Endpoint = this.Endpoint, - Headers = this.Headers, - Body = this.Body, - Output = this.OutputFormat + Method = method, + Endpoint = endpoint, + Headers = headers, + Body = body, + Output = outputFormat }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/InputDataModelDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/InputDataModelDefinitionBuilder.cs index 589a4fb..f3adb34 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/InputDataModelDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/InputDataModelDefinitionBuilder.cs @@ -16,34 +16,37 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class InputDataModelDefinitionBuilder +public sealed class InputDataModelDefinitionBuilder : IInputDataModelDefinitionBuilder { - /// - /// Gets the to configure - /// - protected InputDataModelDefinition Input { get; } = new(); + InputDataModelDefinition input = new(); /// - public virtual IInputDataModelDefinitionBuilder From(object expression) + public IInputDataModelDefinitionBuilder From(OneOf expression) { ArgumentNullException.ThrowIfNull(expression); - this.Input.From = expression; + input = input with + { + From = expression + }; return this; } /// - public virtual IInputDataModelDefinitionBuilder WithSchema(Action setup) + public IInputDataModelDefinitionBuilder WithSchema(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new SchemaDefinitionBuilder(); setup(builder); - this.Input.Schema = builder.Build(); + input = input with + { + Schema = builder.Build() + }; return this; } /// - public virtual InputDataModelDefinition Build() => this.Input; + public InputDataModelDefinition Build() => input; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ICallTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ICallTaskDefinitionBuilder.cs index 554bcb1..20450db 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ICallTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ICallTaskDefinitionBuilder.cs @@ -33,13 +33,13 @@ public interface ICallTaskDefinitionBuilder /// The argument's name /// The argument's value /// The configured - ICallTaskDefinitionBuilder With(string name, object value); + ICallTaskDefinitionBuilder With(string name, JsonNode value); /// /// Sets the arguments to call the function with /// /// A name/value mapping of the arguments to call the function with /// The configured - ICallTaskDefinitionBuilder With(IDictionary arguments); + ICallTaskDefinitionBuilder With(JsonObject arguments); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IErrorFilterDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IErrorFilterDefinitionBuilder.cs index 17b9029..db28691 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IErrorFilterDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IErrorFilterDefinitionBuilder.cs @@ -25,14 +25,14 @@ public interface IErrorFilterDefinitionBuilder /// The name of the attribute to filter errors by /// The value of the attribute to filter errors by. Supports runtime expressions /// The configured - IErrorFilterDefinitionBuilder With(string name, object value); + IErrorFilterDefinitionBuilder With(string name, JsonNode value); /// /// Sets a name/value mapping of the attributes to filter errors by /// /// A name/value mapping of the attributes to filter errors by. Supports runtime expressions /// The configured - IErrorFilterDefinitionBuilder With(IDictionary attributes); + IErrorFilterDefinitionBuilder With(JsonObject attributes); /// /// Builds the configured diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventDefinitionBuilder.cs index 5686b3e..471298f 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventDefinitionBuilder.cs @@ -25,14 +25,14 @@ public interface IEventDefinitionBuilder /// The attribute's name /// The attribute's value. Supports runtime expressions /// The configured - IEventDefinitionBuilder With(string name, object value); + IEventDefinitionBuilder With(string name, JsonNode value); /// /// Sets the event's attributes /// /// A name/value mapping of the event's attributes. Supports runtime expressions /// The configured - IEventDefinitionBuilder With(IDictionary attributes); + IEventDefinitionBuilder With(JsonObject attributes); /// /// Builds the configured diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionBuilder.cs index 999cc64..4bfd50a 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionBuilder.cs @@ -25,14 +25,14 @@ public interface IEventFilterDefinitionBuilder /// The name of the attribute to filter events by /// The value of the attribute to filter events by. Supports runtime expressions /// The configured - IEventFilterDefinitionBuilder With(string name, object value); + IEventFilterDefinitionBuilder With(string name, JsonNode value); /// /// Sets a name/value mapping of the attributes to filter events by /// /// A name/value mapping of the attributes to filter events by. Supports runtime expressions /// The configured - IEventFilterDefinitionBuilder With(IDictionary attributes); + IEventFilterDefinitionBuilder With(JsonObject attributes); /// /// Builds the configured diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionCollectionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionCollectionBuilder.cs index b410e26..bb46f73 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionCollectionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IEventFilterDefinitionCollectionBuilder.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IGenericTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IGenericTaskDefinitionBuilder.cs index 35b23d7..83539d6 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IGenericTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IGenericTaskDefinitionBuilder.cs @@ -98,7 +98,7 @@ public interface IGenericTaskDefinitionBuilder /// /// A name/value mapping of the variables to set. Supports runtime expressions /// A new - ISetTaskDefinitionBuilder Set(IDictionary? variables = null); + ISetTaskDefinitionBuilder Set(JsonObject? variables = null); /// /// Configures the task to branch the flow based on defined conditions diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IHttpCallDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IHttpCallDefinitionBuilder.cs index 222121f..7287222 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IHttpCallDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IHttpCallDefinitionBuilder.cs @@ -84,7 +84,7 @@ public interface IHttpCallDefinitionBuilder /// /// The request body /// The configured - IHttpCallDefinitionBuilder WithBody(object body); + IHttpCallDefinitionBuilder WithBody(JsonNode body); /// /// Uses the specified output format diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IInputDataModelDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IInputDataModelDefinitionBuilder.cs index 1be73a4..e81f406 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IInputDataModelDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IInputDataModelDefinitionBuilder.cs @@ -31,7 +31,7 @@ public interface IInputDataModelDefinitionBuilder /// /// The runtime expression used to filter the input data /// The configured - IInputDataModelDefinitionBuilder From(object expression); + IInputDataModelDefinitionBuilder From(OneOf expression); /// /// Builds the configured diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IOutputDataModelDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IOutputDataModelDefinitionBuilder.cs index 464deb5..370732e 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IOutputDataModelDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IOutputDataModelDefinitionBuilder.cs @@ -31,7 +31,7 @@ public interface IOutputDataModelDefinitionBuilder /// /// The runtime expression used to filter the data to output /// The configured - IOutputDataModelDefinitionBuilder As(object expression); + IOutputDataModelDefinitionBuilder As(OneOf expression); /// /// Builds the configured diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISchemaDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISchemaDefinitionBuilder.cs index bacc732..9c3ed35 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISchemaDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISchemaDefinitionBuilder.cs @@ -38,7 +38,7 @@ public interface ISchemaDefinitionBuilder /// /// The schema document /// The configured - ISchemaDefinitionBuilder WithDocument(object document); + ISchemaDefinitionBuilder WithDocument(JsonObject document); /// /// Builds the configured diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISetTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISetTaskDefinitionBuilder.cs index 50f2d6a..e517dbd 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISetTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/ISetTaskDefinitionBuilder.cs @@ -26,13 +26,13 @@ public interface ISetTaskDefinitionBuilder /// The name of the variable to set /// The value of the variable to set. Supports runtime expressions /// The configured - ISetTaskDefinitionBuilder Set(string name, object value); + ISetTaskDefinitionBuilder Set(string name, JsonNode value); /// /// Configures the task to set the specified variable /// /// A name/value mapping of the variables to set /// The configured - ISetTaskDefinitionBuilder Set(IDictionary variables); + ISetTaskDefinitionBuilder Set(JsonObject variables); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IWorkflowProcessDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IWorkflowProcessDefinitionBuilder.cs index f17c60b..b5032c6 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IWorkflowProcessDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Interfaces/IWorkflowProcessDefinitionBuilder.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Processes; - namespace ServerlessWorkflow.Sdk.Builders; /// @@ -48,6 +46,6 @@ public interface IWorkflowProcessDefinitionBuilder /// /// The input of the workflow to run. Supports runtime expressions /// The configured - IWorkflowProcessDefinitionBuilder WithInput(object input); + IWorkflowProcessDefinitionBuilder WithInput(JsonObject input); } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/JitterDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/JitterDefinitionBuilder.cs index ee294d4..4a182ff 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/JitterDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/JitterDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,47 +16,38 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -/// The minimum duration of the jitter range -/// The maximum duration of the jitter range -public class JitterDefinitionBuilder(Duration? from = null, Duration? to = null) +public sealed class JitterDefinitionBuilder(Duration? from = null, Duration? to = null) : IJitterDefinitionBuilder { - /// - /// Gets the minimum duration of the jitter range - /// - protected Duration? JitterFrom { get; set; } = from; - - /// - /// Gets the maximum duration of the jitter range - /// - protected Duration? JitterTo { get; set; } = to; + Duration? jitterFrom = from; + Duration? jitterTo = to; /// - public virtual IJitterDefinitionBuilder From(Duration from) + public IJitterDefinitionBuilder From(Duration from) { ArgumentNullException.ThrowIfNull(from); - this.JitterFrom = from; + jitterFrom = from; return this; } /// - public virtual IJitterDefinitionBuilder To(Duration to) + public IJitterDefinitionBuilder To(Duration to) { ArgumentNullException.ThrowIfNull(to); - this.JitterTo = to; + jitterTo = to; return this; } /// - public virtual JitterDefinition Build() + public JitterDefinition Build() { - if (this.JitterFrom == null) throw new NullReferenceException("The jitter range's minimum duration must be set"); - if (this.JitterTo == null) throw new NullReferenceException("The jitter range's maximum duration must be set"); + if (jitterFrom == null) throw new NullReferenceException("The jitter range's minimum duration must be set"); + if (jitterTo == null) throw new NullReferenceException("The jitter range's maximum duration must be set"); return new() { - From = this.JitterFrom, - To = this.JitterTo, + From = jitterFrom, + To = jitterTo, }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/LinearBackoffDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/LinearBackoffDefinitionBuilder.cs index 36f8f7a..15e9516 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/LinearBackoffDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/LinearBackoffDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,30 +16,26 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -/// The linear incrementation to the delay between retry attempts -public class LinearBackoffDefinitionBuilder(Duration? increment = null) +public sealed class LinearBackoffDefinitionBuilder(Duration? increment = null) : ILinearBackoffDefinitionBuilder { - /// - /// Gets/sets the linear incrementation to the delay between retry attempts - /// - protected Duration? LinearIncrement { get; set; } = increment; + Duration? linearIncrement = increment; /// - public virtual ILinearBackoffDefinitionBuilder WithIncrement(Duration increment) + public ILinearBackoffDefinitionBuilder WithIncrement(Duration increment) { ArgumentNullException.ThrowIfNull(increment); - this.LinearIncrement = increment; + linearIncrement = increment; return this; } /// - public virtual LinearBackoffDefinition Build() => new() + public LinearBackoffDefinition Build() => new() { - Increment = this.LinearIncrement + Increment = linearIncrement }; - BackoffDefinition IBackoffDefinitionBuilder.Build() => this.Build(); + BackoffDefinition IBackoffDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ListenTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ListenTaskDefinitionBuilder.cs index 3893e1f..2dc1917 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ListenTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ListenTaskDefinitionBuilder.cs @@ -16,36 +16,42 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ListenTaskDefinitionBuilder +public sealed class ListenTaskDefinitionBuilder : TaskDefinitionBuilder, IListenTaskDefinitionBuilder { - /// - /// Gets/sets the to configure - /// - protected ListenTaskDefinition Task { get; } = new() { Listen = null! }; + ListenTaskDefinition task = new() + { + Listen = null! + }; /// - public virtual IListenTaskDefinitionBuilder To(Action setup) + public IListenTaskDefinitionBuilder To(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ListenerDefinitionBuilder(); setup(builder); - this.Task.Listen = builder.Build(); + task = task with + { + Listen = builder.Build() + }; return this; } /// - public virtual IListenTaskDefinitionBuilder Foreach(Action setup) + public IListenTaskDefinitionBuilder Foreach(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new SubscriptionIteratorDefinitionBuilder(); setup(builder); - this.Task.Foreach = builder.Build(); + task = task with + { + Foreach = builder.Build() + }; return this; } /// - public override ListenTaskDefinition Build() => this.Configure(this.Task); + public override ListenTaskDefinition Build() => Configure(task); } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/ListenerDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ListenerDefinitionBuilder.cs index ebffd54..e74c0c2 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ListenerDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ListenerDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,30 +16,37 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -/// The listener's target -public class ListenerDefinitionBuilder(EventConsumptionStrategyDefinition? to = null) +public sealed class ListenerDefinitionBuilder(EventConsumptionStrategyDefinition? to = null) : ListenerTargetDefinitionBuilder, IListenerDefinitionBuilder { /// - /// Gets/sets the to configure + /// Gets/sets the initial target value /// - protected ListenerDefinition Listener { get; } = new() { To = to! }; + readonly EventConsumptionStrategyDefinition? initialTo = to; + + /// + /// Gets/sets the read mode + /// + string? readMode; /// - public virtual IListenerDefinitionBuilder Read(string readMode) + public IListenerDefinitionBuilder Read(string readMode) { ArgumentException.ThrowIfNullOrWhiteSpace(readMode); - this.Listener.Read = readMode; - return this; + this.readMode = readMode; + IListenerDefinitionBuilder self = this; return self; } /// - public virtual new ListenerDefinition Build() + public new ListenerDefinition Build() { - var to = base.Build() ?? throw new NullReferenceException("The listener's target must be set"); - this.Listener.To = to; - return this.Listener; + var target = initialTo ?? base.Build() ?? throw new NullReferenceException("The listener's target must be set"); + return new() + { + To = target, + Read = readMode + }; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/ListenerTargetDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ListenerTargetDefinitionBuilder.cs index 6e85dfe..5881d0b 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ListenerTargetDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ListenerTargetDefinitionBuilder.cs @@ -20,81 +20,64 @@ public class ListenerTargetDefinitionBuilder : IListenerTargetDefinitionBuilder { - /// - /// Gets/sets a list containing all the events that must be listened to, if any - /// - protected IEventFilterDefinitionCollectionBuilder? AllEvents { get; set; } - - /// - /// Gets/sets a list containing any of the events to listen to, if any - /// - protected IEventFilterDefinitionCollectionBuilder? AnyEvents { get; set; } - - /// - /// Gets/sets the single event to listen to - /// - protected IEventFilterDefinitionBuilder? SingleEvent { get; set; } - - /// - /// Gets the runtime expression that represents the condition that must match for the task to stop consuming events - /// - protected string? UntilExpression { get; private set; } - - /// - /// Gets the strategy used to configure the events to consume for the task to stop consuming events - /// - protected EventConsumptionStrategyDefinition? UntilEvents { get; private set; } + IEventFilterDefinitionCollectionBuilder? allEvents; + IEventFilterDefinitionCollectionBuilder? anyEvents; + IEventFilterDefinitionBuilder? singleEvent; + string? untilExpression; + EventConsumptionStrategyDefinition? untilEvents; /// - public virtual IEventFilterDefinitionCollectionBuilder All() + public IEventFilterDefinitionCollectionBuilder All() { - this.AllEvents = new EventFilterDefinitionCollectionBuilder(); - return this.AllEvents; + allEvents = new EventFilterDefinitionCollectionBuilder(); + return allEvents; } /// - public virtual IEventFilterDefinitionCollectionBuilder Any() + public IEventFilterDefinitionCollectionBuilder Any() { - this.AnyEvents = new EventFilterDefinitionCollectionBuilder(); - return this.AnyEvents; + anyEvents = new EventFilterDefinitionCollectionBuilder(); + return anyEvents; } /// - public virtual IEventFilterDefinitionBuilder One() + public IEventFilterDefinitionBuilder One() { - this.SingleEvent = new EventFilterDefinitionBuilder(); - return this.SingleEvent; + singleEvent = new EventFilterDefinitionBuilder(); + return singleEvent; } /// - public virtual void Until(string expression) + public void Until(string expression) { ArgumentException.ThrowIfNullOrWhiteSpace(expression); - if (this.AnyEvents == null) throw new Exception("The until clause can only be specified when the strategy is used to consume any events"); - this.UntilExpression = expression; + if (anyEvents == null) throw new Exception("The until clause can only be specified when the strategy is used to consume any events"); + untilExpression = expression; } /// - public virtual void Until(Action setup) + public void Until(Action setup) { ArgumentNullException.ThrowIfNull(setup); - if (this.AnyEvents == null) throw new Exception("The until clause can only be specified when the strategy is used to consume any events"); + if (anyEvents == null) throw new Exception("The until clause can only be specified when the strategy is used to consume any events"); var builder = new ListenerTargetDefinitionBuilder(); setup(builder); - this.UntilEvents = builder.Build(); + untilEvents = builder.Build(); } /// - public virtual EventConsumptionStrategyDefinition Build() + public EventConsumptionStrategyDefinition Build() { - if (this.AllEvents == null && this.AnyEvents == null && this.SingleEvent == null) throw new NullReferenceException("The target must be defined"); + if (allEvents == null && anyEvents == null && singleEvent == null) throw new NullReferenceException("The target must be defined"); + OneOf? until = null; + if (untilExpression != null) until = untilExpression; + else if (untilEvents != null) until = untilEvents; return new() { - All = this.AllEvents?.Build(), - Any = this.AnyEvents?.Build(), - One = this.SingleEvent?.Build(), - UntilExpression = this.UntilExpression, - Until = this.UntilEvents + All = allEvents?.Build(), + Any = anyEvents?.Build(), + One = singleEvent?.Build(), + Until = until }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationClientDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationClientDefinitionBuilder.cs index 41e5ba2..829cc04 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationClientDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationClientDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,72 +16,54 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class OAuth2AuthenticationClientDefinitionBuilder +public sealed class OAuth2AuthenticationClientDefinitionBuilder : IOAuth2AuthenticationClientDefinitionBuilder { - /// - /// Gets/sets the OAUTH2 `client_id` to use - /// - protected string? Id { get; set; } - - /// - /// Gets/sets the OAUTH2 `client_secret` to use, if any - /// - protected string? Secret { get; set; } - - /// - /// Gets/sets a JWT containing a signed assertion with the application credentials - /// - protected string? Assertion { get; set; } - - /// - /// Gets/sets the authentication method to use to authenticate the client - /// - protected string? Authentication { get; set; } + string? id; + string? secret; + string? assertion; + string? authentication; /// - public virtual IOAuth2AuthenticationClientDefinitionBuilder WithId(string id) + public IOAuth2AuthenticationClientDefinitionBuilder WithId(string id) { ArgumentException.ThrowIfNullOrWhiteSpace(id); - this.Id = id; + this.id = id; return this; } /// - public virtual IOAuth2AuthenticationClientDefinitionBuilder WithSecret(string secret) + public IOAuth2AuthenticationClientDefinitionBuilder WithSecret(string secret) { ArgumentException.ThrowIfNullOrWhiteSpace(secret); - this.Secret = secret; + this.secret = secret; return this; } /// - public virtual IOAuth2AuthenticationClientDefinitionBuilder WithAssertion(string assertion) + public IOAuth2AuthenticationClientDefinitionBuilder WithAssertion(string assertion) { ArgumentException.ThrowIfNullOrWhiteSpace(assertion); - this.Assertion = assertion; + this.assertion = assertion; return this; } /// - public virtual IOAuth2AuthenticationClientDefinitionBuilder WithAuthenticationMethod(string method) + public IOAuth2AuthenticationClientDefinitionBuilder WithAuthenticationMethod(string method) { ArgumentException.ThrowIfNullOrWhiteSpace(method); - this.Authentication = method; + authentication = method; return this; } /// - public virtual OAuth2AuthenticationClientDefinition Build() + public OAuth2AuthenticationClientDefinition Build() => new() { - return new() - { - Id = this.Id, - Secret = this.Secret, - Assertion = this.Assertion, - Authentication = this.Authentication - }; - } + Id = id, + Secret = secret, + Assertion = assertion, + Authentication = authentication + }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationEndpointsDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationEndpointsDefinitionBuilder.cs index 514886f..bf06c9c 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationEndpointsDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationEndpointsDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,64 +16,53 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class OAuth2AuthenticationEndpointsDefinitionBuilder +public sealed class OAuth2AuthenticationEndpointsDefinitionBuilder : IOAuth2AuthenticationEndpointsDefinitionBuilder { - /// - /// Gets/sets the relative path to the token endpoint. Defaults to `/oauth2/token` - /// - protected Uri Token { get; set; } = new("/oauth2/token"); - - /// - /// Gets/sets the relative path to the revocation endpoint. Defaults to `/oauth2/revoke` - /// - protected Uri Revocation { get; set; } = new("/oauth2/revoke"); - - /// - /// Gets/sets the relative path to the introspection endpoint. Defaults to `/oauth2/introspect` - /// - protected Uri Introspection { get; set; } = new("/oauth2/introspect"); + Uri token = new("/oauth2/token", UriKind.Relative); + Uri revocation = new("/oauth2/revoke", UriKind.Relative); + Uri introspection = new("/oauth2/introspect", UriKind.Relative); /// - public virtual IOAuth2AuthenticationEndpointsDefinitionBuilder WithTokenEndpoint(Uri uri) + public IOAuth2AuthenticationEndpointsDefinitionBuilder WithTokenEndpoint(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (uri.IsAbsoluteUri) throw new ArgumentException("The specified uri must be relative to the configured authority", nameof(uri)); - this.Token = uri; - return this; + token = uri; + IOAuth2AuthenticationEndpointsDefinitionBuilder self = this; return self; } /// - public virtual IOAuth2AuthenticationEndpointsDefinitionBuilder WithRevocationEndpoint(Uri uri) + public IOAuth2AuthenticationEndpointsDefinitionBuilder WithRevocationEndpoint(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (uri.IsAbsoluteUri) throw new ArgumentException("The specified uri must be relative to the configured authority", nameof(uri)); - this.Revocation = uri; - return this; + revocation = uri; + IOAuth2AuthenticationEndpointsDefinitionBuilder self = this; return self; } /// - public virtual IOAuth2AuthenticationEndpointsDefinitionBuilder WithIntrospectionEndpoint(Uri uri) + public IOAuth2AuthenticationEndpointsDefinitionBuilder WithIntrospectionEndpoint(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (uri.IsAbsoluteUri) throw new ArgumentException("The specified uri must be relative to the configured authority", nameof(uri)); - this.Introspection = uri; - return this; + introspection = uri; + IOAuth2AuthenticationEndpointsDefinitionBuilder self = this; return self; } /// - public virtual OAuth2AuthenticationEndpointsDefinition Build() + public OAuth2AuthenticationEndpointsDefinition Build() { - if (this.Token == null) throw new NullReferenceException("The token endpoint must be configured"); - if (this.Revocation == null) throw new NullReferenceException("The revocation endpoint must be configured"); - if (this.Introspection == null) throw new NullReferenceException("The introspection endpoint must be configured"); + if (token == null) throw new NullReferenceException("The token endpoint must be configured"); + if (revocation == null) throw new NullReferenceException("The revocation endpoint must be configured"); + if (introspection == null) throw new NullReferenceException("The introspection endpoint must be configured"); return new() { - Token = this.Token, - Revocation = this.Revocation, - Introspection = this.Introspection + Token = token, + Revocation = revocation, + Introspection = introspection }; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationRequestDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationRequestDefinitionBuilder.cs index d40a653..dd32aa0 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationRequestDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationRequestDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,28 +16,28 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class OAuth2AuthenticationRequestDefinitionBuilder +public sealed class OAuth2AuthenticationRequestDefinitionBuilder : IOAuth2AuthenticationRequestDefinitionBuilder { - /// - /// Gets/sets the encoding of the authentication request. Defaults to 'application/x-www-form-urlencoded' - /// - public virtual string? Encoding { get; set; } + string? encoding; /// - public virtual IOAuth2AuthenticationRequestDefinitionBuilder WithEncoding(string encoding) + public IOAuth2AuthenticationRequestDefinitionBuilder WithEncoding(string encoding) { ArgumentException.ThrowIfNullOrWhiteSpace(encoding); - this.Encoding = encoding; + this.encoding = encoding; return this; } /// - public virtual OAuth2AuthenticationRequestDefinition Build() + public OAuth2AuthenticationRequestDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Encoding)) throw new NullReferenceException("The request encoding must be set"); - return new() { Encoding = this.Encoding }; + if (string.IsNullOrWhiteSpace(encoding)) throw new NullReferenceException("The request encoding must be set"); + return new() + { + Encoding = encoding + }; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationSchemeDefinitionBuilder.cs index ac0ebfc..14eee41 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/OAuth2AuthenticationSchemeDefinitionBuilder.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// @@ -84,7 +82,7 @@ public abstract class OAuth2AuthenticationSchemeDefinitionBuilder public virtual TBuilder WithScopes(params string[] scopes) { - this.Scopes = new(scopes); + Scopes = new(scopes); return (TBuilder)(object)this; } @@ -159,32 +157,32 @@ public virtual TBuilder WithScopes(params string[] scopes) public virtual TBuilder WithActor(OAuth2TokenDefinition actor) { ArgumentNullException.ThrowIfNull(actor); - this.Actor = actor; + Actor = actor; return (TBuilder)(object)this; } /// public virtual TBuilder WithUsername(string username) { - this.Username = username; + Username = username; return (TBuilder)(object)this; } /// public virtual TBuilder WithPassword(string password) { - this.Password = password; + Password = password; return (TBuilder)(object)this; } /// public virtual TBuilder WithSubject(OAuth2TokenDefinition subject) { - this.Subject = subject; + Subject = subject; return (TBuilder)(object)this; } - AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => this.Build(); + AuthenticationSchemeDefinition IAuthenticationSchemeDefinitionBuilder.Build() => Build(); } @@ -204,7 +202,7 @@ public class OAuth2AuthenticationSchemeDefinitionBuilder public virtual IOAuth2AuthenticationSchemeDefinitionBuilder WithEndpoints(OAuth2AuthenticationEndpointsDefinition endpoints) { ArgumentNullException.ThrowIfNull(endpoints); - this.Endpoints = endpoints; + Endpoints = endpoints; return this; } @@ -214,30 +212,30 @@ public virtual IOAuth2AuthenticationSchemeDefinitionBuilder WithEndpoints(Action ArgumentNullException.ThrowIfNull(setup); var builder = new OAuth2AuthenticationEndpointsDefinitionBuilder(); setup(builder); - this.Endpoints = builder.Build(); + Endpoints = builder.Build(); return this; } /// public override OAuth2AuthenticationSchemeDefinition Build() { - if (this.Authority == null) throw new NullReferenceException("The authority must be set"); - if (string.IsNullOrWhiteSpace(this.GrantType)) throw new NullReferenceException("The grant type must be set"); + if (Authority == null) throw new NullReferenceException("The authority must be set"); + if (string.IsNullOrWhiteSpace(GrantType)) throw new NullReferenceException("The grant type must be set"); return new() { - Use = this.Secret, - Authority = this.Authority, - Endpoints = this.Endpoints, - Grant = this.GrantType, - Client = this.Client, - Request = this.Request, - Issuers = this.Issuers, - Audiences = this.Audiences, - Scopes = this.Scopes, - Actor = this.Actor, - Username = this.Username, - Password = this.Password, - Subject = this.Subject + Use = Secret, + Authority = Authority, + Endpoints = Endpoints, + Grant = GrantType, + Client = Client, + Request = Request, + Issuers = Issuers, + Audiences = Audiences, + Scopes = Scopes, + Actor = Actor, + Username = Username, + Password = Password, + Subject = Subject }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilder.cs index 3674d70..396f9dc 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilder.cs @@ -16,29 +16,29 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class OpenIDConnectAuthenticationSchemeDefinitionBuilder +public sealed class OpenIDConnectAuthenticationSchemeDefinitionBuilder : OAuth2AuthenticationSchemeDefinitionBuilder, IOpenIDConnectAuthenticationSchemeDefinitionBuilder { /// public override OpenIDConnectSchemeDefinition Build() { - if (this.Authority == null) throw new NullReferenceException("The authority must be set"); - if (string.IsNullOrWhiteSpace(this.GrantType)) throw new NullReferenceException("The grant type must be set"); + if (Authority == null) throw new NullReferenceException("The authority must be set"); + if (string.IsNullOrWhiteSpace(GrantType)) throw new NullReferenceException("The grant type must be set"); return new() { - Use = this.Secret, - Authority = this.Authority, - Grant = this.GrantType, - Client = this.Client, - Request = this.Request, - Issuers = this.Issuers, - Audiences = this.Audiences, - Scopes = this.Scopes, - Actor = this.Actor, - Username = this.Username, - Password = this.Password, - Subject = this.Subject + Use = Secret, + Authority = Authority, + Grant = GrantType, + Client = Client, + Request = Request, + Issuers = Issuers, + Audiences = Audiences, + Scopes = Scopes, + Actor = Actor, + Username = Username, + Password = Password, + Subject = Subject }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/OutputDataModelDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/OutputDataModelDefinitionBuilder.cs index 0e39803..51a14cd 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/OutputDataModelDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/OutputDataModelDefinitionBuilder.cs @@ -16,34 +16,37 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class OutputDataModelDefinitionBuilder +public sealed class OutputDataModelDefinitionBuilder : IOutputDataModelDefinitionBuilder { - /// - /// Gets the to configure - /// - protected OutputDataModelDefinition Output { get; } = new(); + OutputDataModelDefinition output = new(); /// - public virtual IOutputDataModelDefinitionBuilder As(object expression) + public IOutputDataModelDefinitionBuilder As(OneOf expression) { ArgumentNullException.ThrowIfNull(expression); - this.Output.As = expression; + output = output with + { + As = expression + }; return this; } /// - public virtual IOutputDataModelDefinitionBuilder WithSchema(Action setup) + public IOutputDataModelDefinitionBuilder WithSchema(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new SchemaDefinitionBuilder(); setup(builder); - this.Output.Schema = builder.Build(); + output = output with + { + Schema = builder.Build() + }; return this; } /// - public virtual OutputDataModelDefinition Build() => this.Output; + public OutputDataModelDefinition Build() => output; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/ProcessDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ProcessDefinitionBuilder.cs index 0fa8ae6..e699e06 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ProcessDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ProcessDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -25,5 +25,6 @@ public abstract class ProcessDefinitionBuilder /// public abstract TDefinition Build(); - ProcessDefinition IProcessDefinitionBuilder.Build() => this.Build(); -} \ No newline at end of file + ProcessDefinition IProcessDefinitionBuilder.Build() => Build(); + +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/RaiseTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/RaiseTaskDefinitionBuilder.cs index 48e7c39..3766924 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/RaiseTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/RaiseTaskDefinitionBuilder.cs @@ -17,41 +17,38 @@ namespace ServerlessWorkflow.Sdk.Builders; /// Represents the default implementation of the interface /// /// The error to raise -public class RaiseTaskDefinitionBuilder(ErrorDefinition? errorDefinition = null) +public sealed class RaiseTaskDefinitionBuilder(ErrorDefinition? errorDefinition = null) : TaskDefinitionBuilder, IRaiseTaskDefinitionBuilder { - /// - /// Gets/sets the error to raise - /// - protected ErrorDefinition? ErrorDefinition { get; set; } = errorDefinition; + ErrorDefinition? errorDefinition = errorDefinition; /// - public virtual IRaiseTaskDefinitionBuilder Error(ErrorDefinition error) + public IRaiseTaskDefinitionBuilder Error(ErrorDefinition error) { ArgumentNullException.ThrowIfNull(error); - this.ErrorDefinition = error; + errorDefinition = error; return this; } /// - public virtual IRaiseTaskDefinitionBuilder Error(Action setup) + public IRaiseTaskDefinitionBuilder Error(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ErrorDefinitionBuilder(); setup(builder); - return this.Error(builder.Build()); + return Error(builder.Build()); } /// public override RaiseTaskDefinition Build() { - if (this.ErrorDefinition == null) throw new NullReferenceException("The error to raise must be set"); - return this.Configure(new() + if (errorDefinition == null) throw new NullReferenceException("The error to raise must be set"); + return Configure(new() { Raise = new() { - Error = this.ErrorDefinition + Error = errorDefinition } }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/RetryAttemptLimitDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/RetryAttemptLimitDefinitionBuilder.cs index 3d20f28..6d91477 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/RetryAttemptLimitDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/RetryAttemptLimitDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -14,41 +14,34 @@ namespace ServerlessWorkflow.Sdk.Builders; /// -/// Represents the default implementation +/// Represents the default implementation of the interface /// -public class RetryAttemptLimitDefinitionBuilder +public sealed class RetryAttemptLimitDefinitionBuilder : IRetryAttemptLimitDefinitionBuilder { - /// - /// Gets/sets the maximum attempts count - /// - protected uint? AttemptCount { get; set; } - - /// - /// Gets/sets the duration limit, if any, for all retry attempts - /// - protected Duration? AttemptDuration { get; set; } + uint? attemptCount; + Duration? attemptDuration; /// - public virtual IRetryAttemptLimitDefinitionBuilder Count(uint count) + public IRetryAttemptLimitDefinitionBuilder Count(uint count) { - this.AttemptCount = count; + attemptCount = count; return this; } /// - public virtual IRetryAttemptLimitDefinitionBuilder Duration(Duration duration) + public IRetryAttemptLimitDefinitionBuilder Duration(Duration duration) { - this.AttemptDuration = duration; + attemptDuration = duration; return this; } /// - public virtual RetryAttemptLimitDefinition Build() => new() + public RetryAttemptLimitDefinition Build() => new() { - Count = AttemptCount, - Duration = AttemptDuration + Count = attemptCount, + Duration = attemptDuration }; -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyDefinitionBuilder.cs index c8adb67..a11a7ee 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,118 +16,95 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class RetryPolicyDefinitionBuilder +public sealed class RetryPolicyDefinitionBuilder : IRetryPolicyDefinitionBuilder { - /// - /// Gets/sets a runtime expression used to determine whether or not to retry running the task, in a given context - /// - protected string? RetryWhen { get; set; } - - /// - /// Gets/sets a runtime expression used to determine whether or not to retry running the task, in a given context - /// - protected string? RetryExceptWhen { get; set; } - - /// - /// Gets/sets the parameters, if any, that control the randomness or variability of the delay between retry attempts - /// - protected RetryPolicyLimitDefinition? RetryLimit { get; set; } - - /// - /// Gets/sets the delay duration between retry attempts - /// - protected Duration? RetryDelay { get; set; } - - /// - /// Gets/sets the limits, if any, of the retry policy to build - /// - protected BackoffStrategyDefinition? RetryBackoff { get; set; } - - /// - /// Gets/sets the backoff strategy to use, if any - /// - protected JitterDefinition? RetryJitter { get; set; } + string? retryWhen; + string? retryExceptWhen; + RetryPolicyLimitDefinition? retryLimit; + Duration? retryDelay; + BackoffStrategyDefinition? retryBackoff; + JitterDefinition? retryJitter; /// - public virtual IRetryPolicyDefinitionBuilder When(string expression) + public IRetryPolicyDefinitionBuilder When(string expression) { - this.RetryWhen = expression; + retryWhen = expression; return this; } /// - public virtual IRetryPolicyDefinitionBuilder ExceptWhen(string expression) + public IRetryPolicyDefinitionBuilder ExceptWhen(string expression) { - this.RetryExceptWhen = expression; + retryExceptWhen = expression; return this; } /// - public virtual IRetryPolicyDefinitionBuilder Limit(RetryPolicyLimitDefinition limits) + public IRetryPolicyDefinitionBuilder Limit(RetryPolicyLimitDefinition limits) { - this.RetryLimit = limits; + retryLimit = limits; return this; } /// - public virtual IRetryPolicyDefinitionBuilder Limit(Action setup) + public IRetryPolicyDefinitionBuilder Limit(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new RetryPolicyLimitDefinitionBuilder(); setup(builder); - return this.Limit(builder.Build()); + return Limit(builder.Build()); } /// - public virtual IRetryPolicyDefinitionBuilder Delay(Duration duration) + public IRetryPolicyDefinitionBuilder Delay(Duration duration) { - this.RetryDelay = duration; + retryDelay = duration; return this; } /// - public virtual IRetryPolicyDefinitionBuilder Backoff(BackoffStrategyDefinition backoff) + public IRetryPolicyDefinitionBuilder Backoff(BackoffStrategyDefinition backoff) { - this.RetryBackoff = backoff; + retryBackoff = backoff; return this; } /// - public virtual IRetryPolicyDefinitionBuilder Backoff(Action setup) + public IRetryPolicyDefinitionBuilder Backoff(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new BackoffStrategyDefinitionBuilder(); setup(builder); - return this.Backoff(builder.Build()); + return Backoff(builder.Build()); } /// - public virtual IRetryPolicyDefinitionBuilder Jitter(JitterDefinition jitter) + public IRetryPolicyDefinitionBuilder Jitter(JitterDefinition jitter) { - this.RetryJitter = jitter; + retryJitter = jitter; return this; } /// - public virtual IRetryPolicyDefinitionBuilder Jitter(Action setup) + public IRetryPolicyDefinitionBuilder Jitter(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new JitterDefinitionBuilder(); setup(builder); - return this.Jitter(builder.Build()); + return Jitter(builder.Build()); } /// - public virtual RetryPolicyDefinition Build() => new() + public RetryPolicyDefinition Build() => new() { - When = this.RetryWhen, - ExceptWhen = this.RetryExceptWhen, - Limit = this.RetryLimit, - Delay = this.RetryDelay, - Backoff = this.RetryBackoff, - Jitter = this.RetryJitter + When = retryWhen, + ExceptWhen = retryExceptWhen, + Limit = retryLimit, + Delay = retryDelay, + Backoff = retryBackoff, + Jitter = retryJitter }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyLimitDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyLimitDefinitionBuilder.cs index 4f0a83a..2b18ccb 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyLimitDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/RetryPolicyLimitDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,39 +16,32 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class RetryPolicyLimitDefinitionBuilder +public sealed class RetryPolicyLimitDefinitionBuilder : IRetryPolicyLimitDefinitionBuilder { - /// - /// Gets the service used to build the definition of the limits for all retry attempts of a given policy - /// - protected IRetryAttemptLimitDefinitionBuilder? LimitAttempt { get; set; } - - /// - /// Gets the maximum duration during which retrying is allowed - /// - protected Duration? LimitDuration { get; set; } + RetryAttemptLimitDefinitionBuilder? limitAttempt; + Duration? limitDuration; /// - public virtual IRetryAttemptLimitDefinitionBuilder Attempt() + public IRetryAttemptLimitDefinitionBuilder Attempt() { - this.LimitAttempt = new RetryAttemptLimitDefinitionBuilder(); - return this.LimitAttempt; + limitAttempt = new RetryAttemptLimitDefinitionBuilder(); + return limitAttempt; } /// - public virtual IRetryPolicyLimitDefinitionBuilder Duration(Duration duration) + public IRetryPolicyLimitDefinitionBuilder Duration(Duration duration) { - this.LimitDuration = duration; + limitDuration = duration; return this; } /// - public virtual RetryPolicyLimitDefinition Build() => new() + public RetryPolicyLimitDefinition Build() => new() { - Attempt = this.LimitAttempt?.Build(), - Duration = this.LimitDuration, + Attempt = limitAttempt?.Build(), + Duration = limitDuration, }; -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/RunTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/RunTaskDefinitionBuilder.cs index 9fa1b73..ba0973c 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/RunTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/RunTaskDefinitionBuilder.cs @@ -11,72 +11,63 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Processes; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class RunTaskDefinitionBuilder +public sealed class RunTaskDefinitionBuilder : TaskDefinitionBuilder, IRunTaskDefinitionBuilder { - /// - /// Gets/sets a boolean indicating whether or not the task to build should await the execution of the defined process - /// - protected bool? AwaitProcess { get; set; } - - /// - /// Gets/sets the process to run - /// - protected IProcessDefinitionBuilder? ProcessBuilder { get; set; } + bool? awaitProcess; + IProcessDefinitionBuilder? processBuilder; /// - public virtual IContainerProcessDefinitionBuilder Container() + public IContainerProcessDefinitionBuilder Container() { var builder = new ContainerProcessDefinitionBuilder(); - this.ProcessBuilder = builder; + processBuilder = builder; return builder; } /// - public virtual IScriptProcessDefinitionBuilder Script() + public IScriptProcessDefinitionBuilder Script() { var builder = new ScriptProcessDefinitionBuilder(); - this.ProcessBuilder = builder; + processBuilder = builder; return builder; } /// - public virtual IShellProcessDefinitionBuilder Shell() + public IShellProcessDefinitionBuilder Shell() { var builder = new ShellProcessDefinitionBuilder(); - this.ProcessBuilder = builder; + processBuilder = builder; return builder; } /// - public virtual IWorkflowProcessDefinitionBuilder Workflow() + public IWorkflowProcessDefinitionBuilder Workflow() { var builder = new WorkflowProcessDefinitionBuilder(); - this.ProcessBuilder = builder; + processBuilder = builder; return builder; } /// - public virtual IRunTaskDefinitionBuilder Await(bool await) + public IRunTaskDefinitionBuilder Await(bool await) { - this.AwaitProcess = await; + awaitProcess = await; return this; } /// public override RunTaskDefinition Build() { - if (this.ProcessBuilder == null) throw new NullReferenceException("The process to run must be set"); - var process = this.ProcessBuilder.Build(); - return this.Configure(new() + if (processBuilder == null) throw new NullReferenceException("The process to run must be set"); + var process = processBuilder.Build(); + return Configure(new() { Run = new() { @@ -84,7 +75,7 @@ public override RunTaskDefinition Build() Script = process is ScriptProcessDefinition script ? script : null, Shell = process is ShellProcessDefinition shell ? shell : null, Workflow = process is WorkflowProcessDefinition workflow ? workflow : null, - Await = this.AwaitProcess + Await = awaitProcess } }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/SchemaDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/SchemaDefinitionBuilder.cs index 40e71bc..62be2f5 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/SchemaDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/SchemaDefinitionBuilder.cs @@ -16,42 +16,48 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class SchemaDefinitionBuilder +public sealed class SchemaDefinitionBuilder : ISchemaDefinitionBuilder { - /// - /// Gets the to configure - /// - protected SchemaDefinition Schema { get; } = new(); + SchemaDefinition schema = new(); /// - public virtual ISchemaDefinitionBuilder WithFormat(string format) + public ISchemaDefinitionBuilder WithFormat(string format) { ArgumentException.ThrowIfNullOrWhiteSpace(format); - this.Schema.Format = format; + schema = schema with + { + Format = format + }; return this; } /// - public virtual ISchemaDefinitionBuilder WithResource(Action setup) + public ISchemaDefinitionBuilder WithResource(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ExternalResourceDefinitionBuilder(); setup(builder); - this.Schema.Resource = builder.Build(); + schema = schema with + { + Resource = builder.Build() + }; return this; } /// - public virtual ISchemaDefinitionBuilder WithDocument(object document) + public ISchemaDefinitionBuilder WithDocument(JsonObject document) { ArgumentNullException.ThrowIfNull(document); - this.Schema.Document = document; + schema = schema with + { + Document = document + }; return this; } /// - public virtual SchemaDefinition Build() => this.Schema; + public SchemaDefinition Build() => schema; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ScriptProcessDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ScriptProcessDefinitionBuilder.cs index 7db5d67..6e57e96 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ScriptProcessDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ScriptProcessDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -11,131 +11,108 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; -using ServerlessWorkflow.Sdk.Models.Processes; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ScriptProcessDefinitionBuilder +public sealed class ScriptProcessDefinitionBuilder : ProcessDefinitionBuilder, IScriptProcessDefinitionBuilder { - /// - /// Gets/sets the language of the script to run - /// - public virtual string? Language { get; set; } - - /// - /// Gets/sets the script's code - /// - public virtual string? Code { get; set; } - - /// - /// Gets/sets the script's source - /// - public ExternalResourceDefinition? Source { get; set; } - - /// - /// Gets/sets the uri that references the script's source. - /// - public Uri? SourceUri { get; set; } - - /// - /// Gets the arguments, if any, of the command to execute - /// - protected virtual EquatableDictionary? Arguments { get; set; } - - /// - /// Gets/sets the environment variables, if any, of the shell command to execute - /// - protected virtual EquatableDictionary? Environment { get; set; } + string? language; + string? code; + ExternalResourceDefinition? source; + Uri? sourceUri; + EquatableDictionary? arguments; + EquatableDictionary? environment; /// - public virtual IScriptProcessDefinitionBuilder WithLanguage(string language) + public IScriptProcessDefinitionBuilder WithLanguage(string language) { ArgumentException.ThrowIfNullOrWhiteSpace(language); - this.Language = language; + this.language = language; return this; } /// - public virtual IScriptProcessDefinitionBuilder WithCode(string code) + public IScriptProcessDefinitionBuilder WithCode(string code) { ArgumentException.ThrowIfNullOrWhiteSpace(code); - this.Code = code; + this.code = code; return this; } /// - public virtual IScriptProcessDefinitionBuilder WithSource(Uri source) + public IScriptProcessDefinitionBuilder WithSource(Uri source) { ArgumentNullException.ThrowIfNull(source); - this.SourceUri = source; + sourceUri = source; return this; } /// - public virtual IScriptProcessDefinitionBuilder WithSource(Action setup) + public IScriptProcessDefinitionBuilder WithSource(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ExternalResourceDefinitionBuilder(); setup(builder); - this.Source = builder.Build(); + source = builder.Build(); return this; } /// - public virtual IScriptProcessDefinitionBuilder WithArgument(string name, object value) + public IScriptProcessDefinitionBuilder WithArgument(string name, object value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Arguments ??= []; - this.Arguments[name] = value; + arguments ??= []; + arguments[name] = value; return this; } /// - public virtual IScriptProcessDefinitionBuilder WithArguments(IDictionary arguments) + public IScriptProcessDefinitionBuilder WithArguments(IDictionary arguments) { ArgumentNullException.ThrowIfNull(arguments); - this.Arguments = new(arguments); + this.arguments = [.. arguments]; return this; } /// - public virtual IScriptProcessDefinitionBuilder WithEnvironment(string name, string value) + public IScriptProcessDefinitionBuilder WithEnvironment(string name, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Environment ??= []; - this.Environment[name] = value; + environment ??= []; + environment[name] = value; return this; } /// - public virtual IScriptProcessDefinitionBuilder WithEnvironment(IDictionary environment) + public IScriptProcessDefinitionBuilder WithEnvironment(IDictionary environment) { ArgumentNullException.ThrowIfNull(environment); - this.Environment = new(environment); + this.environment = [.. environment]; return this; } /// public override ScriptProcessDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Language)) throw new NullReferenceException("The language in which the script to run is expressed must be set"); - if (string.IsNullOrWhiteSpace(this.Code) && this.Source == null && this.SourceUri == null) throw new NullReferenceException("Either the code or the source properties must be set"); - var process = new ScriptProcessDefinition() + if (string.IsNullOrWhiteSpace(language)) throw new NullReferenceException("The language in which the script to run is expressed must be set"); + if (string.IsNullOrWhiteSpace(code) && this.source == null && sourceUri == null) throw new NullReferenceException("Either the code or the source properties must be set"); + ExternalResourceDefinition? source = this.source; + if (source == null && sourceUri != null) source = new() + { + Endpoint = sourceUri + }; + return new() { - Language = this.Language, - Code = this.Code, - Arguments = this.Arguments, - Environment = this.Environment + Language = language, + Code = code, + Source = source, + Arguments = arguments, + Environment = environment }; - if (this.Source != null) process.Source = this.Source; - else if (this.SourceUri != null) process.Source = new() { EndpointUri = this.SourceUri }; - return process; } } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj b/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj index eccdd43..fc61704 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj +++ b/src/ServerlessWorkflow.Sdk.Builders/ServerlessWorkflow.Sdk.Builders.csproj @@ -1,10 +1,11 @@ - + - net8.0;net9.0 + net10.0 enable enable - 1.0.1 + true + 1.0.2 $(VersionPrefix) $(VersionPrefix) en @@ -16,7 +17,7 @@ serverless-workflow;serverless;workflow;dsl;sdk;builders true Apache-2.0 - readme.md + README.md Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. https://github.com/serverlessworkflow/sdk-net https://github.com/serverlessworkflow/sdk-net @@ -25,14 +26,18 @@ - - \ - True - + + + + \ + True + + + diff --git a/src/ServerlessWorkflow.Sdk.Builders/SetTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/SetTaskDefinitionBuilder.cs index 6cf8cf6..8affc7e 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/SetTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/SetTaskDefinitionBuilder.cs @@ -11,43 +11,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// /// A name/value mapping of the variables to set -public class SetTaskDefinitionBuilder(IDictionary? variables = null) +public sealed class SetTaskDefinitionBuilder(JsonObject? variables = null) : TaskDefinitionBuilder, ISetTaskDefinitionBuilder { - /// - /// Gets a name/value mapping of the variables to set - /// - protected EquatableDictionary Variables { get; set; } = [..variables]; + JsonObject variables = variables ?? []; /// - public virtual ISetTaskDefinitionBuilder Set(string name, object value) + public ISetTaskDefinitionBuilder Set(string name, JsonNode value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Variables[name] = value; + variables[name] = value; return this; } /// - public virtual ISetTaskDefinitionBuilder Set(IDictionary variables) + public ISetTaskDefinitionBuilder Set(JsonObject variables) { ArgumentNullException.ThrowIfNull(variables); - this.Variables = new(variables); + this.variables = variables; return this; } /// - public override SetTaskDefinition Build() => this.Configure(new() + public override SetTaskDefinition Build() => Configure(new() { - Set = this.Variables + Set = variables }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/ShellProcessDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/ShellProcessDefinitionBuilder.cs index d0055ca..d23304b 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/ShellProcessDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/ShellProcessDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -11,84 +11,70 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Processes; -using Neuroglia; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class ShellProcessDefinitionBuilder +public sealed class ShellProcessDefinitionBuilder : ProcessDefinitionBuilder, IShellProcessDefinitionBuilder { - /// - /// Gets the command to execute - /// - protected virtual string? Command { get; set; } - - /// - /// Gets the arguments, if any, of the command to execute - /// - protected virtual EquatableList? Arguments { get; set; } - - /// - /// Gets/sets the environment variables, if any, of the shell command to execute - /// - protected virtual EquatableDictionary? Environment { get; set; } + string? command; + EquatableList? arguments; + EquatableDictionary? environment; /// - public virtual IShellProcessDefinitionBuilder WithCommand(string command) + public IShellProcessDefinitionBuilder WithCommand(string command) { ArgumentException.ThrowIfNullOrWhiteSpace(command); - this.Command = command; + this.command = command; return this; } /// - public virtual IShellProcessDefinitionBuilder WithArgument(string argument) + public IShellProcessDefinitionBuilder WithArgument(string argument) { ArgumentException.ThrowIfNullOrWhiteSpace(argument); - this.Arguments ??= []; - this.Arguments.Add(argument); + this.arguments ??= []; + this.arguments.Add(argument); return this; } /// - public virtual IShellProcessDefinitionBuilder WithArguments(IEnumerable arguments) + public IShellProcessDefinitionBuilder WithArguments(IEnumerable arguments) { ArgumentNullException.ThrowIfNull(arguments); - this.Arguments = new(arguments); + this.arguments = [.. arguments]; return this; } /// - public virtual IShellProcessDefinitionBuilder WithEnvironment(string name, string value) + public IShellProcessDefinitionBuilder WithEnvironment(string name, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Environment ??= []; - this.Environment[name] = value; + this.environment ??= []; + this.environment[name] = value; return this; } /// - public virtual IShellProcessDefinitionBuilder WithEnvironment(IDictionary environment) + public IShellProcessDefinitionBuilder WithEnvironment(IDictionary environment) { ArgumentNullException.ThrowIfNull(environment); - this.Environment = new(environment); + this.environment = [.. environment]; return this; } /// public override ShellProcessDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Command)) throw new NullReferenceException("The shell command to execute must be set"); - return new() - { - Command = this.Command, - Arguments = this.Arguments, - Environment = this.Environment + if (string.IsNullOrWhiteSpace(this.command)) throw new NullReferenceException("The shell command to execute must be set"); + return new() + { + Command = this.command, + Arguments = this.arguments, + Environment = this.environment }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/SubscriptionIteratorDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/SubscriptionIteratorDefinitionBuilder.cs index 40954c9..5c75e86 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/SubscriptionIteratorDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/SubscriptionIteratorDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -16,62 +16,70 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class SubscriptionIteratorDefinitionBuilder +public sealed class SubscriptionIteratorDefinitionBuilder : ISubscriptionIteratorDefinitionBuilder { - /// - /// Gets the to configure - /// - protected SubscriptionIteratorDefinition Iterator { get; } = new(); + string? itemValue; + string? atValue; + Map? doTasks; + OutputDataModelDefinition? outputValue; + OutputDataModelDefinition? exportValue; /// - public virtual ISubscriptionIteratorDefinitionBuilder Item(string item) + public ISubscriptionIteratorDefinitionBuilder Item(string item) { ArgumentException.ThrowIfNullOrWhiteSpace(item); - this.Iterator.Item = item; + itemValue = item; return this; } /// - public virtual ISubscriptionIteratorDefinitionBuilder At(string at) + public ISubscriptionIteratorDefinitionBuilder At(string at) { ArgumentException.ThrowIfNullOrWhiteSpace(at); - this.Iterator.At = at; + atValue = at; return this; } /// - public virtual ISubscriptionIteratorDefinitionBuilder Do(Action setup) + public ISubscriptionIteratorDefinitionBuilder Do(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.Iterator.Do = builder.Build(); + doTasks = builder.Build(); return this; } /// - public virtual ISubscriptionIteratorDefinitionBuilder Output(Action setup) + public ISubscriptionIteratorDefinitionBuilder Output(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new OutputDataModelDefinitionBuilder(); setup(builder); - this.Iterator.Output = builder.Build(); + outputValue = builder.Build(); return this; } /// - public virtual ISubscriptionIteratorDefinitionBuilder Export(Action setup) + public ISubscriptionIteratorDefinitionBuilder Export(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new OutputDataModelDefinitionBuilder(); setup(builder); - this.Iterator.Export = builder.Build(); + exportValue = builder.Build(); return this; } /// - public virtual SubscriptionIteratorDefinition Build() => this.Iterator; + public SubscriptionIteratorDefinition Build() => new() + { + Item = itemValue, + At = atValue, + Do = doTasks, + Output = outputValue, + Export = exportValue + }; -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Builders/Suppressions.cs b/src/ServerlessWorkflow.Sdk.Builders/Suppressions.cs new file mode 100644 index 0000000..f8efb47 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Builders/Suppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "namespace", Target = "~N:ServerlessWorkflow.Sdk.Builders")] diff --git a/src/ServerlessWorkflow.Sdk.Builders/SwitchCaseDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/SwitchCaseDefinitionBuilder.cs index 59ef137..efbf980 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/SwitchCaseDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/SwitchCaseDefinitionBuilder.cs @@ -16,42 +16,35 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class SwitchCaseDefinitionBuilder +public sealed class SwitchCaseDefinitionBuilder : ISwitchCaseDefinitionBuilder { - /// - /// Gets/sets the runtime expression used to determine whether or not the case to build matches - /// - protected virtual string? WhenExpression { get; set; } - - /// - /// Gets/sets the flow directive to execute when the case to build matches - /// - protected virtual string? ThenDirective { get; set; } + string? whenExpression; + string? thenDirective; /// - public virtual ISwitchCaseDefinitionBuilder When(string expression) + public ISwitchCaseDefinitionBuilder When(string expression) { - this.WhenExpression = expression; + whenExpression = expression; return this; } /// - public virtual ISwitchCaseDefinitionBuilder Then(string directive) + public ISwitchCaseDefinitionBuilder Then(string directive) { ArgumentException.ThrowIfNullOrWhiteSpace(directive); - this.ThenDirective = directive; + thenDirective = directive; return this; } /// - public virtual SwitchCaseDefinition Build() + public SwitchCaseDefinition Build() { - if (string.IsNullOrWhiteSpace(this.ThenDirective)) throw new NullReferenceException("The flow directive to execute when the switch case matches must be set"); + if (string.IsNullOrWhiteSpace(thenDirective)) throw new NullReferenceException("The flow directive to execute when the switch case matches must be set"); return new() { - When = this.WhenExpression, - Then = this.ThenDirective + When = whenExpression, + Then = thenDirective }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/SwitchTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/SwitchTaskDefinitionBuilder.cs index 1678aa9..4f04ac3 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/SwitchTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/SwitchTaskDefinitionBuilder.cs @@ -16,14 +16,11 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class SwitchTaskDefinitionBuilder +public sealed class SwitchTaskDefinitionBuilder : TaskDefinitionBuilder, ISwitchTaskDefinitionBuilder { - /// - /// Gets a name/value mapping of the cases of the to build - /// - protected Map Cases { get; } = []; + readonly Map cases = []; /// public ISwitchTaskDefinitionBuilder Case(string name, Action setup) @@ -33,14 +30,14 @@ public ISwitchTaskDefinitionBuilder Case(string name, Action public override SwitchTaskDefinition Build() => this.Configure(new() { - Switch = this.Cases + Switch = cases }); } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs index 697e67b..0f328a7 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionBuilder.cs @@ -58,7 +58,7 @@ public abstract class TaskDefinitionBuilder public virtual TBuilder If(string condition) { ArgumentException.ThrowIfNullOrWhiteSpace(condition); - this.IfExpression = condition; + IfExpression = condition; return (TBuilder)(object)this; } @@ -66,7 +66,7 @@ public virtual TBuilder If(string condition) public virtual TBuilder WithTimeout(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Timeout = name; + Timeout = name; return (TBuilder)(object)this; } @@ -74,7 +74,7 @@ public virtual TBuilder WithTimeout(string name) public virtual TBuilder WithTimeout(TimeoutDefinition timeout) { ArgumentNullException.ThrowIfNull(timeout); - this.Timeout = timeout; + Timeout = timeout; return (TBuilder)(object)this; } @@ -84,7 +84,7 @@ public virtual TBuilder WithTimeout(Action setup) ArgumentNullException.ThrowIfNull(setup); var builder = new TimeoutDefinitionBuilder(); setup(builder); - this.Timeout = builder.Build(); + Timeout = builder.Build(); return (TBuilder)(object)this; } @@ -94,7 +94,7 @@ public virtual TBuilder WithInput(Action setup ArgumentNullException.ThrowIfNull(setup); var builder = new InputDataModelDefinitionBuilder(); setup(builder); - this.Input = builder.Build(); + Input = builder.Build(); return (TBuilder)(object)this; } @@ -104,7 +104,7 @@ public virtual TBuilder WithOutput(Action set ArgumentNullException.ThrowIfNull(setup); var builder = new OutputDataModelDefinitionBuilder(); setup(builder); - this.Output = builder.Build(); + Output = builder.Build(); return (TBuilder)(object)this; } @@ -114,7 +114,7 @@ public virtual TBuilder WithExport(Action set ArgumentNullException.ThrowIfNull(setup); var builder = new OutputDataModelDefinitionBuilder(); setup(builder); - this.Export = builder.Build(); + Export = builder.Build(); return (TBuilder)(object)this; } @@ -122,7 +122,7 @@ public virtual TBuilder WithExport(Action set public virtual TBuilder Then(string directive) { ArgumentException.ThrowIfNullOrWhiteSpace(directive); - this.ThenDirective = directive; + ThenDirective = directive; return (TBuilder)(object)this; } @@ -133,22 +133,20 @@ public virtual TBuilder Then(string directive) /// The configured task definition protected virtual TDefinition Configure(TDefinition definition) { - definition.If = this.IfExpression; - if (this.Timeout != null) + return definition with { - if (this.Timeout.T1Value != null) definition.Timeout = this.Timeout.T1Value; - else definition.TimeoutReference = this.Timeout.T2Value; - } - definition.Then = this.ThenDirective; - definition.Input = this.Input; - definition.Output = this.Output; - definition.Export = this.Export; - return definition; + If = IfExpression, + Timeout = Timeout, + Then = ThenDirective, + Input = Input, + Output = Output, + Export = Export + }; ; } /// public abstract TDefinition Build(); - TaskDefinition ITaskDefinitionBuilder.Build() => this.Build(); + TaskDefinition ITaskDefinitionBuilder.Build() => Build(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionMapBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionMapBuilder.cs index e25b342..8ea166d 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionMapBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/TaskDefinitionMapBuilder.cs @@ -16,27 +16,24 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class TaskDefinitionMapBuilder +public sealed class TaskDefinitionMapBuilder : ITaskDefinitionMapBuilder { - /// - /// Gets a name/value mapping of the tasks the workflow is made out of - /// - protected Map? Tasks { get; set; } + Map? tasks; /// - public virtual ITaskDefinitionMapBuilder Do(string name, TaskDefinition task) + public ITaskDefinitionMapBuilder Do(string name, TaskDefinition task) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(task); - this.Tasks ??= []; - this.Tasks[name] = task; + tasks ??= []; + tasks[name] = task; return this; } /// - public virtual ITaskDefinitionMapBuilder Do(string name, Action setup) + public ITaskDefinitionMapBuilder Do(string name, Action setup) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(setup); @@ -47,10 +44,10 @@ public virtual ITaskDefinitionMapBuilder Do(string name, Action - public virtual Map Build() + public Map Build() { - if (this.Tasks == null || this.Tasks.Count < 1) throw new NullReferenceException("The task must define at least one subtask"); - return this.Tasks; + if (tasks == null || tasks.Count < 1) throw new NullReferenceException("The task must define at least one subtask"); + return tasks; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Builders/TimeoutDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/TimeoutDefinitionBuilder.cs index 119da96..83006ec 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/TimeoutDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/TimeoutDefinitionBuilder.cs @@ -18,38 +18,35 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class TimeoutDefinitionBuilder +public sealed class TimeoutDefinitionBuilder : ITimeoutDefinitionBuilder { - /// - /// Gets/sets the duration after which to timeout - /// - protected Duration? AfterValue { get; set; } + Duration? afterValue; /// - public virtual ITimeoutDefinitionBuilder After(string duration) + public ITimeoutDefinitionBuilder After(string duration) { ArgumentException.ThrowIfNullOrWhiteSpace(duration); - this.AfterValue = XmlConvert.ToTimeSpan(duration); + afterValue = XmlConvert.ToTimeSpan(duration); return this; } /// - public virtual ITimeoutDefinitionBuilder After(Duration duration) + public ITimeoutDefinitionBuilder After(Duration duration) { ArgumentNullException.ThrowIfNull(duration); - this.AfterValue = duration; + afterValue = duration; return this; } /// - public virtual TimeoutDefinition Build() + public TimeoutDefinition Build() { - if (this.AfterValue == null) throw new NullReferenceException("The duration after which to timeout must be set"); + if (afterValue == null) throw new NullReferenceException("The duration after which to timeout must be set"); return new() { - After = this.AfterValue + After = afterValue }; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs index 2eb9566..48e44c8 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/TryTaskDefinitionBuilder.cs @@ -16,48 +16,41 @@ namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class TryTaskDefinitionBuilder +public sealed class TryTaskDefinitionBuilder : TaskDefinitionBuilder, ITryTaskDefinitionBuilder { - /// - /// Gets/sets the tasks to try - /// - protected Map? TryTasks { get; set; } - - /// - /// Gets/sets the definition of the error catcher to use - /// - protected ErrorCatcherDefinition? ErrorCatcher { get; set; } + Map? tryTasks; + ErrorCatcherDefinition? errorCatcher; /// - public virtual ITryTaskDefinitionBuilder Do(Action setup) + public ITryTaskDefinitionBuilder Do(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TaskDefinitionMapBuilder(); setup(builder); - this.TryTasks = builder.Build(); + tryTasks = builder.Build(); return this; } /// - public virtual ITryTaskDefinitionBuilder Catch(Action setup) + public ITryTaskDefinitionBuilder Catch(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new ErrorCatcherDefinitionBuilder(); - this.ErrorCatcher = builder.Build(); + errorCatcher = builder.Build(); return this; } /// public override TryTaskDefinition Build() { - if (this.TryTasks == null || this.TryTasks.Count < 1) throw new NullReferenceException("The task to try must be set"); - if (this.ErrorCatcher == null) throw new NullReferenceException("The catch clause must be set"); + if (tryTasks == null || tryTasks.Count < 1) throw new NullReferenceException("The task to try must be set"); + if (errorCatcher == null) throw new NullReferenceException("The catch clause must be set"); return this.Configure(new() { - Try = this.TryTasks, - Catch = this.ErrorCatcher + Try = tryTasks, + Catch = errorCatcher }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/Usings.cs b/src/ServerlessWorkflow.Sdk.Builders/Usings.cs index 03361af..715ef60 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/Usings.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/Usings.cs @@ -11,6 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +global using Semver; global using ServerlessWorkflow.Sdk.Models; global using ServerlessWorkflow.Sdk.Models.Authentication; -global using ServerlessWorkflow.Sdk.Models.Tasks; \ No newline at end of file +global using ServerlessWorkflow.Sdk.Models.Calls; +global using ServerlessWorkflow.Sdk.Models.Processes; +global using ServerlessWorkflow.Sdk.Models.Tasks; +global using System.Text.Json.Nodes; +global using System.Text.RegularExpressions; diff --git a/src/ServerlessWorkflow.Sdk.Builders/WaitTaskDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/WaitTaskDefinitionBuilder.cs index 264d8cc..7944142 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/WaitTaskDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/WaitTaskDefinitionBuilder.cs @@ -17,30 +17,27 @@ namespace ServerlessWorkflow.Sdk.Builders; /// Represents the default implementation of the interface /// /// The amount of time to wait for -public class WaitTaskDefinitionBuilder(Duration? duration = null) +public sealed class WaitTaskDefinitionBuilder(Duration? duration = null) : TaskDefinitionBuilder, IWaitTaskDefinitionBuilder { - /// - /// Gets/sets the amount of time to wait for - /// - protected Duration? Duration { get; set; } = duration; + Duration? duration = duration; /// - public virtual IWaitTaskDefinitionBuilder For(Duration duration) + public IWaitTaskDefinitionBuilder For(Duration duration) { ArgumentNullException.ThrowIfNull(duration); - this.Duration = duration; + this.duration = duration; return this; } /// public override WaitTaskDefinition Build() { - if (this.Duration == null) throw new NullReferenceException("The amount of time to wait for must be set"); + if (duration == null) throw new NullReferenceException("The amount of time to wait for must be set"); return this.Configure(new() { - Wait = this.Duration + Wait = duration }); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/WorkflowDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/WorkflowDefinitionBuilder.cs index 96995be..cc58241 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/WorkflowDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/WorkflowDefinitionBuilder.cs @@ -11,337 +11,310 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Neuroglia; -using Semver; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class WorkflowDefinitionBuilder +public sealed class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder { - /// - /// Gets/sets the version of the DSL used to define the workflow - /// - protected string Dsl { get; set; } = DslVersion.V1; - - /// - /// Gets/sets the workflow's namespace - /// - protected string? Namespace { get; set; } - - /// - /// Gets/sets the workflow's name - /// - protected string? Name { get; set; } - - /// - /// Gets the workflow's semantic version - /// - protected string? Version { get; set; } - - /// - /// Gets/sets the workflow's title - /// - protected string? Title { get; set; } - - /// - /// Gets/sets the workflow's Markdown summary - /// - protected string? Summary { get; set; } - - /// - /// Gets/sets the workflow's tags - /// - protected EquatableDictionary? Tags { get; set; } - - /// - /// Gets/sets the workflow's timeout, if any - /// - protected OneOf? Timeout { get; set; } - - /// - /// Gets/sets the workflow's input data, if any - /// - protected InputDataModelDefinition? Input { get; set; } - - /// - /// Gets/sets the workflow's output data, if any - /// - protected OutputDataModelDefinition? Output { get; set; } - - /// - /// Gets/sets a name/value mapping of the workflow's reusable components - /// - protected ComponentDefinitionCollection? Components { get; set; } - - /// - /// Gets/sets a name/value mapping of the tasks the workflow is made out of - /// - protected Map? Tasks { get; set; } + string dsl = ServerlessWorkflowSpecificationDefaults.Version; + string? @namespace; + string? name; + string? version; + string? title; + string? summary; + EquatableDictionary? tags; + OneOf? timeout; + InputDataModelDefinition? input; + OutputDataModelDefinition? output; + ComponentDefinitionCollection? components; + Map? tasks; /// - public virtual IWorkflowDefinitionBuilder UseDsl(string version) + public IWorkflowDefinitionBuilder UseDsl(string version) { ArgumentException.ThrowIfNullOrWhiteSpace(version); if (!SemVersion.TryParse(version, SemVersionStyles.Strict, out _)) throw new ArgumentException($"The specified value '{version}' is not a valid semantic version (SemVer 2.0)", nameof(version)); - this.Dsl = version; + dsl = version; return this; } /// - public virtual IWorkflowDefinitionBuilder WithNamespace(string @namespace) + public IWorkflowDefinitionBuilder WithNamespace(string @namespace) { ArgumentException.ThrowIfNullOrWhiteSpace(@namespace); if (!NamingConvention.IsValidName(@namespace)) throw new ArgumentException($"The the specified value '{@namespace}' is not a valid RFC1123 DNS label name", nameof(@namespace)); - this.Namespace = @namespace; + this.@namespace = @namespace; return this; } /// - public virtual IWorkflowDefinitionBuilder WithName(string name) + public IWorkflowDefinitionBuilder WithName(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); if (!NamingConvention.IsValidName(name)) throw new ArgumentException($"The the specified value '{name}' is not a valid RFC1123 DNS label name", nameof(name)); - this.Name = name; + this.name = name; return this; } /// - public virtual IWorkflowDefinitionBuilder WithVersion(string version) + public IWorkflowDefinitionBuilder WithVersion(string version) { ArgumentException.ThrowIfNullOrWhiteSpace(version); if (!SemVersion.TryParse(version, SemVersionStyles.Strict, out _)) throw new ArgumentException($"The specified value '{version}' is not a valid semantic version (SemVer 2.0)", nameof(version)); - this.Version = version; + this.version = version; return this; } /// - public virtual IWorkflowDefinitionBuilder WithTitle(string title) + public IWorkflowDefinitionBuilder WithTitle(string title) { - this.Title = title; + this.title = title; return this; } /// - public virtual IWorkflowDefinitionBuilder WithSummary(string description) + public IWorkflowDefinitionBuilder WithSummary(string summary) { - this.Summary = description; + this.summary = summary; return this; } /// - public virtual IWorkflowDefinitionBuilder WithTag(string name, string value) + public IWorkflowDefinitionBuilder WithTag(string name, string value) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Tags ??= []; - this.Tags[name] = value; + tags ??= []; + tags[name] = value; return this; } /// - public virtual IWorkflowDefinitionBuilder WithTag(IDictionary arguments) + public IWorkflowDefinitionBuilder WithTag(IDictionary tags) { - ArgumentNullException.ThrowIfNull(arguments); - this.Tags = new(arguments); + ArgumentNullException.ThrowIfNull(tags); + this.tags = [.. tags]; return this; } /// - public virtual IWorkflowDefinitionBuilder WithTimeout(string name) + public IWorkflowDefinitionBuilder WithTimeout(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - this.Timeout = name; + timeout = name; return this; } /// - public virtual IWorkflowDefinitionBuilder WithTimeout(TimeoutDefinition timeout) + public IWorkflowDefinitionBuilder WithTimeout(TimeoutDefinition timeout) { ArgumentNullException.ThrowIfNull(timeout); - this.Timeout = timeout; + this.timeout = timeout; return this; } /// - public virtual IWorkflowDefinitionBuilder WithTimeout(Action setup) + public IWorkflowDefinitionBuilder WithTimeout(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new TimeoutDefinitionBuilder(); setup(builder); - this.Timeout = builder.Build(); + timeout = builder.Build(); return this; } /// - public virtual IWorkflowDefinitionBuilder WithInput(Action setup) + public IWorkflowDefinitionBuilder WithInput(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new InputDataModelDefinitionBuilder(); setup(builder); - this.Input = builder.Build(); + this.input = builder.Build(); return this; } /// - public virtual IWorkflowDefinitionBuilder WithOutput(Action setup) + public IWorkflowDefinitionBuilder WithOutput(Action setup) { ArgumentNullException.ThrowIfNull(setup); var builder = new OutputDataModelDefinitionBuilder(); setup(builder); - this.Output = builder.Build(); + this.output = builder.Build(); return this; } /// - public virtual IWorkflowDefinitionBuilder UseAuthentication(string name, AuthenticationPolicyDefinition authentication) + public IWorkflowDefinitionBuilder UseAuthentication(string name, AuthenticationPolicyDefinition authentication) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(authentication); - this.Components ??= new(); - this.Components.Authentications ??= []; - this.Components.Authentications[name] = authentication; + components ??= new(); + var authentications = components.Authentications ?? []; + authentications[name] = authentication; + components = components with + { + Authentications = authentications + }; return this; } /// - public virtual IWorkflowDefinitionBuilder UseAuthentication(string name, Action setup) + public IWorkflowDefinitionBuilder UseAuthentication(string name, Action setup) { var builder = new AuthenticationPolicyDefinitionBuilder(); setup(builder); - return this.UseAuthentication(name, builder.Build()); + return UseAuthentication(name, builder.Build()); } /// - public virtual IWorkflowDefinitionBuilder UseExtension(string name, ExtensionDefinition extension) + public IWorkflowDefinitionBuilder UseExtension(string name, ExtensionDefinition extension) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(extension); - this.Components ??= new(); - this.Components.Extensions ??= []; - this.Components.Extensions[name] = extension; + components ??= new(); + var extensions = components.Extensions ?? []; + extensions[name] = extension; + components = components with + { + Extensions = extensions + }; return this; } /// - public virtual IWorkflowDefinitionBuilder UseExtension(string name, Action setup) + public IWorkflowDefinitionBuilder UseExtension(string name, Action setup) { var builder = new ExtensionDefinitionBuilder(); setup(builder); - return this.UseExtension(name, builder.Build()); + return UseExtension(name, builder.Build()); } /// - public virtual IWorkflowDefinitionBuilder UseFunction(string name, TaskDefinition task) + public IWorkflowDefinitionBuilder UseFunction(string name, TaskDefinition task) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(task); - this.Components ??= new(); - this.Components.Functions ??= []; - this.Components.Functions[name] = task; + components ??= new(); + var functions = components.Functions ?? []; + functions[name] = task; + components = components with + { + Functions = functions + }; return this; } /// - public virtual IWorkflowDefinitionBuilder UseFunction(string name, Action setup) + public IWorkflowDefinitionBuilder UseFunction(string name, Action setup) { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentNullException.ThrowIfNull(setup); var builder = new GenericTaskDefinitionBuilder(); setup(builder); - return this.UseFunction(name, builder.Build()); + return UseFunction(name, builder.Build()); } /// - public virtual IWorkflowDefinitionBuilder UseRetry(string name, RetryPolicyDefinition retry) + public IWorkflowDefinitionBuilder UseRetry(string name, RetryPolicyDefinition retry) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(retry); - this.Components ??= new(); - this.Components.Retries ??= []; - this.Components.Retries[name] = retry; + components ??= new(); + var retries = components.Retries ?? []; + retries[name] = retry; + components = components with + { + Retries = retries + }; return this; } /// - public virtual IWorkflowDefinitionBuilder UseRetry(string name, Action setup) + public IWorkflowDefinitionBuilder UseRetry(string name, Action setup) { var builder = new RetryPolicyDefinitionBuilder(); setup(builder); - return this.UseRetry(name, builder.Build()); + return UseRetry(name, builder.Build()); } /// - public virtual IWorkflowDefinitionBuilder UseSecret(string secret) + public IWorkflowDefinitionBuilder UseSecret(string secret) { ArgumentException.ThrowIfNullOrWhiteSpace(secret); - this.Components ??= new(); - this.Components.Secrets ??= []; - this.Components.Secrets.Add(secret); + components ??= new(); + var secrets = components.Secrets ?? []; + secrets.Add(secret); + components = components with + { + Secrets = secrets + }; return this; } /// - public virtual IWorkflowDefinitionBuilder UseSecrets(params string[] secrets) + public IWorkflowDefinitionBuilder UseSecrets(params string[] secrets) { ArgumentNullException.ThrowIfNull(secrets); - this.Components ??= new(); - this.Components.Secrets = new(secrets); + components ??= new(); + var existingSecrets = components.Secrets ?? []; + existingSecrets.AddRange(secrets); + components = components with + { + Secrets = existingSecrets + }; return this; } /// - public virtual IWorkflowDefinitionBuilder Do(string name, TaskDefinition task) + public IWorkflowDefinitionBuilder Do(string name, TaskDefinition task) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(task); - this.Tasks ??= []; - this.Tasks[name] = task; + tasks ??= []; + tasks[name] = task; return this; } /// - public virtual IWorkflowDefinitionBuilder Do(string name, Action setup) + public IWorkflowDefinitionBuilder Do(string name, Action setup) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(setup); var builder = new GenericTaskDefinitionBuilder(); setup(builder); var task = builder.Build(); - return this.Do(name, task); + return Do(name, task); } /// - public virtual WorkflowDefinition Build() + public WorkflowDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Dsl)) throw new NullReferenceException("The workflow DSL must be set"); - if (string.IsNullOrWhiteSpace(this.Name)) throw new NullReferenceException("The workflow name must be set"); - if (string.IsNullOrWhiteSpace(this.Version)) throw new NullReferenceException("The workflow version must be set"); - if (this.Tasks == null || this.Tasks.Count < 1) throw new NullReferenceException("The workflow must define at least one task"); + if (string.IsNullOrWhiteSpace(dsl)) throw new NullReferenceException("The workflow DSL must be set"); + if (string.IsNullOrWhiteSpace(name)) throw new NullReferenceException("The workflow name must be set"); + if (string.IsNullOrWhiteSpace(version)) throw new NullReferenceException("The workflow version must be set"); + if (tasks == null || tasks.Count < 1) throw new NullReferenceException("The workflow must define at least one task"); var definition = new WorkflowDefinition() { Document = new() { - Dsl = this.Dsl, - Namespace = string.IsNullOrWhiteSpace(this.Namespace) ? WorkflowDefinitionMetadata.DefaultNamespace : this.Namespace, - Name = this.Name, - Version = this.Version, - Title = this.Title, - Summary = this.Summary, - Tags = this.Tags + Dsl = dsl, + Namespace = string.IsNullOrWhiteSpace(@namespace) ? WorkflowDefinitionMetadata.DefaultNamespace : @namespace, + Name = name, + Version = version, + Title = title, + Summary = summary, + Tags = tags }, - Use = this.Components, - Do = this.Tasks + Use = components, + Do = tasks, + Timeout = timeout }; - if(this.Timeout != null) - { - if (this.Timeout.T1Value != null) definition.Timeout = this.Timeout.T1Value; - else definition.TimeoutReference = this.Timeout.T2Value; - } return definition; } - Map ITaskDefinitionMapBuilder.Build() => this.Tasks!; + Map ITaskDefinitionMapBuilder.Build() => tasks!; } diff --git a/src/ServerlessWorkflow.Sdk.Builders/WorkflowProcessDefinitionBuilder.cs b/src/ServerlessWorkflow.Sdk.Builders/WorkflowProcessDefinitionBuilder.cs index e546c48..4052993 100644 --- a/src/ServerlessWorkflow.Sdk.Builders/WorkflowProcessDefinitionBuilder.cs +++ b/src/ServerlessWorkflow.Sdk.Builders/WorkflowProcessDefinitionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -11,84 +11,67 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Processes; -using Semver; - namespace ServerlessWorkflow.Sdk.Builders; /// /// Represents the default implementation of the interface /// -public class WorkflowProcessDefinitionBuilder +public sealed partial class WorkflowProcessDefinitionBuilder : ProcessDefinitionBuilder, IWorkflowProcessDefinitionBuilder { - /// - /// Gets/sets the namespace of the workflow to run - /// - protected virtual string? Namespace { get; set; } - - /// - /// Gets/sets the name of the workflow to run - /// - protected virtual string? Name { get; set; } - - /// - /// Gets/sets the version of the workflow to run. Defaults to `latest` - /// - protected virtual string Version { get; set; } = "latest"; - - /// - /// Gets/sets the data, if any, to pass as input to the workflow to execute. The value should be validated against the target workflow's input schema, if specified - /// - protected virtual object? Input { get; set; } + string? @namespace; + string? name; + string version = "latest"; + JsonObject? input; /// - public virtual IWorkflowProcessDefinitionBuilder WithNamespace(string @namespace) + public IWorkflowProcessDefinitionBuilder WithNamespace(string @namespace) { ArgumentException.ThrowIfNullOrWhiteSpace(@namespace); - if (!NamingConvention.IsValidName(@namespace)) throw new ArgumentException($"The the specified value '{@namespace}' is not a valid RFC1123 DNS label name", nameof(@namespace)); - this.Namespace = @namespace; + if (!DnsLabelRegex().IsMatch(@namespace)) throw new ArgumentException($"The specified value '{@namespace}' is not a valid RFC1123 DNS label name", nameof(@namespace)); + this.@namespace = @namespace; return this; } /// - public virtual IWorkflowProcessDefinitionBuilder WithName(string name) + public IWorkflowProcessDefinitionBuilder WithName(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); - if (!NamingConvention.IsValidName(name)) throw new ArgumentException($"The the specified value '{name}' is not a valid RFC1123 DNS label name", nameof(name)); - this.Name = name; + if (!DnsLabelRegex().IsMatch(name)) throw new ArgumentException($"The specified value '{name}' is not a valid RFC1123 DNS label name", nameof(name)); + this.name = name; return this; } /// - public virtual IWorkflowProcessDefinitionBuilder WithVersion(string version) + public IWorkflowProcessDefinitionBuilder WithVersion(string version) { ArgumentException.ThrowIfNullOrWhiteSpace(version); - if (!SemVersion.TryParse(version, SemVersionStyles.Strict, out _)) throw new ArgumentException($"The specified value '{version}' is not a valid semantic version (SemVer 2.0)", nameof(version)); - this.Version = version; + this.version = version; return this; } /// - public virtual IWorkflowProcessDefinitionBuilder WithInput(object input) + public IWorkflowProcessDefinitionBuilder WithInput(JsonObject input) { - this.Input = input; + this.input = input; return this; } /// public override WorkflowProcessDefinition Build() { - if (string.IsNullOrWhiteSpace(this.Name)) throw new NullReferenceException("The name of the workflow to run must be set"); - if (string.IsNullOrWhiteSpace(this.Version)) throw new NullReferenceException("The version of the workflow to run must be set"); + if (string.IsNullOrWhiteSpace(name)) throw new NullReferenceException("The name of the workflow to run must be set"); + if (string.IsNullOrWhiteSpace(version)) throw new NullReferenceException("The version of the workflow to run must be set"); return new() { - Namespace = string.IsNullOrWhiteSpace(this.Namespace) ? WorkflowDefinitionMetadata.DefaultNamespace : this.Namespace, - Name = this.Name, - Version = this.Version, - Input = this.Input + Namespace = string.IsNullOrWhiteSpace(@namespace) ? WorkflowDefinitionMetadata.DefaultNamespace : @namespace, + Name = name, + Version = version, + Input = input }; } + [GeneratedRegex(@"^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$", RegexOptions.Compiled)] + private static partial Regex DnsLabelRegex(); } diff --git a/src/ServerlessWorkflow.Sdk.Builders/readme.md b/src/ServerlessWorkflow.Sdk.Builders/readme.md new file mode 100644 index 0000000..d4fc436 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Builders/readme.md @@ -0,0 +1,52 @@ +# ServerlessWorkflow.Sdk.Builders + +Fluent builders for constructing [Serverless Workflow DSL](https://github.com/serverlessworkflow/specification/blob/main/dsl.md) definitions programmatically in .NET. + +This package provides a strongly-typed, chainable API for authoring `WorkflowDefinition` instances without having to hand-write JSON or YAML. + +## Installation + +```bash +dotnet add package ServerlessWorkflow.Sdk.Builders +``` + +## Usage + +```csharp +using ServerlessWorkflow.Sdk.Builders; + +var definition = new WorkflowDefinitionBuilder() + .UseDsl("1.0.0") + .WithNamespace("samples") + .WithName("fake-workflow") + .WithVersion("0.1.0") + .WithTitle("Fake Workflow") + .WithSummary("A sample workflow that calls an HTTP endpoint.") + .Do("fetch-data", task => task + .Call("http") + .With("method", "get") + .With("uri", "https://fake-api.com")) + .Build(); +``` + +### Common builder methods + +| Method | Purpose | +|---|---| +| `UseDsl(version)` | Sets the DSL version. | +| `WithNamespace(ns)` | RFC1123 DNS label namespace. | +| `WithName(name)` / `WithVersion(version)` | Identity of the workflow. | +| `WithTitle` / `WithSummary` / `WithTag` | Metadata. | +| `WithInput` / `WithOutput` | Input/output schemas. | +| `UseAuthentication` / `UseExtension` / `UseFunction` / `UseRetry` / `UseSecret` | Reusable components. | +| `Do(name, task => ...)` | Adds a task to the workflow. | +| `Build()` | Returns a `WorkflowDefinition`. | + +Dedicated builders are available for every DSL construct — call, do, for, fork, listen, raise, run, set, switch, try, wait, retry policies, authentication schemes, and more. + +## Related packages + +- [ServerlessWorkflow.Sdk](../ServerlessWorkflow.Sdk) — core DSL models +- [ServerlessWorkflow.Sdk.Runtime](../ServerlessWorkflow.Sdk.Runtime) — workflow runtime +- [ServerlessWorkflow.Sdk.Runtime.Cli](../ServerlessWorkflow.Sdk.Runtime.Cli) — `swf` command-line runner +- [Project root](../../README.md) diff --git a/src/ServerlessWorkflow.Sdk.IO/Extensions/IServiceCollectionExtensions.cs b/src/ServerlessWorkflow.Sdk.IO/Extensions/IServiceCollectionExtensions.cs index e0cd5b1..8c4cba6 100644 --- a/src/ServerlessWorkflow.Sdk.IO/Extensions/IServiceCollectionExtensions.cs +++ b/src/ServerlessWorkflow.Sdk.IO/Extensions/IServiceCollectionExtensions.cs @@ -11,11 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; -using Neuroglia.Serialization.Yaml; -using ServerlessWorkflow.Sdk.Serialization.Yaml; - +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace ServerlessWorkflow.Sdk.IO; /// @@ -31,25 +27,6 @@ public static class IServiceCollectionExtensions /// The configured public static IServiceCollection AddServerlessWorkflowIO(this IServiceCollection services) { - services.AddJsonSerializer(); - services.AddYamlDotNetSerializer(options => - { - YamlSerializer.DefaultSerializerConfiguration(options.Serializer); - YamlSerializer.DefaultDeserializerConfiguration(options.Deserializer); - options.Deserializer.WithNodeDeserializer( - inner => new TaskDefinitionYamlDeserializer(inner), - syntax => syntax.InsteadOf()); - options.Deserializer.WithNodeDeserializer( - inner => new OneOfNodeDeserializer(inner), - syntax => syntax.InsteadOf()); - options.Deserializer.WithNodeDeserializer( - inner => new OneOfScalarDeserializer(inner), - syntax => syntax.InsteadOf()); - var mapEntryConverter = new MapEntryYamlConverter(() => options.Serializer.Build(), () => options.Deserializer.Build()); - options.Deserializer.WithTypeConverter(mapEntryConverter); - options.Serializer.WithTypeConverter(mapEntryConverter); - options.Serializer.WithTypeConverter(new OneOfConverter()); - }); services.AddSingleton(); services.AddSingleton(); return services; diff --git a/src/ServerlessWorkflow.Sdk.IO/Interfaces/IWorkflowDefinitionReader.cs b/src/ServerlessWorkflow.Sdk.IO/IWorkflowDefinitionReader.cs similarity index 100% rename from src/ServerlessWorkflow.Sdk.IO/Interfaces/IWorkflowDefinitionReader.cs rename to src/ServerlessWorkflow.Sdk.IO/IWorkflowDefinitionReader.cs diff --git a/src/ServerlessWorkflow.Sdk.IO/Interfaces/IWorkflowDefinitionWriter.cs b/src/ServerlessWorkflow.Sdk.IO/IWorkflowDefinitionWriter.cs similarity index 99% rename from src/ServerlessWorkflow.Sdk.IO/Interfaces/IWorkflowDefinitionWriter.cs rename to src/ServerlessWorkflow.Sdk.IO/IWorkflowDefinitionWriter.cs index dc0f63a..4a99564 100644 --- a/src/ServerlessWorkflow.Sdk.IO/Interfaces/IWorkflowDefinitionWriter.cs +++ b/src/ServerlessWorkflow.Sdk.IO/IWorkflowDefinitionWriter.cs @@ -29,4 +29,4 @@ public interface IWorkflowDefinitionWriter /// A new awaitable Task WriteAsync(WorkflowDefinition workflow, Stream stream, string format = WorkflowDefinitionFormat.Yaml, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj b/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj index c1a5989..b8813f3 100644 --- a/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj +++ b/src/ServerlessWorkflow.Sdk.IO/ServerlessWorkflow.Sdk.IO.csproj @@ -1,10 +1,10 @@ - + - net8.0;net9.0 + net10.0 enable enable - 1.0.1 + 1.0.2 $(VersionPrefix) $(VersionPrefix) en @@ -16,7 +16,7 @@ serverless-workflow;serverless;workflow;dsl;sdk;io true Apache-2.0 - readme.md + README.md Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. https://github.com/serverlessworkflow/sdk-net https://github.com/serverlessworkflow/sdk-net @@ -25,14 +25,19 @@ - - \ - True - + + + + + + \ + True + + diff --git a/src/ServerlessWorkflow.Sdk.IO/Usings.cs b/src/ServerlessWorkflow.Sdk.IO/Usings.cs index 34fbf4b..a38ed7d 100644 --- a/src/ServerlessWorkflow.Sdk.IO/Usings.cs +++ b/src/ServerlessWorkflow.Sdk.IO/Usings.cs @@ -11,4 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using ServerlessWorkflow.Sdk.Models; \ No newline at end of file +global using Microsoft.Extensions.DependencyInjection; +global using ServerlessWorkflow.Sdk.Models; +global using ServerlessWorkflow.Sdk.Serialization.Json; +global using System.Text.Json; +global using Yaml2JsonNode; \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReader.cs b/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReader.cs index 9867cf7..7631f90 100644 --- a/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReader.cs +++ b/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReader.cs @@ -11,41 +11,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; - namespace ServerlessWorkflow.Sdk.IO; /// /// Represents the default implementation of the interface /// -/// The service used to serialize/deserialize objects to/from JSON -/// The service used to serialize/deserialize objects to/from JSON -public class WorkflowDefinitionReader(IJsonSerializer jsonSerializer, IYamlSerializer yamlSerializer) +public sealed class WorkflowDefinitionReader : IWorkflowDefinitionReader { - /// - /// Gets the service used to serialize/deserialize objects to/from JSON - /// - protected IJsonSerializer JsonSerializer { get; } = jsonSerializer; - - /// - /// Gets the service used to serialize/deserialize objects to/from YAML - /// - protected IYamlSerializer YamlSerializer { get; } = yamlSerializer; - /// - public virtual Task ReadAsync(Stream stream, WorkflowDefinitionReaderOptions? options = null, CancellationToken cancellationToken = default) + public async Task ReadAsync(Stream stream, WorkflowDefinitionReaderOptions? options = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(stream); using var reader = new StreamReader(stream); - var input = reader.ReadToEnd(); - var workflow = (input.TrimStart().StartsWith('{') && input.TrimEnd().EndsWith('}') - ? this.JsonSerializer.Deserialize(input) - : this.YamlSerializer.Deserialize(input)) + var input = (await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).Trim(); + var workflow = (input.StartsWith('{') && input.EndsWith('}') + ? JsonSerializer.Deserialize(input, JsonSerializationContext.Default.WorkflowDefinition) + : YamlSerializer.Deserialize(input, JsonSerializationContext.Default.Options)) ?? throw new NullReferenceException(); - return Task.FromResult(workflow); + return workflow; } /// diff --git a/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReaderOptions.cs b/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReaderOptions.cs index e0379d8..c14acbd 100644 --- a/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReaderOptions.cs +++ b/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionReaderOptions.cs @@ -16,27 +16,27 @@ namespace ServerlessWorkflow.Sdk.IO; /// /// Represents the options used to configure an /// -public class WorkflowDefinitionReaderOptions +public sealed class WorkflowDefinitionReaderOptions { /// /// Gets/sets the base to use to combine to relative s when the property is set to /// - public virtual Uri? BaseUri { get; set; } + public Uri? BaseUri { get; set; } /// /// Gets/sets the base directory to use when resolving relative when the property is set to . Defaults to /// - public virtual string BaseDirectory { get; set; } = AppContext.BaseDirectory; + public string BaseDirectory { get; set; } = AppContext.BaseDirectory; /// /// Gets/sets the to use. Defaults to /// - public virtual string RelativeUriResolutionMode { get; set; } = RelativeUriReferenceResolutionMode.ConvertToRelativeFilePath; + public string RelativeUriResolutionMode { get; set; } = RelativeUriReferenceResolutionMode.ConvertToRelativeFilePath; /// /// Gets/sets a boolean indicating whether or not to load external definitions /// - public virtual bool LoadExternalDefinitions { get; set; } + public bool LoadExternalDefinitions { get; set; } } diff --git a/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionWriter.cs b/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionWriter.cs index 03314fc..6e36989 100644 --- a/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionWriter.cs +++ b/src/ServerlessWorkflow.Sdk.IO/WorkflowDefinitionWriter.cs @@ -11,30 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; - namespace ServerlessWorkflow.Sdk.IO; /// /// Represents the default implementation of the interface /// -/// /// The service used to serialize/deserialize objects to/from JSON -/// The service used to serialize/deserialize objects to/from JSON -public class WorkflowDefinitionWriter(IJsonSerializer jsonSerializer, IYamlSerializer yamlSerializer) +public class WorkflowDefinitionWriter : IWorkflowDefinitionWriter { - /// - /// Gets the service used to serialize/deserialize objects to/from JSON - /// - protected IJsonSerializer JsonSerializer { get; } = jsonSerializer; - - /// - /// Gets the service used to serialize/deserialize objects to/from YAML - /// - protected IYamlSerializer YamlSerializer { get; } = yamlSerializer; - /// public virtual async Task WriteAsync(WorkflowDefinition workflow, Stream stream, string format = WorkflowDefinitionFormat.Yaml, CancellationToken cancellationToken = default) { @@ -42,8 +27,8 @@ public virtual async Task WriteAsync(WorkflowDefinition workflow, Stream stream, ArgumentNullException.ThrowIfNull(stream); var input = format switch { - WorkflowDefinitionFormat.Json => this.JsonSerializer.SerializeToText(workflow), - WorkflowDefinitionFormat.Yaml => this.YamlSerializer.SerializeToText(workflow), + WorkflowDefinitionFormat.Json => JsonSerializer.Serialize(workflow, JsonSerializationContext.Default.WorkflowDefinition), + WorkflowDefinitionFormat.Yaml => YamlSerializer.Serialize(workflow, JsonSerializationContext.Default.Options), _ => throw new NotSupportedException($"The specified workflow definition format '{format}' is not supported"), }; using var streamWriter = new StreamWriter(stream, leaveOpen: true); diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ConcurrentHashSet.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ConcurrentHashSet.cs new file mode 100644 index 0000000..1ac13eb --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ConcurrentHashSet.cs @@ -0,0 +1,66 @@ +// 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 System.Collections; +using System.Collections.Concurrent; + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Represents a concurrent +/// +/// The type of value contained by the +public sealed class ConcurrentHashSet + : ICollection + where T : notnull +{ + + readonly ConcurrentDictionary _dictionary = new(); + + /// + public int Count => _dictionary.Count; + + /// + public bool IsReadOnly => false; + + /// + public bool Contains(T item) => _dictionary.ContainsKey(item); + + /// + public void Add(T item) + { + if (!_dictionary.TryAdd(item, false)) throw new InvalidOperationException("The specified item already exists in the collection"); + } + + /// + /// Attempts to add the specified item to the hashset + /// + /// The item to add + /// A boolean indicating whether or not the item has been added or if it was already present in the set + public bool TryAdd(T item) => _dictionary.TryAdd(item, false); + + /// + public bool Remove(T item) => _dictionary.Remove(item, out _); + + /// + public void Clear() => _dictionary.Clear(); + + /// + public void CopyTo(T[] array, int arrayIndex) => _dictionary.Keys.CopyTo(array, arrayIndex); + + /// + public IEnumerator GetEnumerator() => _dictionary.Keys.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Configuration/WorkflowExecutionsOptions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Configuration/WorkflowExecutionsOptions.cs new file mode 100644 index 0000000..2c7ba9b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Configuration/WorkflowExecutionsOptions.cs @@ -0,0 +1,27 @@ +// 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.Runtime.Configuration; + +/// +/// Represents the options used to configure an +/// +public sealed class WorkflowExecutionsOptions +{ + + /// + /// Gets or sets the options used to configure the 's lifecycle events + /// + public WorkflowProcessLifecycleEventsOptions LifecycleEvents { get; set; } = new(); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Configuration/WorkflowProcessLifecycleEventsOptions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Configuration/WorkflowProcessLifecycleEventsOptions.cs new file mode 100644 index 0000000..5d57a5f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Configuration/WorkflowProcessLifecycleEventsOptions.cs @@ -0,0 +1,32 @@ +// 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.Runtime.Configuration; + +/// +/// Represents the options used to configure the lifecycle events of an +/// +public sealed class WorkflowProcessLifecycleEventsOptions +{ + + /// + /// Gets or sets a boolean value indicating whether or not to publish lifecycle events + /// + public bool Publish { get; set; } = true; + + /// + /// Gets or sets the source to use when publishing lifecycle events. + /// + public Uri Source { get; set; } = new Uri("https://serverlessworkflow.io/runtime"); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/CorrelationContextStatus.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/CorrelationContextStatus.cs new file mode 100644 index 0000000..674a65d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/CorrelationContextStatus.cs @@ -0,0 +1,51 @@ +// 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.Runtime; + +/// +/// Exposes all default statuses of a correlation context +/// +public static class CorrelationContextStatus +{ + + /// + /// Indicates that the context is currently active and in use. + /// + public const string Active = "active"; + /// + /// Indicates that the context is inactive or paused + /// + public const string Inactive = "inactive"; + /// + /// Indicates that the correlation process has been successfully completed. + /// + public const string Completed = "completed"; + /// + /// Indicates that the correlation process has been cancelled. + /// + public const string Cancelled = "cancelled"; + + /// + /// Gets a new used to enumerate the default correlation context statuses + /// + /// A new used to enumerate the default correlation context statuses + public static IEnumerable AsEnumerable() + { + yield return Active; + yield return Inactive; + yield return Completed; + yield return Cancelled; + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/AuthenticationPolicyDefinitionExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/AuthenticationPolicyDefinitionExtensions.cs new file mode 100644 index 0000000..73c4d6b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/AuthenticationPolicyDefinitionExtensions.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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class AuthenticationPolicyDefinitionExtensions +{ + + /// + /// Attempts to get the name of the secret, if any, on which the is based + /// + /// The extended + /// The name of the secret, if any, on which the is based + /// A boolean indicating whether or not the is secret based + public static bool TryGetBaseSecret(this AuthenticationPolicyDefinition authentication, out string? secretName) + { + secretName = authentication.Scheme switch + { + AuthenticationScheme.Basic => authentication.Basic?.Use, + AuthenticationScheme.Bearer => authentication.Bearer?.Use, + AuthenticationScheme.Certificate => authentication.Certificate?.Use, + AuthenticationScheme.Digest => authentication.Digest?.Use, + AuthenticationScheme.OAuth2 => authentication.OAuth2?.Use, + AuthenticationScheme.OpenIDConnect => authentication.Oidc?.Use, + _ => throw new NotSupportedException($"The specified authentication schema '{authentication.Scheme}' is not supported") + }; + return !string.IsNullOrWhiteSpace(secretName); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IHostEnvironmentExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IHostEnvironmentExtensions.cs new file mode 100644 index 0000000..1a1e5da --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IHostEnvironmentExtensions.cs @@ -0,0 +1,40 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +using Microsoft.Extensions.Hosting; + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class IHostEnvironmentExtensions +{ + + /// + /// Determines whether or not the runs in Docker + /// + /// The to check + /// A boolean indicating whether or not the runs in Docker + public static bool RunsInDocker(this IHostEnvironment env) => File.Exists("/.dockerenv"); + + /// + /// Determines whether or not the runs in Kubernetes + /// + /// The to check + /// A boolean indicating whether or not the runs in Kubernetes + public static bool RunsInKubernetes(this IHostEnvironment env) => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST")); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IRuntimeExpressionEvaluatorExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IRuntimeExpressionEvaluatorExtensions.cs new file mode 100644 index 0000000..8fef8fb --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IRuntimeExpressionEvaluatorExtensions.cs @@ -0,0 +1,200 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s. +/// +public static class IRuntimeExpressionEvaluatorExtensions +{ + + /// + /// Evaluates the specified expression with the given input and arguments, if any, and returns the result as a boolean value. + /// + /// The to use to evaluate the expression + /// The expression to evaluate + /// The input to evaluate the expression with + /// The arguments, if any, to evaluate the expression with + /// A + /// A boolean indicating whether the condition specified by the expression is satisfied or not + public static async Task EvaluateConditionAsync(this IRuntimeExpressionEvaluator expressionEvaluator, string expression, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var node = await expressionEvaluator.EvaluateAsync(expression, input, arguments, cancellationToken); + if (node is null) return false; + return node.GetValue(); + } + + /// + /// Evaluates the specified value, which can be either a , an ISO 8601 duration expression or a runtime expression, with the given input and arguments, if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The value to evaluate, which can be either a , an ISO 8601 duration expression or a runtime expression + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, OneOf? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + return value is null ? null : await value.MatchAsync + ( + async (timeout, ct) => await expressionEvaluator.EvaluateAsync(timeout.After, input, arguments, ct).ConfigureAwait(false), + async (expression, ct) => + { + if (!expression.IsRuntimeExpression()) return Duration.FromTimeSpan(System.Xml.XmlConvert.ToTimeSpan(expression)); + var node = await expressionEvaluator.EvaluateAsync(expression, input, arguments, ct).ConfigureAwait(false); + if (node is null) return null!; + return JsonSerializer.Deserialize(node, Serialization.Json.JsonSerializationContext.Default.Duration); + }, + cancellationToken + ); + } + + /// + /// Evaluates the specified value, which can be either a , an ISO 8601 duration expression or a runtime expression, with the given input and arguments, if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The value to evaluate, which can be either a , an ISO 8601 duration expression or a runtime expression + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, OneOf? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + return value is null ? null : await value.MatchAsync + ( + (duration, ct) => Task.FromResult(duration), + async (expression, ct) => + { + if (!expression.IsRuntimeExpression()) return Duration.FromTimeSpan(System.Xml.XmlConvert.ToTimeSpan(expression)); + var node = await expressionEvaluator.EvaluateAsync(expression, input, arguments, ct).ConfigureAwait(false); + if (node is null) return null!; + return JsonSerializer.Deserialize(node, Serialization.Json.JsonSerializationContext.Default.Duration); + }, + cancellationToken + ); + } + + /// + /// Evaluates the specified value, which can be either a , a JSON string or a runtime expression, with the given input and arguments, if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The value to evaluate, which can be either a , a JSON string or a runtime expression + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, OneOf? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + if (value is null) return null; + return await value.MatchAsync + ( + async (jsonObject, ct) => await expressionEvaluator.EvaluateAsync(jsonObject, input, arguments, cancellationToken).ConfigureAwait(false), + async (expression, ct) => await expressionEvaluator.EvaluateAsync(expression, input, arguments, ct).ConfigureAwait(false), + cancellationToken + ); + } + + /// + /// Evaluates the specified , if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The to evaluate, if any + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, JsonNode? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + if (value is null) return null; + return value switch + { + JsonArray jsonArray => await expressionEvaluator.EvaluateAsync(jsonArray, input, arguments, cancellationToken).ConfigureAwait(false), + JsonObject jsonObject => await expressionEvaluator.EvaluateAsync(jsonObject, input, arguments, cancellationToken).ConfigureAwait(false), + JsonValue jsonValue => await expressionEvaluator.EvaluateAsync(jsonValue, input, arguments, cancellationToken).ConfigureAwait(false), + _ => value + }; + } + + /// + /// Evaluates the specified , if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The to evaluate, if any + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, JsonArray? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + if (value is null) return null; + var nodes = new List(value.Count); + foreach (var node in value) nodes.Add(await expressionEvaluator.EvaluateAsync(node, input, arguments, cancellationToken).ConfigureAwait(false) ?? throw new InvalidOperationException("Unexpected null value")); + return new JsonArray(nodes.ToArray()); + } + + /// + /// Evaluates the specified , if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The to evaluate, if any + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, JsonObject? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + if (value is null) return null; + var properties = new List>(value.Count); + foreach (var property in value) properties.Add(new KeyValuePair(property.Key, (await expressionEvaluator.EvaluateAsync(property.Value, input, arguments, cancellationToken).ConfigureAwait(false) ?? throw new InvalidOperationException("Unexpected null value")).DeepClone())); + return new JsonObject(properties); + } + + /// + /// Evaluates the specified , if any. + /// + /// The to use to evaluate the value, if it's a runtime expression + /// The to evaluate, if any + /// The input to evaluate the value with + /// The arguments, if any, to evaluate the value with + /// A + /// The result, if any, of the value evaluation + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, JsonValue? value, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + if (value is null) return null; + if (value.TryGetValue(out var expression) && expression.IsRuntimeExpression()) return await expressionEvaluator.EvaluateAsync(expression, input, arguments, cancellationToken).ConfigureAwait(false); + return value; + } + + /// + /// Evaluates the specified expression with the given input and arguments, if any, and returns the result as a value of the specified type. + /// + /// The type to deserialize the result of the expression evaluation to + /// The to use to evaluate the expression + /// The expression to evaluate + /// The input to evaluate the expression with + /// The arguments, if any, to evaluate the expression with + /// A + /// The result of the expression evaluation deserialized to the specified type, if any + public static async Task EvaluateAsync(this IRuntimeExpressionEvaluator expressionEvaluator, string expression, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var node = await expressionEvaluator.EvaluateAsync(expression, input, arguments, cancellationToken).ConfigureAwait(false); + if (node is null) return default; + var typeInfo = Serialization.Json.JsonSerializationContext.Default.GetTypeInfo(typeof(T)); + if (typeInfo is null) return JsonSerializer.Deserialize(node); + return (T?)JsonSerializer.Deserialize(node, typeInfo); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/ITaskExecutionContextExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/ITaskExecutionContextExtensions.cs new file mode 100644 index 0000000..1bc4211 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/ITaskExecutionContextExtensions.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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class ITaskExecutionContextExtensions +{ + + /// + /// Gets a new used to describe the + /// + /// The to describe + /// A new + public static TaskDescriptor GetDescriptor(this ITaskExecutionContext task) + { + return new() + { + Id = task.Instance.Id, + Name = task.Instance.Name, + Reference = task.Instance.Reference, + Definition = task.Definition, + Input = task.Instance.Input, + Output = task.Instance.Output, + StartedAt = task.Instance.StartedAt?.GetDescriptor() + }; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IWorkflowExecutionContextExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IWorkflowExecutionContextExtensions.cs new file mode 100644 index 0000000..39c6334 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IWorkflowExecutionContextExtensions.cs @@ -0,0 +1,40 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class IWorkflowExecutionContextExtensions +{ + + /// + /// Gets a new used to describe the + /// + /// The to describe + /// A new + public static WorkflowDescriptor GetDescriptor(this IWorkflowExecutionContext workflow) + { + return new() + { + Id = workflow.Instance.Id, + Definition = workflow.Definition, + Input = workflow.Instance.Input, + StartedAt = workflow.Instance.StartedAt?.GetDescriptor() + }; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IWorkflowStateExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IWorkflowStateExtensions.cs new file mode 100644 index 0000000..5bf9440 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Extensions/IWorkflowStateExtensions.cs @@ -0,0 +1,31 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class IWorkflowStateExtensions +{ + + /// + /// Gets the qualified name of the 's definition, in the format {namespace}.{name}:{version} + /// + /// The to get the qualified name of + /// + public static string GetQualifiedName(this IWorkflowInstance state) => $"{state.Definition}-{state.Id}"; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/IOneOf.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IAuthenticationResult.cs similarity index 69% rename from src/ServerlessWorkflow.Sdk/IOneOf.cs rename to src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IAuthenticationResult.cs index 9d2a565..d936418 100644 --- a/src/ServerlessWorkflow.Sdk/IOneOf.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IAuthenticationResult.cs @@ -11,18 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk; +namespace ServerlessWorkflow.Sdk.Runtime; /// -/// Defines the fundamentals of a service that wraps around multiple alternative value types +/// Defines the fundamentals of an authentication result /// -public interface IOneOf +public interface IAuthenticationResult { /// - /// Gets the object's current value + /// Gets the authentication scheme /// - /// The object's current value - object? GetValue(); + string Scheme { get; } -} \ No newline at end of file + /// + /// Gets the authentication value + /// + string Value { get; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ICloudEvent.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ICloudEvent.cs new file mode 100644 index 0000000..956af25 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ICloudEvent.cs @@ -0,0 +1,72 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a Cloud Event +/// +public interface ICloudEvent +{ + + /// + /// Gets/sets the version of the CloudEvents specification which the event uses + /// + string SpecVersion { get; } + + /// + /// Gets/sets the date and time at which the event has been produced + /// + DateTimeOffset? Time { get; } + + /// + /// Gets/sets the cloud event's type + /// + Uri Source { get; } + + /// + /// Gets/sets the cloud event's type + /// + string Type { get; } + + /// + /// Gets/sets a value that describes the subject of the event in the context of the event producer. Used as correlation id by default. + /// + string? Subject { get; } + + /// + /// Gets/sets the cloud event's data content type. Defaults to + /// + string? DataContentType { get; } + + /// + /// Gets/sets an that references the versioned schema of the event's data + /// + Uri? DataSchema { get; } + + /// + /// Gets/sets the event's data, if any. Only used if the event has been formatted using the structured mode + /// + object? Data { get; } + + /// + /// Gets/sets the event's binary data, encoded in base 64. Only used if the event has been formatted using the binary mode + /// + string? DataBase64 { get; } + + /// + /// Gets a that contains the event's extension attributes + /// + IDictionary? ExtensionAttributes { get; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ICorrelationContext.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ICorrelationContext.cs new file mode 100644 index 0000000..30ab3ac --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ICorrelationContext.cs @@ -0,0 +1,47 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a correlation context +/// +public interface ICorrelationContext +{ + + /// + /// Gets the context's unique identifier + /// + string Id { get; } + + /// + /// Gets the context's status + /// + string Status { get; } + + /// + /// Gets a key/value mapping of the context's correlation keys + /// + EquatableDictionary Keys { get; } + + /// + /// Gets a key/value mapping of all correlated events, with the key being the index of the matched correlation filter + /// + EquatableDictionary Events { get; } + + /// + /// Gets the offset that serves as the index of the event being processed by the consumer, if streaming has been enabled for the correlation associated with the context. + /// + uint? Offset { get; init; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IOAuth2Token.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IOAuth2Token.cs new file mode 100644 index 0000000..95ca83c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IOAuth2Token.cs @@ -0,0 +1,64 @@ +// 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 System.Reflection.Metadata; + +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines the fundamentals of an OAUTH2 token +/// +public interface IOAuth2Token +{ + + /// + /// Gets the UTC date and time at which the has been created + /// + DateTime CreatedAt { get; } + + /// + /// Gets the OAUTH2 token type + /// + string? TokenType { get; } + + /// + /// Gets the OAUTH2 token id + /// + string? TokenId { get; } + + /// + /// Gets the OAUTH2 access token + /// + string? AccessToken { get; } + + /// + /// Gets the OAUTH2 refresh token + /// + string? RefreshToken { get; } + + /// + /// Gets the Time To Live, in seconds + /// + int Ttl { get; } + + /// + /// Gets the UTC date and time at which the expires + /// + DateTime? ExpiresAt { get; } + + /// + /// Gets a boolean indicating whether or not the has expired + /// + bool HasExpired { get; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ISchemaValidationResult.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ISchemaValidationResult.cs new file mode 100644 index 0000000..34bbd26 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ISchemaValidationResult.cs @@ -0,0 +1,32 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a schema validation result +/// +public interface ISchemaValidationResult +{ + + /// + /// Gets a boolean indicating whether or not the validation result is valid + /// + bool IsValid { get; } + + /// + /// Gets a mapping of errors, if any, that occurred during the validation process. The keys of the mapping represent the paths to the invalid nodes, while the values are lists of error messages related to each path. + /// + IReadOnlyDictionary>? Errors { get; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IStreamedCloudEvent.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IStreamedCloudEvent.cs new file mode 100644 index 0000000..39d181e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IStreamedCloudEvent.cs @@ -0,0 +1,39 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of an object used to wrap a streamed +/// +public interface IStreamedCloudEvent +{ + + /// + /// Gets the streamed + /// + ICloudEvent Event { get; } + + /// + /// Gets the position of the within its originating stream + /// + uint Offset { get; } + + /// + /// Acknowledges that the has been successfully processed + /// + /// A + /// A new awaitable + Task AckAsync(CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskInstance.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskInstance.cs new file mode 100644 index 0000000..2fd8d49 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskInstance.cs @@ -0,0 +1,174 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a task instance +/// +public interface ITaskInstance +{ + + /// + /// Gets the task's unique identifier + /// + string Id { get; } + + /// + /// Gets the unique identifier of the workflow the task belongs to. + /// + string WorkflowId { get; } + + /// + /// Gets the task's name, if any + /// + string? Name { get; } + + /// + /// Gets a relative uri that references the task's definition + /// + JsonPointer Reference { get; } + + /// + /// Gets a boolean indicating whether or not the task is part of an extension + /// + bool IsExtension { get; } + + /// + /// Gets the id of the task's parent, if any + /// + string? ParentId { get; } + + /// + /// Gets the date and time the task was created at + /// + DateTimeOffset CreatedAt { get; } + + /// + /// Gets the date and time the task has been started at, if applicable + /// + DateTimeOffset? StartedAt { get; } + + /// + /// Gets the date and time the task has ended, if applicable + /// + DateTimeOffset? EndedAt { get; } + + /// + /// Gets the task's status + /// + string? Status { get; } + + /// + /// Gets the reason, if any, why the task is in its actual status + /// + string? StatusReason { get; } + + /// + /// Gets the error, if any, that has occurred during the task's execution + /// + Error? Error { get; } + + /// + /// Gets the task's input data + /// + JsonNode Input { get; } + + /// + /// Gets the task's output data, if any + /// + JsonNode? Output { get; } + + /// + /// Gets the flow directive that must be performed next, if the task ran to completion + /// + string? Next { get; } + + /// + /// Gets a value indicating whether the task is in an operative state + /// + bool IsOperative => Status == TaskStatus.Pending || Status == TaskStatus.Running || Status == TaskStatus.Suspended; + + /// + /// Gets/sets a list that contains the task's runs, if any + /// + IReadOnlyCollection? Runs { get; } + + /// + /// Gets/sets a list that contains the task's retry attempts, if any + /// + IReadOnlyCollection? Retries { get; } + + /// + /// Starts the task + /// + /// A + /// A new awaitable + Task StartAsync(CancellationToken cancellationToken = default); + + /// + /// Suspends the task's execution + /// + /// A + /// A new awaitable + Task SuspendAsync(CancellationToken cancellationToken = default); + + /// + /// Resumes the task's execution + /// + /// A + /// A new awaitable + Task ResumeAsync(CancellationToken cancellationToken = default); + + /// + /// Retries the task's execution + /// + /// The error that caused the retry + /// A + /// A new awaitable + Task RetryAsync(Error cause, CancellationToken cancellationToken = default); + + /// + /// Skips the task + /// + /// The task's output, if any + /// The flow directive that must be performed next + /// A + /// A new awaitable + Task SkipAsync(JsonNode? output, string next, CancellationToken cancellationToken = default); + + /// + /// Sets the task's output + /// + /// The task's output + /// The flow directive that must be performed next + /// A + /// A new awaitable + Task SetOutputAsync(JsonNode? output, string next, CancellationToken cancellationToken = default); + + /// + /// Sets the error that has occurred during the task's execution + /// + /// The error that has occurred during the task's execution + /// A + /// A new awaitable + Task SetErrorAsync(Error error, CancellationToken cancellationToken = default); + + /// + /// Cancels the task's execution + /// + /// A + /// A new awaitable + Task CancelAsync(CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/Interfaces/IValidationResult.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskLifeCycleEvent.cs similarity index 63% rename from src/ServerlessWorkflow.Sdk/Validation/Interfaces/IValidationResult.cs rename to src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskLifeCycleEvent.cs index fe330e5..557e1f9 100644 --- a/src/ServerlessWorkflow.Sdk/Validation/Interfaces/IValidationResult.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskLifeCycleEvent.cs @@ -11,22 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Validation; +namespace ServerlessWorkflow.Sdk.Runtime; /// -/// Defines the fundamentals of a validation attempt +/// Defines the fundamentals of a task life cycle event /// -public interface IValidationResult +public interface ITaskLifeCycleEvent { /// - /// Gets a boolean indicating whether or not the workflow definition is valid + /// Gets the type of task life cycle event /// - bool IsValid { get; } + string Type { get; } /// - /// Gets an containing the errors, if any, that have occurred during validation + /// Gets the task life cycle event's data, if any /// - IReadOnlyCollection? Errors { get; } + object? Data { get; } } diff --git a/src/ServerlessWorkflow.Sdk/DslVersion.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskRetryAttempt.cs similarity index 63% rename from src/ServerlessWorkflow.Sdk/DslVersion.cs rename to src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskRetryAttempt.cs index bf7f259..082c480 100644 --- a/src/ServerlessWorkflow.Sdk/DslVersion.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskRetryAttempt.cs @@ -11,24 +11,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk; +namespace ServerlessWorkflow.Sdk.Runtime; /// -/// Exposes supported Serverless Workflow DSL versions +/// Defines the fundamentals of an object used to describe a task retry attempt /// -public static class DslVersion +public interface ITaskRetryAttempt { + /// - /// Gets the Serverless Workflow DSL version '1.0.0-alpha1' + /// Gets the retry attempt number /// - public const string V1Alpha1 = "1.0.0-alpha1"; + uint Number { get; } + /// - /// Gets the Serverless Workflow DSL version '1.0.0-alpha2' + /// Gets the date and time at which the retry attempt was performed /// - public const string V1Alpha2 = "1.0.0-alpha2"; + DateTimeOffset Time { get; } + /// - /// Gets the Serverless Workflow DSL version '1.0.0' + /// Gets the that is the cause of the try attempt /// - public const string V1 = "1.0.0"; + Error Cause { get; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskRun.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskRun.cs new file mode 100644 index 0000000..88cef1f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskRun.cs @@ -0,0 +1,37 @@ +// 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.Runtime; + +/// +/// Defines the state of a single run of a task +/// +public interface ITaskRun +{ + + /// + /// Gets the start time of the run + /// + DateTimeOffset StartedAt { get; } + + /// + /// Gets the end time of the run, if the task has completed + /// + DateTimeOffset? EndedAt { get; } + + /// + /// Gets the run's outcome or, in other words, the status of the task when the run ended + /// + string? Outcome { get; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskStore.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskStore.cs new file mode 100644 index 0000000..bdfca80 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ITaskStore.cs @@ -0,0 +1,64 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a service used to manage s +/// +public interface ITaskStore +{ + + /// + /// Adds a the specified + /// + /// The to add + /// A + /// The added + Task AddAsync(ITaskInstance instance, CancellationToken cancellationToken = default); + + /// + /// Gets the with the specified unique identifier, belonging to the specified workflow + /// + /// The unique identifier of the workflow the task to get the of belongs to + /// The unique identifier of the task to get the of + /// A + /// The with the specified unique identifier + Task GetAsync(string workflowId, string taskId, CancellationToken cancellationToken = default); + + /// + /// Lists the s belonging to the specified workflow + /// + /// The unique identifier of the workflow to list the s of + /// A + /// A new used to enumerate the s belonging to the specified workflow + IAsyncEnumerable ListAsync(string workflowId, CancellationToken cancellationToken = default); + + /// + /// Lists the s belonging to the specified task of the specified workflow + /// + /// The unique identifier of the workflow to list the s of + /// The unique identifier of the task to list the s of + /// A + /// A new used to enumerate the s belonging to the specified task of the specified workflow + IAsyncEnumerable ListAsync(string workflowId, string taskId, CancellationToken cancellationToken = default); + + /// + /// Updates the specified + /// + /// The to update + /// A + /// The updated + Task UpdateAsync(ITaskInstance instance, CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowInstance.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowInstance.cs new file mode 100644 index 0000000..81b941f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowInstance.cs @@ -0,0 +1,134 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a workflow instance +/// +public interface IWorkflowInstance +{ + + /// + /// Gets the workflow's unique identifier + /// + string Id { get; } + + /// + /// Gets a reference to the workflow's definition + /// + WorkflowDefinitionReference Definition { get; } + + /// + /// Gets the workflow's status + /// + string Status { get; } + + /// + /// Gets the date and time at which the workflow has been created + /// + DateTimeOffset CreatedAt { get; } + + /// + /// Gets the date and time the workflow has been started at, if applicable + /// + DateTimeOffset? StartedAt { get; } + + /// + /// Gets the date and time the workflow has ended, if applicable + /// + DateTimeOffset? EndedAt { get; } + + /// + /// Gets the workflow's input data + /// + JsonObject? Input { get; } + + /// + /// Gets the workflow's context data + /// + JsonObject ContextData { get; } + + /// + /// Gets the workflow's output data, if any + /// + JsonNode? Output { get; } + + /// + /// Gets the error, if any, that has occurred during the workflow's execution + /// + Error? Error { get; } + + /// + /// Gets a value indicating whether the workflow is in an operative state + /// + bool IsOperative => Status == TaskStatus.Pending || Status == TaskStatus.Running || Status == TaskStatus.Suspended; + + /// + /// Gets a collection containing the workflow's runs + /// + IReadOnlyCollection? Runs { get; } + + /// + /// Starts the workflow + /// + /// A + /// A new awaitable + Task StartAsync(CancellationToken cancellationToken = default); + + /// + /// Suspends the workflow's execution + /// + /// A + /// A new awaitable + Task SuspendAsync(CancellationToken cancellationToken = default); + + /// + /// Resumes the workflow's execution + /// + /// A + /// A new awaitable + Task ResumeAsync(CancellationToken cancellationToken = default); + + /// + /// Sets the workflow's output + /// + /// The workflow's output + /// A + /// A new awaitable + Task SetOutputAsync(JsonNode? output, CancellationToken cancellationToken = default); + + /// + /// Sets the error that has occurred during the workflow's execution + /// + /// The error that has occurred during the workflow's execution + /// A + /// A new awaitable + Task SetErrorAsync(Error error, CancellationToken cancellationToken = default); + + /// + /// Sets the workflow's context data + /// + /// The workflow's context data + /// A + /// A new awaitable + Task SetContextDataAsync(JsonObject contextData, CancellationToken cancellationToken = default); + + /// + /// Cancels the workflow's execution + /// + /// A + /// A new awaitable + Task CancelAsync(CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk/Validation/ValidationError.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowLifeCycleEvent.cs similarity index 66% rename from src/ServerlessWorkflow.Sdk/Validation/ValidationError.cs rename to src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowLifeCycleEvent.cs index 953986d..b911b05 100644 --- a/src/ServerlessWorkflow.Sdk/Validation/ValidationError.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowLifeCycleEvent.cs @@ -11,23 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Validation; +namespace ServerlessWorkflow.Sdk.Runtime; /// -/// Represents a validation error +/// Defines the fundamentals of a workflow lifecycle event /// -[DataContract] -public record ValidationError +public interface IWorkflowLifeCycleEvent { /// - /// Gets the reference, if any, of the component to which the error applies + /// Gets the type of workflow lifecycle event /// - public virtual string? Reference { get; set; } + string Type { get; } /// - /// Gets detailed information, if any, about the validation error + /// Gets the workflow lifecycle event's data, if any /// - public virtual string? Details { get; set; } + object? Data { get; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowProcessFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowProcessFactory.cs new file mode 100644 index 0000000..9ee4995 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowProcessFactory.cs @@ -0,0 +1,32 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a service used to create es +/// +public interface IWorkflowProcessFactory +{ + + /// + /// Creates a new + /// + /// The to create the process for + /// The to create the process for + /// The options used to configure the workflow's execution + /// A + /// A new + Task CreateAsync(WorkflowDefinition definition, IWorkflowInstance state, WorkflowExecutionsOptions executionOptions, CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowRun.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowRun.cs new file mode 100644 index 0000000..5f95826 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowRun.cs @@ -0,0 +1,32 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a workflow run +/// +public interface IWorkflowRun +{ + + /// + /// Gets/sets the start time of the run + /// + DateTimeOffset StartedAt { get; } + + /// + /// Gets/sets the end time of the run, if the workflow has completed + /// + DateTimeOffset? EndedAt { get; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowStore.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowStore.cs new file mode 100644 index 0000000..094c488 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/IWorkflowStore.cs @@ -0,0 +1,47 @@ +// 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.Runtime; + +/// +/// Defines the fundamentals of a service used to manage s +/// +public interface IWorkflowStore +{ + + /// + /// Adds a the specified + /// + /// The definition of the workflow to add + /// The input of the workflow to add + /// A + /// The added + Task AddAsync(WorkflowDefinition definition, JsonObject? input = null, CancellationToken cancellationToken = default); + + /// + /// Gets the with the specified unique identifier, belonging to the specified workflow + /// + /// The unique identifier of the workflow to get the state of + /// A + /// The with the specified unique identifier + Task GetAsync(string id, CancellationToken cancellationToken = default); + + /// + /// Updates the specified + /// + /// The to update + /// A + /// The updated + Task UpdateAsync(IWorkflowInstance instance, CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ServerlessWorkflow.Sdk.Runtime.Abstractions.csproj b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ServerlessWorkflow.Sdk.Runtime.Abstractions.csproj new file mode 100644 index 0000000..0f5b288 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/ServerlessWorkflow.Sdk.Runtime.Abstractions.csproj @@ -0,0 +1,48 @@ + + + + net10.0 + enable + enable + ServerlessWorkflow.Sdk.Runtime + true + 1.0.2 + $(VersionPrefix) + $(VersionPrefix) + en + true + True + true + Serverless Workflow SDK - Runtime Abstractions + Contains the abstractions for the runtime services used to execute ServerlessWorkflow workflow definitions + serverless-workflow;serverless;workflow;dsl;sdk;runtime;abstractions + true + Apache-2.0 + README.md + Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. + https://github.com/serverlessworkflow/sdk-net + https://github.com/serverlessworkflow/sdk-net + git + embedded + + + + + + + + + + + + + + + + + \ + True + + + + diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IAuthenticationHandler.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IAuthenticationHandler.cs new file mode 100644 index 0000000..7994d9f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IAuthenticationHandler.cs @@ -0,0 +1,31 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to handle authentication policies +/// +public interface IAuthenticationHandler +{ + + /// + /// Handles the specified authentication policy and returns an that can be used to authenticate requests to external resources + /// + /// The to handle + /// The , if any, that defines the authentication policy to handle + /// A + /// A new + Task HandleAsync(AuthenticationPolicyDefinition policy, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ICloudEventBus.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ICloudEventBus.cs new file mode 100644 index 0000000..77b23af --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ICloudEventBus.cs @@ -0,0 +1,37 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to publish and subscribe to s +/// +public interface ICloudEventBus +{ + + /// + /// Publishes the specified + /// + /// The to publish + /// A + /// A new awaitable + Task PublishAsync(ICloudEvent e, CancellationToken cancellationToken = default); + + /// + /// Subscribes to streamed s + /// + /// A + /// A new used to observe streamed s + Task> SubscribeAsync(CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IContainer.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IContainer.cs new file mode 100644 index 0000000..4fd2114 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IContainer.cs @@ -0,0 +1,59 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a container +/// +public interface IContainer + : IDisposable, IAsyncDisposable +{ + + /// + /// Gets the container's standard output stream + /// + StreamReader? StandardOutput { get; } + + /// + /// Gets the container's standard error stream + /// + StreamReader? StandardError { get; } + + /// + /// Gets the container's exit code + /// + long? ExitCode { get; } + + /// + /// Starts the container + /// + /// A + /// A new awaitable + Task StartAsync(CancellationToken cancellationToken = default); + + /// + /// Waits for the container to exit + /// + /// A + /// A new awaitable + Task WaitForExitAsync(CancellationToken cancellationToken = default); + + /// + /// Stops the container + /// + /// A + /// A new awaitable + Task StopAsync(CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IContainerRuntime.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IContainerRuntime.cs new file mode 100644 index 0000000..dbb5756 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IContainerRuntime.cs @@ -0,0 +1,32 @@ +// 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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to create, run and manage the lifecycle of s. +/// +public interface IContainerRuntime +{ + + /// + /// Creates a new container based on the specified + /// + /// The that defines the container to create + /// A + /// A new + Task CreateAsync(ContainerProcessDefinition definition, CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IExternalResourceReader.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IExternalResourceReader.cs new file mode 100644 index 0000000..3a8e673 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IExternalResourceReader.cs @@ -0,0 +1,31 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to read external resources +/// +public interface IExternalResourceReader +{ + + /// + /// Reads the specified external resource + /// + /// The reference to the external resource to get + /// The , if any, in the context of which to read the specified resource + /// A + /// A used to read the external resource's contents + Task ReadAsync(ExternalResourceDefinition resource, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/Interfaces/IWorkflowDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IOAuthTokenManager.cs similarity index 56% rename from src/ServerlessWorkflow.Sdk/Validation/Interfaces/IWorkflowDefinitionValidator.cs rename to src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IOAuthTokenManager.cs index fc2d36b..fe658f3 100644 --- a/src/ServerlessWorkflow.Sdk/Validation/Interfaces/IWorkflowDefinitionValidator.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IOAuthTokenManager.cs @@ -11,22 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models; - -namespace ServerlessWorkflow.Sdk.Validation; +namespace ServerlessWorkflow.Sdk.Runtime.Services; /// -/// Defines the fundamentals of a service used to validate s +/// Defines the fundamentals of a service used to manage s /// -public interface IWorkflowDefinitionValidator +public interface IOAuth2TokenManager { /// - /// Validates the specified + /// Gets an /// - /// The to validate + /// The configuration that defines how to generate the to get /// A - /// An object that describe the result of the validation attempt - Task ValidateAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default); + /// An + Task GetTokenAsync(OAuth2AuthenticationSchemeDefinitionBase configuration, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IRuntimeExpressionEvaluator.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IRuntimeExpressionEvaluator.cs new file mode 100644 index 0000000..5020a07 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IRuntimeExpressionEvaluator.cs @@ -0,0 +1,39 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to evaluate runtime expressions +/// +public interface IRuntimeExpressionEvaluator +{ + + /// + /// Determines whether the specified language is supported by the expression evaluator + /// + /// The expression language to check + /// A boolean indicating whether the specified language is supported by the expression evaluator + bool Supports(string language); + + /// + /// Evaluates the specified expression with the given input and arguments, if any + /// + /// The expression to evaluate + /// The input to evaluate the expression with + /// The arguments, if an, to evaluate the expression with + /// A + /// The result, if any, of the expression evaluation + Task EvaluateAsync(string expression, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IRuntimeExpressionEvaluatorProvider.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IRuntimeExpressionEvaluatorProvider.cs new file mode 100644 index 0000000..a878f98 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IRuntimeExpressionEvaluatorProvider.cs @@ -0,0 +1,29 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to provide runtime expression evaluators +/// +public interface IRuntimeExpressionEvaluatorProvider +{ + + /// + /// Gets an that supports the specified language, if any + /// + /// The expression language to get an for + /// The first registered , if any, that supports the specified expression language + IRuntimeExpressionEvaluator? GetEvaluator(string language); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISchemaHandler.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISchemaHandler.cs new file mode 100644 index 0000000..a18b1c5 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISchemaHandler.cs @@ -0,0 +1,38 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to handle s +/// +public interface ISchemaHandler +{ + + /// + /// Determines whether or not the supports the specified schema format + /// + /// The format to check + /// A boolean indicating whether or not the supports the specified schema format + bool Supports(string format); + + /// + /// Validates an object against the specified schema + /// + /// The object to validate + /// The schema to validate the graph against + /// A + /// An object that describes the validation result + Task ValidateAsync(JsonNode graph, SchemaDefinition schema, CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISchemaHandlerProvider.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISchemaHandlerProvider.cs new file mode 100644 index 0000000..27c3e68 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISchemaHandlerProvider.cs @@ -0,0 +1,29 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to provide s +/// +public interface ISchemaHandlerProvider +{ + + /// + /// Gets the first registered that supports the specified schema format + /// + /// The schema format to get an for + /// The first registered , if any, that supports the specified schema format + ISchemaHandler? GetHandler(string format); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IScriptExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IScriptExecutor.cs new file mode 100644 index 0000000..1184ae7 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IScriptExecutor.cs @@ -0,0 +1,39 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to execute scripts +/// +public interface IScriptExecutor +{ + + /// + /// Determines whether this supports executing scripts written in the specified language + /// + /// The language to check for support + /// A boolean indicating whether this supports executing scripts written in the specified language + bool Supports(string language); + + /// + /// Executes a script with the specified code, language, arguments and environment variables, returning the associated + /// + /// The code of the script to execute + /// An optional collection of arguments to pass to the script being executed + /// A optional dictionary of environment variables to set for the script being executed + /// A + /// A new used to execute the script + Task ExecuteAsync(string script, IEnumerable? arguments = null, IDictionary? environment = null, CancellationToken cancellationToken = default); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IScriptExecutorProvider.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IScriptExecutorProvider.cs new file mode 100644 index 0000000..76f6385 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IScriptExecutorProvider.cs @@ -0,0 +1,29 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to provide s +/// +public interface IScriptExecutorProvider +{ + + /// + /// Gets the for the specified language. + /// + /// The scripting language. + /// The for the specified language. + IScriptExecutor? GetExecutor(string language); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISecretsManager.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISecretsManager.cs new file mode 100644 index 0000000..a95703e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ISecretsManager.cs @@ -0,0 +1,29 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to manage secrets +/// +public interface ISecretsManager +{ + + /// + /// Gets all available secrets + /// + /// A + /// A new that contains the key/value mappings of all available secrets + Task> GetAsync(CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutionContext.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutionContext.cs new file mode 100644 index 0000000..3efef61 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutionContext.cs @@ -0,0 +1,150 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of the context of a task's execution +/// +public interface ITaskExecutionContext +{ + + /// + /// Gets the workflow the task to execute belongs to + /// + IWorkflowExecutionContext Workflow { get; } + + /// + /// Gets the of the task to execute + /// + TaskDefinition Definition { get; } + + /// + /// Gets the task to execute + /// + ITaskInstance Instance { get; } + + /// + /// Gets a name/value mapping of the task's arguments, if any + /// + JsonObject? Arguments { get; } + + /// + /// Executes the task + /// + /// A + /// A new awaitable + Task StartAsync(CancellationToken cancellationToken = default); + + /// + /// Streams events + /// + /// A + /// A new used to stream s + Task> StreamAsync(CancellationToken cancellationToken = default); + + /// + /// Begins correlating events + /// + /// A + /// The resulting + Task CorrelateAsync(CancellationToken cancellationToken = default); + + /// + /// Publishes the specified + /// + /// The to publish + /// A + /// A new awaitable + Task PublishAsync(ICloudEvent e, CancellationToken cancellationToken = default); + + /// + /// Suspends the task + /// + /// A + /// A new awaitable + Task SuspendAsync(CancellationToken cancellationToken = default); + + /// + /// Retries the task + /// + /// The to retry the task for + /// A + /// A new awaitable + Task RetryAsync(Error cause, CancellationToken cancellationToken = default); + + /// + /// Sets an that has occurred during the task's execution + /// + /// The that has occurred + /// A + /// A new awaitable + Task SetErrorAsync(Error error, CancellationToken cancellationToken = default); + + /// + /// Sets the task's result, if any + /// + /// The task's result, if any + /// The to perform next + /// A + /// A new awaitable + Task SetResultAsync(JsonNode? result, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default); + + /// + /// Sets the task's context data + /// + /// The task's context data + /// A + /// A new awaitable + Task SetContextDataAsync(JsonObject contextData, CancellationToken cancellationToken = default); + + /// + /// Skips the task + /// + /// The task's result, if any + /// The to perform next + /// A + /// A new awaitable + Task SkipAsync(JsonNode? result, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default); + + /// + /// Cancels the task + /// + /// A + /// A new awaitable + Task CancelAsync(CancellationToken cancellationToken = default); + + /// + /// Gets the subtasks the task is made out of + /// + /// A + /// A new used to enumerate subtasks + IAsyncEnumerable GetSubTasksAsync(CancellationToken cancellationToken = default); + +} + +/// +/// Defines the fundamentals of the context of a task's execution +/// +/// The type of task to run +public interface ITaskExecutionContext + : ITaskExecutionContext + where TDefinition : TaskDefinition +{ + + /// + /// Gets the of the task to execute + /// + new TDefinition Definition { get; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutionContextFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutionContextFactory.cs new file mode 100644 index 0000000..9366ecb --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutionContextFactory.cs @@ -0,0 +1,32 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to create s +/// +public interface ITaskExecutionContextFactory +{ + + /// + /// Creates a new implementation for the + /// + /// The the belongs to + /// The of the to run + /// The to run + /// A name/value mapping of the task's arguments, if any + /// A new + ITaskExecutionContext Create(IWorkflowExecutionContext workflow, TaskDefinition definition, ITaskInstance instance, JsonObject? arguments = null); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutor.cs new file mode 100644 index 0000000..4192be0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutor.cs @@ -0,0 +1,96 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to execute a task +/// +public interface ITaskExecutor + : IObservable, IDisposable, IAsyncDisposable +{ + + /// + /// Gets the to run + /// + ITaskExecutionContext Task { get; } + + /// + /// Initializes the task + /// + /// A + /// A new awaitable + Task InitializeAsync(CancellationToken cancellationToken = default); + + /// + /// Runs the task + /// + /// A + /// A new awaitable + Task ExecuteAsync(CancellationToken cancellationToken = default); + + /// + /// Suspends the task + /// + /// A + /// A new awaitable + Task SuspendAsync(CancellationToken cancellationToken = default); + + /// + /// Retries to run the task + /// + /// The that caused the retry attempt + /// A + /// A new awaitable + Task RetryAsync(Error cause, CancellationToken cancellationToken = default); + + /// + /// Faults the handled task + /// + /// + /// A + /// A new awaitable + Task SetErrorAsync(Error error, CancellationToken cancellationToken = default); + + /// + /// Sets the task's result and transitions to ''. + /// + /// The task's result, if any + /// The to perform next + /// A + /// A new awaitable + Task SetResultAsync(JsonNode? result = null, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default); + + /// + /// Cancels the task + /// + /// A + /// A new awaitable + Task CancelAsync(CancellationToken cancellationToken = default); + +} + +/// +/// Defines the fundamentals of a service used to execute a task +/// +public interface ITaskExecutor + : ITaskExecutor + where TDefinition : TaskDefinition +{ + + /// + /// Gets the to run + /// + new ITaskExecutionContext Task { get; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutorFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutorFactory.cs new file mode 100644 index 0000000..5724c93 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/ITaskExecutorFactory.cs @@ -0,0 +1,38 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to create s +/// +public interface ITaskExecutorFactory +{ + + /// + /// Creates a new for the specified task + /// + /// The to create a new for + /// A new for the specified task + ITaskExecutor Create(ITaskExecutionContext context); + + /// + /// Creates a new for the specified task + /// + /// The of the task to execute + /// The to create a new for + /// A new for the specified task + ITaskExecutor Create(ITaskExecutionContext context) + where TDefinition : TaskDefinition; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowDefinitionStore.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowDefinitionStore.cs new file mode 100644 index 0000000..6ef6633 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowDefinitionStore.cs @@ -0,0 +1,64 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to manage s +/// +public interface IWorkflowDefinitionStore +{ + + /// + /// Adds the specified + /// + /// The to add + /// A + /// A new awaitable + Task AddAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default); + + /// + /// Gets the with the specified namespace, name and version + /// + /// The namespace the to get belongs to + /// The name of the to get + /// The version, if any, of the to get. If not set, defaults to latest version + /// A + /// The with the specified namespace, name and version, or null if not found + Task GetAsync(string @namespace, string name, string? version = null, CancellationToken cancellationToken = default); + + /// + /// Lists all s + /// + /// A + /// A new used to enumerate s + IAsyncEnumerable ListAsync(CancellationToken cancellationToken = default); + + /// + /// Lists all s belonging to the specified namespace + /// + /// The namespace to list s from + /// A + /// A new used to enumerate s + IAsyncEnumerable ListAsync(string @namespace, CancellationToken cancellationToken = default); + + /// + /// Lists all versions of the specified + /// + /// The namespace the to list versions of belongs to + /// The name of the to list versions of + /// A + /// A new used to enumerate s + IAsyncEnumerable ListAsync(string @namespace, string name, CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowExecutionContext.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowExecutionContext.cs new file mode 100644 index 0000000..9880af1 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowExecutionContext.cs @@ -0,0 +1,140 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of the context of a workflow's execution +/// +public interface IWorkflowExecutionContext +{ + + /// + /// Gets the of the current workflow + /// + WorkflowDefinition Definition { get; } + + /// + /// Gets the current + /// + IWorkflowInstance Instance { get; } + + /// + /// Gets the current + /// + IRuntimeExpressionEvaluator Expressions { get; } + + /// + /// Gets the service used to run the workflow + /// + IWorkflowRuntime Runtime { get; } + + /// + /// Gets the options used to configure the workflow's execution + /// + WorkflowExecutionsOptions Options { get; } + + /// + /// Gets a new , if any, containing the runtime expression evaluation arguments for the to run + /// + /// A new , if any, containing the runtime expression evaluation arguments for the to run + JsonObject GetExpressionEvaluationArguments(); + + /// + /// Continues execution with the provided + /// + /// The to continue with + /// A + /// A new awaitable + Task ContinueWithAsync(TaskDefinition task, CancellationToken cancellationToken = default); + + /// + /// Creates a new + /// + /// The of the to create + /// The path used to reference the of the to create + /// The input data, if any + /// The parent of the to create, if any + /// Indicates whether or not the task is part of an extension + /// A + /// The updated + Task CreateTaskAsync(TaskDefinition definition, JsonPointer path, JsonNode input, ITaskExecutionContext? parent = null, bool isExtension = false, CancellationToken cancellationToken = default); + + /// + /// Gets the workflow's tasks + /// + /// A + /// A new to asynchronously enumerate the tasks the workflow owns + IAsyncEnumerable GetTasksAsync(CancellationToken cancellationToken = default); + + /// + /// Starts the workflow + /// + /// A + /// A new awaitable + Task StartAsync(CancellationToken cancellationToken = default); + + /// + /// Suspends the workflow + /// + /// A + /// A new awaitable + Task SuspendAsync(CancellationToken cancellationToken = default); + + /// + /// Resumes the workflow + /// + /// A + /// A new awaitable + Task ResumeAsync(CancellationToken cancellationToken = default); + + /// + /// Publishes the specified + /// + /// The to publish + /// A + /// A new awaitable + Task PublishAsync(ICloudEvent e, CancellationToken cancellationToken = default); + + /// + /// Sets the error that has faulted the workflow's execution + /// + /// The that has faulted the workflow + /// A + /// A new awaitable + Task SetErrorAsync(Error error, CancellationToken cancellationToken = default); + + /// + /// Sets the workflow's result + /// + /// The workflow's result, if any + /// A + /// A new awaitable + Task SetResultAsync(JsonNode? result, CancellationToken cancellationToken = default); + + /// + /// Sets the workflow's context data + /// + /// The workflow's context data + /// A + /// A new awaitable + Task SetContextDataAsync(JsonObject contextData, CancellationToken cancellationToken = default); + + /// + /// Cancels the workflow's execution + /// + /// A + /// A new awaitable + Task CancelAsync(CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowExecutionContextFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowExecutionContextFactory.cs new file mode 100644 index 0000000..234573a --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowExecutionContextFactory.cs @@ -0,0 +1,31 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to create s +/// +public interface IWorkflowExecutionContextFactory +{ + + /// + /// Creates a new + /// + /// The to create the context for + /// The to create the context for + /// The options used to configure the workflow's execution + /// A new + IWorkflowExecutionContext Create(WorkflowDefinition definition, IWorkflowInstance instance, WorkflowExecutionsOptions executionsOptions); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowProcess.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowProcess.cs new file mode 100644 index 0000000..f0dec00 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowProcess.cs @@ -0,0 +1,52 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a workflow process, which holds the methods to manage a workflow's execution +/// +public interface IWorkflowProcess + : IObservable, IAsyncDisposable +{ + + /// + /// Waits for the workflow to reach a non-running state, such as completion, suspension, cancellation, or failure. + /// + /// A + /// A new awaitable + Task WaitAsync(CancellationToken cancellationToken = default); + + /// + /// Resumes the workflow + /// + /// A + /// A new awaitable + Task ResumeAsync(CancellationToken cancellationToken = default); + + /// + /// Suspends the workflow + /// + /// A + /// A new awaitable + Task SuspendAsync(CancellationToken cancellationToken = default); + + /// + /// Cancels the workflow's execution + /// + /// A + /// A new awaitable + Task CancelAsync(CancellationToken cancellationToken = default); + +} + diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowRuntime.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowRuntime.cs new file mode 100644 index 0000000..96f6cda --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowRuntime.cs @@ -0,0 +1,50 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to execute workflows +/// +public interface IWorkflowRuntime + : IAsyncDisposable +{ + + /// + /// Gets an object used to describe the current runtime environment + /// + RuntimeDescriptor Descriptor { get; } + + /// + /// Runs a workflow with the specified name and version, using the provided input + /// + /// The namespace the workflow to run belongs to + /// The name of the workflow to run + /// The version, if any, of the workflow to run. If not specified, the latest version will be used + /// The input to run the workflow with + /// The options used to configure the workflow's execution + /// A + /// A new + Task RunAsync(string @namespace, string name, string? version = null, JsonObject? input = null, WorkflowExecutionsOptions? executionOptions = null, CancellationToken cancellationToken = default); + + /// + /// Runs the specified workflow + /// + /// The definition of the workflow to run + /// The input to run the workflow with + /// The options used to configure the workflow's execution + /// A + /// A new + Task RunAsync(WorkflowDefinition definition, JsonObject? input = null, WorkflowExecutionsOptions? executionOptions = null, CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowRuntimeBuilder.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowRuntimeBuilder.cs new file mode 100644 index 0000000..22860ca --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Services/IWorkflowRuntimeBuilder.cs @@ -0,0 +1,350 @@ +// 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.Runtime.Services; + +/// +/// Defines the fundamentals of a service used to build and configure s +/// +public interface IWorkflowRuntimeBuilder +{ + + /// + /// Gets the underlying used to register runtime services + /// + IServiceCollection Services { get; } + + /// + /// Gets the application's + /// + IConfiguration Configuration { get; } + + /// + /// Gets the used to register all runtime services + /// + ServiceLifetime ServiceLifetime { get; } + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseAuthenticationHandler() + where THandler : class, IAuthenticationHandler; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseAuthenticationHandler(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseCloudEventBus() + where TBus : class, ICloudEventBus; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseCloudEventBus(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseOAuth2TokenManager() + where TManager : class, IOAuth2TokenManager; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseOAuth2TokenManager(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseContainerRuntime() + where TRuntime : class, IContainerRuntime; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseContainerRuntime(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseExternalResourceReader() + where TReader : class, IExternalResourceReader; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseExternalResourceReader(Func factory); + + /// + /// Adds an implementation + /// + /// The type of to add + /// The configured + IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluator() + where TEvaluator : class, IRuntimeExpressionEvaluator; + + /// + /// Adds an implementation + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluator(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluatorProvider() + where TProvider : class, IRuntimeExpressionEvaluatorProvider; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluatorProvider(Func factory); + + /// + /// Adds an implementation + /// + /// The type of to add + /// The configured + IWorkflowRuntimeBuilder UseSchemaHandler() + where THandler : class, ISchemaHandler; + + /// + /// Adds an implementation + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseSchemaHandler(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseSchemaHandlerProvider() + where TProvider : class, ISchemaHandlerProvider; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseSchemaHandlerProvider(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseSecretsManager() + where TManager : class, ISecretsManager; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseSecretsManager(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseScriptExecutor() + where TExecutor : class, IScriptExecutor; + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseScriptExecutor(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseScriptExecutorProvider() + where TProvider : class, IScriptExecutorProvider; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseScriptExecutorProvider(Func factory); + + /// + /// Registers a for the specified type + /// + /// The type of handled by the executor + /// The type of to register + /// The configured + IWorkflowRuntimeBuilder UseTaskExecutor() + where TDefinition : TaskDefinition + where TExecutor : class, ITaskExecutor; + + /// + /// Registers a for the specified call type (e.g. "http", "openapi", "asyncapi", "grpc") + /// + /// The call type discriminator to register the executor for + /// The type of to register + /// The configured + IWorkflowRuntimeBuilder UseCallTaskExecutor(string callType) + where TExecutor : class, ITaskExecutor; + + /// + /// Registers a for the specified process type (e.g. "container", "shell", "script", "workflow") + /// + /// The process type discriminator to register the executor for + /// The type of to register + /// The configured + IWorkflowRuntimeBuilder UseRunTaskExecutor(string processType) + where TExecutor : class, ITaskExecutor; + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseWorkflowProcessFactory() + where TFactory : class, IWorkflowProcessFactory; + + /// + /// Configures the implementation to use + /// + /// An to use + /// The configured + IWorkflowRuntimeBuilder UseWorkflowProcessFactory(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseTaskExecutorFactory() + where TFactory : class, ITaskExecutorFactory; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseTaskExecutorFactory(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseWorkflowExecutionContextFactory() + where TFactory : class, IWorkflowExecutionContextFactory; + + /// + /// Configures the implementation to use + /// + /// An to use + /// The configured + IWorkflowRuntimeBuilder UseWorkflowExecutionContextFactory(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseTaskExecutionContextFactory() + where TFactory : class, ITaskExecutionContextFactory; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseTaskExecutionContextFactory(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseWorkflowDefinitionStore() + where TStore : class, IWorkflowDefinitionStore; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseWorkflowDefinitionStore(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseWorkflowStateStore() + where TStore : class, IWorkflowStore; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseWorkflowStateStore(Func factory); + + /// + /// Configures the implementation to use + /// + /// The type of to use + /// The configured + IWorkflowRuntimeBuilder UseTaskStateStore() + where TStore : class, ITaskStore; + + /// + /// Configures the implementation to use + /// + /// A factory function used to create the + /// The configured + IWorkflowRuntimeBuilder UseTaskStateStore(Func factory); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/TaskLifeCycleEventType.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/TaskLifeCycleEventType.cs new file mode 100644 index 0000000..0212afc --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/TaskLifeCycleEventType.cs @@ -0,0 +1,50 @@ +// 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.Runtime; + +/// +/// Exposes all default types of events that can be emitted during a task lifecycle +/// +public static class TaskLifeCycleEventType +{ + /// + /// Indicates that the task has been initialized + /// + public const string Initialized = "initialized"; + /// + /// Indicates that the task is running + /// + public const string Running = "running"; + /// + /// Indicates that the task has been suspended + /// + public const string Suspended = "suspended"; + /// + /// Indicates that the task has been cancelled + /// + public const string Cancelled = "cancelled"; + /// + /// Indicates that the task has faulted + /// + public const string Faulted = "faulted"; + /// + /// Indicates that the task ran to completion + /// + public const string Completed = "completed"; + /// + /// Indicates that the task has been skipped + /// + public const string Skipped = "skipped"; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/TaskStatus.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/TaskStatus.cs new file mode 100644 index 0000000..8df2f99 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/TaskStatus.cs @@ -0,0 +1,65 @@ +// 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.Runtime; + +/// +/// Exposes default task instance statuses +/// +public static class TaskStatus +{ + + /// + /// Indicates that the task has been created and is pending execution + /// + public const string Pending = "pending"; + /// + /// Indicates that the task is running + /// + public const string Running = "running"; + /// + /// Indicates that the task encountered an error or exception during execution + /// + public const string Faulted = "faulted"; + /// + /// Indicates that the task has been explicitly omitted or bypassed during execution, probably because that task failed the condition defined by its `if` property + /// + public const string Skipped = "skipped"; + /// + /// Indicates that the task was suspended + /// + public const string Suspended = "suspended"; + /// + /// Indicates that the task was terminated or aborted before completion + /// + public const string Cancelled = "cancelled"; + /// + /// Indicates that the task ran to completion + /// + public const string Completed = "completed"; + + /// + /// Gets an containing default task statuses + /// + /// A new containing default task statuses + public static IEnumerable AsEnumerable() + { + yield return Pending; + yield return Running; + yield return Faulted; + yield return Skipped; + yield return Cancelled; + yield return Completed; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Usings.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Usings.cs new file mode 100644 index 0000000..27e31cf --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/Usings.cs @@ -0,0 +1,25 @@ +// 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. + +global using Json.Pointer; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using ServerlessWorkflow.Sdk.Models; +global using ServerlessWorkflow.Sdk.Models.Authentication; +global using ServerlessWorkflow.Sdk.Models.Tasks; +global using ServerlessWorkflow.Sdk.Runtime.Configuration; +global using ServerlessWorkflow.Sdk.Runtime.Services; +global using System.Diagnostics; +global using System.Net.Mime; +global using System.Text.Json; +global using System.Text.Json.Nodes; diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/WorkflowLifeCycleEventType.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/WorkflowLifeCycleEventType.cs new file mode 100644 index 0000000..c5ab030 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/WorkflowLifeCycleEventType.cs @@ -0,0 +1,46 @@ +// 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.Runtime; + +/// +/// Exposes all default types of events that can be emitted during a workflow lifecycle +/// +public static class WorkflowLifeCycleEventType +{ + /// + /// Indicates that the workflow has been initialized + /// + public const string Initialized = "initialized"; + /// + /// Indicates that the workflow is running + /// + public const string Running = "running"; + /// + /// Indicates that the workflow has been suspended + /// + public const string Suspended = "suspended"; + /// + /// Indicates that the workflow has been cancelled + /// + public const string Cancelled = "cancelled"; + /// + /// Indicates that the workflow has faulted + /// + public const string Faulted = "faulted"; + /// + /// Indicates that the workflow ran to completion + /// + public const string Completed = "completed"; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/WorkflowStatus.cs b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/WorkflowStatus.cs new file mode 100644 index 0000000..666cfe7 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Abstractions/WorkflowStatus.cs @@ -0,0 +1,66 @@ +// 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.Runtime; + +/// +/// Exposes default workflow instance statuses +/// +public static class WorkflowStatus +{ + + /// + /// Indicates that the workflow is pending execution + /// + public const string Pending = "pending"; + /// + /// Indicates that the workflow is being executed + /// + public const string Running = "running"; + /// + /// Indicates that the workflow's execution has been suspended + /// + public const string Suspended = "suspended"; + /// + /// Indicates that the workflow's execution is waiting for event(s) + /// + public const string Waiting = "waiting"; + /// + /// Indicates that the workflow ran to completion + /// + public const string Completed = "completed"; + /// + /// Indicates that the workflow's execution has been cancelled + /// + public const string Cancelled = "cancelled"; + /// + /// Indicates that the workflow encountered an unhandled error during its execution and consequently faulted + /// + public const string Faulted = "faulted"; + + /// + /// Gets a new containing default workflow statuses + /// + /// A new containing default workflow statuses + public static IEnumerable AsEnumerable() + { + yield return Pending; + yield return Running; + yield return Suspended; + yield return Waiting; + yield return Completed; + yield return Cancelled; + yield return Faulted; + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Commands/RunWorkflowCommand.cs b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Commands/RunWorkflowCommand.cs new file mode 100644 index 0000000..f60b16a --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Commands/RunWorkflowCommand.cs @@ -0,0 +1,472 @@ +// 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 System.Diagnostics; +using System.Threading.Channels; +using ServerlessWorkflow.Sdk.Events.Tasks; +using ServerlessWorkflow.Sdk.Events.Workflows; +using Spectre.Console.Json; + +namespace ServerlessWorkflow.Sdk.Runtime.Cli.Commands; + +internal sealed class RunWorkflowCommand(IWorkflowRuntime workflowRuntime, ICloudEventBus cloudEventBus) + : AsyncCommand +{ + + protected override async Task ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken) + { + var file = new FileInfo(settings.WorkflowFile); + if (!file.Exists) + { + AnsiConsole.MarkupLineInterpolated($"[red]✗ Workflow file not found:[/] [yellow]{settings.WorkflowFile}[/]"); + return -1; + } + WorkflowDefinition definition; + try + { + definition = await LoadAsync(file, cancellationToken).ConfigureAwait(false) ?? throw new NullReferenceException($"Failed to deserialize workflow definition from '{file.FullName}'"); + } + catch (Exception ex) + { + AnsiConsole.MarkupLineInterpolated($"[red]✗ Failed to load workflow:[/] {ex.Message}"); + return -1; + } + JsonObject? input = null; + if (!string.IsNullOrWhiteSpace(settings.InputFile)) + { + var inputFile = new FileInfo(settings.InputFile); + if (!inputFile.Exists) + { + AnsiConsole.MarkupLineInterpolated($"[red]✗ Input file not found:[/] [yellow]{settings.InputFile}[/]"); + return -1; + } + try { input = await LoadAsync(inputFile, cancellationToken).ConfigureAwait(false); } + catch (Exception ex) + { + AnsiConsole.MarkupLineInterpolated($"[red]✗ Failed to load input:[/] {ex.Message}"); + return -1; + } + } + RenderBanner(definition, file, settings); + var channel = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true }); + var observable = await cloudEventBus.SubscribeAsync(cancellationToken).ConfigureAwait(false); + using var subscription = observable.Subscribe( + ev => channel.Writer.TryWrite(ev), + _ => channel.Writer.TryComplete(), + () => channel.Writer.TryComplete()); + var tracker = new WorkflowRunTracker(); + var table = BuildTable(); + var stopwatch = new Stopwatch(); + var initialLayout = new Rows(RenderStatus(tracker, TimeSpan.Zero, 0), table); + + Task? runTask = null; + try + { + await AnsiConsole.Live(initialLayout) + .AutoClear(false) + .Overflow(VerticalOverflow.Ellipsis) + .Cropping(VerticalOverflowCropping.Bottom) + .StartAsync(async ctx => + { + UpdateTable(table, tracker, 0); + ctx.UpdateTarget(new Rows(RenderStatus(tracker, TimeSpan.Zero, 0), table)); + ctx.Refresh(); + + stopwatch.Start(); + runTask = Task.Run(async () => + { + var process = await workflowRuntime.RunAsync(definition, input, new(), cancellationToken).ConfigureAwait(false); + try { await process.WaitAsync(cancellationToken).ConfigureAwait(false); } + catch { } + }, cancellationToken); + + var frame = 0; + while (true) + { + while (channel.Reader.TryRead(out var ev)) tracker.Apply(ev); + UpdateTable(table, tracker, frame); + ctx.UpdateTarget(new Rows(RenderStatus(tracker, stopwatch.Elapsed, frame), table)); + ctx.Refresh(); + if (runTask.IsCompleted) break; + frame++; + await Task.Delay(80, cancellationToken).ConfigureAwait(false); + } + while (channel.Reader.TryRead(out var ev)) tracker.Apply(ev); + UpdateTable(table, tracker, frame); + ctx.UpdateTarget(new Rows(RenderStatus(tracker, stopwatch.Elapsed, frame), table)); + ctx.Refresh(); + }).ConfigureAwait(false); + } + finally + { + stopwatch.Stop(); + if (runTask != null) { try { await runTask.ConfigureAwait(false); } catch { } } + } + RenderResult(tracker, stopwatch.Elapsed, settings); + if (tracker.Status == WorkflowStatus.Completed) return 0; + return 1; + } + + static string FormatDuration(TimeSpan d) => $"{d.TotalSeconds:0.000000}s"; + + static string ToRelativePath(string path) + { + try + { + var relative = Path.GetRelativePath(Directory.GetCurrentDirectory(), path); + return relative.Length < path.Length ? relative : path; + } + catch + { + return path; + } + } + + static async Task LoadAsync(FileInfo file, CancellationToken cancellationToken) + where T : class + { + var text = await File.ReadAllTextAsync(file.FullName, cancellationToken).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(text)) return default; + return file.Extension.ToLowerInvariant() switch + { + ".json" => JsonSerializer.Deserialize(text, Sdk.Serialization.Json.JsonSerializationContext.Default.Options), + ".yaml" or ".yml" => YamlSerializer.Deserialize(text, Sdk.Serialization.Json.JsonSerializationContext.Default.Options), + _ => throw new NotSupportedException($"Unsupported file extension '{file.Extension}'") + }; + } + + static void RenderBanner(WorkflowDefinition definition, FileInfo file, Settings settings) + { + var name = definition.Document?.Name ?? file.Name; + var version = definition.Document?.Version ?? "-"; + var ns = definition.Document?.Namespace ?? "-"; + var figlet = new FigletText("Serverless Workflow").Color(Color.DeepSkyBlue1).LeftJustified(); + var info = new Grid() + .AddColumn(new GridColumn().NoWrap().PadRight(2)) + .AddColumn(new GridColumn().NoWrap().PadRight(2)) + .AddColumn() + .AddRow("[deepskyblue1]❯[/]", "[grey]workflow[/]", $"[bold white]{Markup.Escape(name)}[/]") + .AddRow("[deepskyblue1]❯[/]", "[grey]namespace[/]", $"[white]{Markup.Escape(ns)}[/]") + .AddRow("[deepskyblue1]❯[/]", "[grey]version[/]", $"[white]{Markup.Escape(version)}[/]") + .AddRow("[deepskyblue1]❯[/]", "[grey]source[/]", $"[white]{Markup.Escape(ToRelativePath(file.FullName))}[/]"); + if (!string.IsNullOrWhiteSpace(settings.InputFile)) info.AddRow("[deepskyblue1]❯[/]", "[grey]input[/]", $"[white]{Markup.Escape(ToRelativePath(settings.InputFile))}[/]"); + if (!string.IsNullOrWhiteSpace(settings.OutputFile)) info.AddRow("[deepskyblue1]❯[/]", "[grey]output[/]", $"[white]{Markup.Escape(ToRelativePath(settings.OutputFile))}[/]"); + if (!string.IsNullOrWhiteSpace(settings.LogFile)) info.AddRow("[deepskyblue1]❯[/]", "[grey]log[/]", $"[white]{Markup.Escape(ToRelativePath(settings.LogFile))}[/]"); + AnsiConsole.WriteLine(); + AnsiConsole.Write(figlet); + AnsiConsole.WriteLine(); + AnsiConsole.Write(new Rule { Style = new Style(Color.DeepSkyBlue1) }); + AnsiConsole.Write(new Padder(info).PadTop(1).PadBottom(1).PadLeft(1)); + AnsiConsole.Write(new Rule { Style = new Style(Color.Grey35) }); + AnsiConsole.WriteLine(); + } + + static Table BuildTable() + { + var table = new Table() + .Border(TableBorder.Rounded) + .BorderColor(Color.Grey35) + .Expand(); + table.AddColumn(new TableColumn("[grey] [/]").Width(3).NoWrap()); + table.AddColumn(new TableColumn("[grey]Task[/]")); + table.AddColumn(new TableColumn("[grey]Status[/]").Width(14).NoWrap()); + table.AddColumn(new TableColumn("[grey]Duration[/]").RightAligned().Width(12).NoWrap()); + return table; + } + + static void UpdateTable(Table table, WorkflowRunTracker tracker, int frame) + { + table.Rows.Clear(); + var now = DateTimeOffset.Now; + foreach (var task in tracker.Tasks.Values) + { + var (glyph, color, label) = task.Status switch + { + TaskRunStatus.Pending => ("•", "grey", "pending"), + TaskRunStatus.Running => (SpinnerFrames[frame % SpinnerFrames.Length], "deepskyblue1", "running"), + TaskRunStatus.Completed => ("✓", "green", "completed"), + TaskRunStatus.Faulted => ("✗", "red", "faulted"), + TaskRunStatus.Skipped => ("»", "yellow", "skipped"), + TaskRunStatus.Cancelled => ("⊘", "orange3", "cancelled"), + TaskRunStatus.Suspended => ("‖", "grey70", "suspended"), + _ => ("?", "grey", "unknown") + }; + string duration; + if (task.Duration is { } d) duration = FormatDuration(d); + else if (task.Status == TaskRunStatus.Running && task.StartedAt is { } startedAt) duration = $"[deepskyblue1]{FormatDuration(now - startedAt)}[/]"; + else duration = "[grey]-[/]"; + var reference = Markup.Escape(task.Reference); + table.AddRow( + $"[{color}]{glyph}[/]", + $"[white]{reference}[/]", + $"[{color}]{label}[/]", + duration); + } + if (tracker.Tasks.Count == 0) table.AddRow("[grey]·[/]", "[grey]waiting for first task…[/]", string.Empty, string.Empty); + } + + static readonly string[] SpinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + + static Markup RenderStatus(WorkflowRunTracker tracker, TimeSpan elapsed, int frame) + { + var (color, label, running) = tracker.Status switch + { + WorkflowStatus.Running => ("deepskyblue1", "running", true), + WorkflowStatus.Completed => ("green", "completed", false), + WorkflowStatus.Faulted => ("red", "faulted", false), + WorkflowStatus.Cancelled => ("orange3", "cancelled", false), + WorkflowStatus.Suspended => ("grey70", "suspended", false), + _ => ("deepskyblue1", "pending", true) + }; + var glyph = running ? SpinnerFrames[frame % SpinnerFrames.Length] : "●"; + return new Markup($" [{color}]{glyph}[/] [bold {color}]{label}[/] [grey]·[/] [white]{elapsed:mm\\:ss\\.fff}[/] [grey]·[/] [white]{tracker.CompletedCount}[/][grey]/[/][white]{tracker.Tasks.Count}[/] [grey]tasks[/]"); + } + + static void RenderResult(WorkflowRunTracker tracker, TimeSpan elapsed, Settings settings) + { + AnsiConsole.WriteLine(); + var (color, glyph, label) = tracker.Status switch + { + WorkflowStatus.Completed => (Color.Green, "✓", "COMPLETED"), + WorkflowStatus.Faulted => (Color.Red, "✗", "FAULTED"), + WorkflowStatus.Cancelled => (Color.Orange3, "⊘", "CANCELLED"), + WorkflowStatus.Suspended => (Color.Grey70, "‖", "SUSPENDED"), + _ => (Color.Grey, "?", "UNKNOWN") + }; + + var summary = new Grid() + .AddColumn(new GridColumn().NoWrap().PadRight(2)) + .AddColumn() + .AddRow("[grey]status[/]", $"[bold {color.ToMarkup()}]{glyph} {label}[/]") + .AddRow("[grey]duration[/]", $"[white]{FormatDuration(elapsed)}[/]") + .AddRow("[grey]tasks[/]", $"[green]{tracker.CompletedCount} completed[/] [yellow]{tracker.SkippedCount} skipped[/] [red]{tracker.FaultedCount} faulted[/] [white]{tracker.Tasks.Count} total[/]"); + + AnsiConsole.Write(new Panel(summary) + { + Header = new PanelHeader($" [bold {color.ToMarkup()}]workflow {label.ToLowerInvariant()}[/] "), + Border = BoxBorder.Rounded, + BorderStyle = new Style(color), + Padding = new Padding(1, 0, 1, 0), + Expand = true + }); + + if (tracker.Error != null) + { + var errGrid = new Grid().AddColumn(new GridColumn().NoWrap().PadRight(2)).AddColumn(); + if (tracker.Error.Type != null) errGrid.AddRow("[grey]type[/]", $"[red]{Markup.Escape(tracker.Error.Type.ToString()!)}[/]"); + if (!string.IsNullOrWhiteSpace(tracker.Error.Title)) errGrid.AddRow("[grey]title[/]", $"[red]{Markup.Escape(tracker.Error.Title!)}[/]"); + if (tracker.Error.Status != 0) errGrid.AddRow("[grey]status[/]", $"[red]{tracker.Error.Status}[/]"); + if (!string.IsNullOrWhiteSpace(tracker.Error.Detail)) errGrid.AddRow("[grey]detail[/]", $"[red]{Markup.Escape(tracker.Error.Detail!)}[/]"); + AnsiConsole.Write(new Panel(errGrid) + { + Header = new PanelHeader(" [bold red]error[/] "), + Border = BoxBorder.Rounded, + BorderStyle = new Style(Color.Red), + Padding = new Padding(1, 0, 1, 0), + Expand = true + }); + } + + if (tracker.Output != null) + { + var json = tracker.Output.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + var jsonText = new JsonText(json) + .BracesColor(Color.Grey70) + .BracketColor(Color.Grey70) + .ColonColor(Color.Grey) + .CommaColor(Color.Grey) + .MemberColor(Color.White) + .StringColor(Color.DarkSeaGreen2) + .NumberColor(Color.DeepSkyBlue1) + .BooleanColor(Color.Gold1) + .NullColor(Color.Grey); + var outputPanel = new Panel(jsonText) + { + Header = new PanelHeader(" [bold deepskyblue1]output[/] "), + Border = BoxBorder.Rounded, + BorderStyle = new Style(Color.DeepSkyBlue1), + Padding = new Padding(1, 0, 1, 0), + Expand = true + }; + AnsiConsole.Write(outputPanel); + + if (!string.IsNullOrWhiteSpace(settings.OutputFile)) + { + try + { + var dir = Path.GetDirectoryName(Path.GetFullPath(settings.OutputFile)); + if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir); + File.WriteAllText(settings.OutputFile, json); + AnsiConsole.MarkupLineInterpolated($"[grey]→ output written to[/] [white]{settings.OutputFile}[/]"); + } + catch (Exception ex) + { + AnsiConsole.MarkupLineInterpolated($"[red]✗ Failed to write output file:[/] {ex.Message}"); + } + } + } + + AnsiConsole.WriteLine(); + } + + internal sealed class Settings + : CommandSettings + { + + [CommandOption("-f|--file")] + [Description("Path to the workflow definition file (.json, .yaml, .yml)")] + public required string WorkflowFile { get; init; } + + [CommandOption("-i|--input")] + [Description("Path to an input data file (.json, .yaml, .yml)")] + public string? InputFile { get; init; } + + [CommandOption("-o|--output")] + [Description("Write the workflow's output to the specified file")] + public string? OutputFile { get; init; } + + [CommandOption("-l|--log")] + [Description("Write runtime logs to the specified file (instead of polluting the console)")] + public string? LogFile { get; init; } + + } + + enum TaskRunStatus { Pending, Running, Completed, Faulted, Skipped, Cancelled, Suspended } + + sealed class TaskRunInfo + { + public required string Reference { get; init; } + public TaskRunStatus Status { get; set; } = TaskRunStatus.Pending; + public DateTimeOffset? StartedAt { get; set; } + public DateTimeOffset? EndedAt { get; set; } + public TimeSpan? Duration => StartedAt.HasValue && EndedAt.HasValue ? EndedAt - StartedAt : null; + } + + sealed class WorkflowRunTracker + { + + public Dictionary Tasks { get; } = new(); + public string Status { get; private set; } = WorkflowStatus.Pending; + public JsonNode? Output { get; private set; } + public Error? Error { get; private set; } + + public int CompletedCount => CountBy(TaskRunStatus.Completed); + public int FaultedCount => CountBy(TaskRunStatus.Faulted); + public int SkippedCount => CountBy(TaskRunStatus.Skipped); + + int CountBy(TaskRunStatus status) + { + var n = 0; + foreach (var t in Tasks.Values) if (t.Status == status) n++; + return n; + } + + public void Apply(ICloudEvent ev) + { + switch (ev.Data) + { + case WorkflowStartedEvent: + Status = WorkflowStatus.Running; + break; + case WorkflowCompletedEvent completed: + Status = WorkflowStatus.Completed; + Output = ToJsonNode(completed.Output); + break; + case WorkflowFaultedEvent faulted: + Status = WorkflowStatus.Faulted; + Error = faulted.Error; + break; + case WorkflowCancelledEvent: + Status = WorkflowStatus.Cancelled; + break; + case WorkflowSuspendedEvent: + Status = WorkflowStatus.Suspended; + break; + case WorkflowResumedEvent: + Status = WorkflowStatus.Running; + break; + case TaskCreatedEvent created: + GetOrAdd(created.Task); + break; + case TaskStartedEvent started: + { + var task = GetOrAdd(started.Task); + task.Status = TaskRunStatus.Running; + task.StartedAt = started.StartedAt; + break; + } + case TaskCompletedEvent taskCompleted: + { + var task = GetOrAdd(taskCompleted.Task); + task.Status = TaskRunStatus.Completed; + task.EndedAt = taskCompleted.CompletedAt; + break; + } + case TaskFaultedEvent taskFaulted: + { + var task = GetOrAdd(taskFaulted.Task); + task.Status = TaskRunStatus.Faulted; + task.EndedAt = taskFaulted.FaultedAt; + break; + } + case TaskSkippedEvent skipped: + { + var task = GetOrAdd(skipped.Task); + task.Status = TaskRunStatus.Skipped; + task.EndedAt = skipped.SkippedAt; + break; + } + case TaskCancelledEvent cancelled: + { + var task = GetOrAdd(cancelled.Task); + task.Status = TaskRunStatus.Cancelled; + task.EndedAt = cancelled.CancelledAt; + break; + } + case TaskSuspendedEvent suspended: + { + var task = GetOrAdd(suspended.Task); + task.Status = TaskRunStatus.Suspended; + break; + } + } + } + + TaskRunInfo GetOrAdd(Json.Pointer.JsonPointer reference) + { + var key = reference.ToString(); + if (!Tasks.TryGetValue(key, out var task)) + { + task = new TaskRunInfo { Reference = key }; + Tasks[key] = task; + } + return task; + } + + static JsonNode? ToJsonNode(object? value) => value switch + { + null => null, + JsonNode node => node.DeepClone(), + _ => JsonSerializer.SerializeToNode(value, Sdk.Serialization.Json.JsonSerializationContext.Default.Options) + }; + + } + +} + +file static class ColorExtensions +{ + + public static string ToMarkup(this Color color) => color.ToString(); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Extensions/LoggingBuilderExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Extensions/LoggingBuilderExtensions.cs new file mode 100644 index 0000000..a643eda --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Extensions/LoggingBuilderExtensions.cs @@ -0,0 +1,63 @@ +// 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.Runtime.Cli.Extensions; + +/// +/// Defines extensions for configuring the CLI's logging pipeline +/// +internal static class LoggingBuilderExtensions +{ + + const string FileLogOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}"; + + /// + /// Configures Serilog to write to the specified file, if is not null or whitespace + /// + /// The to configure + /// The file to write logs to, if any + /// The configured + public static ILoggingBuilder AddFileLogging(this ILoggingBuilder logging, string? logFile) + { + if (string.IsNullOrWhiteSpace(logFile)) return logging; + var serilog = new LoggerConfiguration() + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.File( + path: logFile, + outputTemplate: FileLogOutputTemplate, + shared: false, + rollOnFileSizeLimit: false) + .CreateLogger(); + logging.AddSerilog(serilog, dispose: true); + return logging; + } + + /// + /// Extracts the value of the -l/--log option from the specified command-line arguments, if any + /// + /// The command-line arguments to inspect + /// The log file path, or if not specified + public static string? GetLogFileOption(this string[] args) + { + for (var i = 0; i < args.Length; i++) + { + var a = args[i]; + if (a is "-l" or "--log") return i + 1 < args.Length ? args[i + 1] : null; + if (a.StartsWith("--log=", StringComparison.OrdinalIgnoreCase)) return a[6..]; + if (a.StartsWith("-l=", StringComparison.OrdinalIgnoreCase)) return a[3..]; + } + return null; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Program.cs b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Program.cs new file mode 100644 index 0000000..bc913b2 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Program.cs @@ -0,0 +1,35 @@ +// 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. + +Console.OutputEncoding = System.Text.Encoding.UTF8; + +var builder = Host.CreateApplicationBuilder(args); +builder.Logging.ClearProviders(); +builder.Logging.AddFileLogging(args.GetLogFileOption()); +builder.Services.AddServerlessWorkflowRuntime(builder.Configuration); +builder.Services.AddSingleton(); + +using var host = builder.Build(); +await host.StartAsync(); + +var app = new CommandApp(new TypeRegistrar(host.Services)); +app.Configure(config => +{ + config.SetApplicationName("Serverless Workflow CLI"); + config.AddCommand("run") + .WithDescription("Runs a workflow definition"); +}); + +var exitCode = app.Run(args); +await host.StopAsync(); +return exitCode; diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Properties/launchSettings.json b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Properties/launchSettings.json new file mode 100644 index 0000000..13cc897 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "ServerlessWorkflow.Sdk.Runtime.Cli": { + "commandName": "Project", + "commandLineArgs": "run -f test.yaml -i input.yaml -o output.yaml -l logs.txt" + } + } +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/README.md b/src/ServerlessWorkflow.Sdk.Runtime.Cli/README.md new file mode 100644 index 0000000..ff816f8 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/README.md @@ -0,0 +1,51 @@ +# ServerlessWorkflow.Sdk.Runtime.Cli (`swf`) + +A command-line runner for [Serverless Workflow DSL](https://github.com/serverlessworkflow/specification/blob/main/dsl.md) definitions, built on top of [ServerlessWorkflow.Sdk.Runtime](../ServerlessWorkflow.Sdk.Runtime). + +The tool is published as a self-contained single-file executable named `swf`. It reads a workflow definition (JSON or YAML), executes it against the local runtime, and renders task progress, status, and duration in the terminal using Spectre.Console. + +## Commands + +### `run` + +Executes a workflow definition. + +```bash +swf run -f [-i ] [-o ] [-l ] +``` + +| Option | Description | +|---|---| +| `-f, --file` | **Required.** Path to the workflow definition (`.json`, `.yaml`, `.yml`). | +| `-i, --input` | Path to an input data file (`.json`, `.yaml`, `.yml`). | +| `-o, --output` | Path to a file to write the workflow output to. | +| `-l, --log` | Path to a file to write runtime logs to (instead of the console). | + +### Examples + +Run a workflow with no input: + +```bash +swf run -f workflow.yaml +``` + +Run a workflow with input, capture its output, and log to a file: + +```bash +swf run -f workflow.yaml -i input.json -o result.json -l runtime.log +``` + +## Output + +The CLI displays: + +- Each task as it is pending, running, completed, faulted, skipped, cancelled, or suspended. +- Per-task duration. +- The final workflow output (or the file it was written to). + +## Related packages + +- [ServerlessWorkflow.Sdk](../ServerlessWorkflow.Sdk) — core DSL models +- [ServerlessWorkflow.Sdk.Builders](../ServerlessWorkflow.Sdk.Builders) — fluent builders +- [ServerlessWorkflow.Sdk.Runtime](../ServerlessWorkflow.Sdk.Runtime) — workflow runtime +- [Project root](../../README.md) diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/ServerlessWorkflow.Sdk.Runtime.Cli.csproj b/src/ServerlessWorkflow.Sdk.Runtime.Cli/ServerlessWorkflow.Sdk.Runtime.Cli.csproj new file mode 100644 index 0000000..c70e3c0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/ServerlessWorkflow.Sdk.Runtime.Cli.csproj @@ -0,0 +1,31 @@ + + + + Exe + net10.0 + enable + enable + swf + true + true + 1.0.2 + $(VersionPrefix) + $(VersionPrefix) + en + true + + + + + + + + + + + + + + + + diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Services/TypeRegistrar.cs b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Services/TypeRegistrar.cs new file mode 100644 index 0000000..fa98805 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Services/TypeRegistrar.cs @@ -0,0 +1,28 @@ +// 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.Runtime.Cli.Services; + +internal sealed class TypeRegistrar(IServiceProvider serviceProvider) + : ITypeRegistrar +{ + + public void Register(Type service, Type implementation) { } + + public void RegisterInstance(Type service, object implementation) { } + + public void RegisterLazy(Type service, Func factory) { } + + public ITypeResolver Build() => new TypeResolver(serviceProvider); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Services/TypeResolver.cs b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Services/TypeResolver.cs new file mode 100644 index 0000000..a19ab34 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Services/TypeResolver.cs @@ -0,0 +1,22 @@ +// 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.Runtime.Cli.Services; + +internal sealed class TypeResolver(IServiceProvider serviceProvider) + : ITypeResolver +{ + + public object? Resolve(Type? type) => type is null ? null : serviceProvider.GetService(type); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Cli/Usings.cs b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Usings.cs new file mode 100644 index 0000000..d766bf3 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Cli/Usings.cs @@ -0,0 +1,29 @@ +// 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. + +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Serilog; +global using ServerlessWorkflow.Sdk.Models; +global using ServerlessWorkflow.Sdk.Runtime; +global using ServerlessWorkflow.Sdk.Runtime.Cli.Commands; +global using ServerlessWorkflow.Sdk.Runtime.Cli.Extensions; +global using ServerlessWorkflow.Sdk.Runtime.Cli.Services; +global using ServerlessWorkflow.Sdk.Runtime.Services; +global using Spectre.Console; +global using Spectre.Console.Cli; +global using System.ComponentModel; +global using System.Text.Json; +global using System.Text.Json.Nodes; +global using Yaml2JsonNode; diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/Configuration/DockerApiConfiguration.cs b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Configuration/DockerApiConfiguration.cs new file mode 100644 index 0000000..0171bac --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Configuration/DockerApiConfiguration.cs @@ -0,0 +1,32 @@ +// 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.Runtime.Configuration; + +/// +/// Represents an object used to configure the Docker API to use +/// +public sealed record DockerApiConfiguration +{ + + /// + /// Gets/sets the endpoint of the Docker API to use + /// + public Uri Endpoint { get; set; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new("npipe://./pipe/docker_engine") : new("unix:/var/run/docker.sock"); + + /// + /// Gets/sets the version of the Docker API to use + /// + public string? Version { get; set; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/Configuration/DockerContainerRuntimeOptions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Configuration/DockerContainerRuntimeOptions.cs new file mode 100644 index 0000000..9ee60b7 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Configuration/DockerContainerRuntimeOptions.cs @@ -0,0 +1,37 @@ +// 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.Runtime.Configuration; + +/// +/// Represents the options used to configure the +/// +public sealed record DockerContainerRuntimeOptions +{ + + /// + /// Gets the default network to connect containers to + /// + public const string DefaultNetwork = "synapse"; + + /// + /// Gets/sets the Docker API to use + /// + public DockerApiConfiguration Api { get; set; } = new(); + + /// + /// Gets/sets the network to connect containers to, if any + /// + public string? Network { get; set; } = DefaultNetwork; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/Extensions/IWorkflowRuntimeBuilder.cs b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Extensions/IWorkflowRuntimeBuilder.cs new file mode 100644 index 0000000..7b2578e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Extensions/IWorkflowRuntimeBuilder.cs @@ -0,0 +1,38 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s. +/// +public static class IWorkflowRuntimeBuilderExtensions +{ + + /// + /// Configures the to use the Docker container runtime. + /// + /// The to configure. + /// An used to configure the . + /// The configured . + public static IWorkflowRuntimeBuilder UseDockerContainerRuntime(this IWorkflowRuntimeBuilder builder, Action? setup = null) + { + if (setup is not null) builder.Services.Configure(setup); + builder.Services.TryAddSingleton(); + builder.Services.AddSingleton(provider => provider.GetRequiredService()); + builder.Services.AddSingleton(provider => provider.GetRequiredService()); + return builder; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/ServerlessWorkflow.Sdk.Runtime.Docker.csproj b/src/ServerlessWorkflow.Sdk.Runtime.Docker/ServerlessWorkflow.Sdk.Runtime.Docker.csproj new file mode 100644 index 0000000..5627a80 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/ServerlessWorkflow.Sdk.Runtime.Docker.csproj @@ -0,0 +1,44 @@ + + + + net10.0 + enable + enable + ServerlessWorkflow.Sdk.Runtime + true + 1.0.2 + $(VersionPrefix) + $(VersionPrefix) + en + true + True + true + Serverless Workflow SDK - Docker Container Runtime + Contains the services used to execute ServerlessWorkflow workflow definitions in a Docker environment + serverless-workflow;serverless;workflow;dsl;sdk;runtime;docker + true + Apache-2.0 + README.md + Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. + https://github.com/serverlessworkflow/sdk-net + https://github.com/serverlessworkflow/sdk-net + git + embedded + + + + + + + + + + + + + \ + True + + + + diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/Services/DockerContainer.cs b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Services/DockerContainer.cs new file mode 100644 index 0000000..bf4832a --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Services/DockerContainer.cs @@ -0,0 +1,94 @@ +// 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.Runtime.Services; + +/// +/// Represents a Docker +/// +/// The container's ID +/// The service used to interact with the Docker API +public sealed class DockerContainer(string id, IDockerClient docker) + : IContainer +{ + + Pipe? standardOutputPipe; + Pipe? standardErrorPipe; + MultiplexedStream? multiplexedStream; + Task? copyTask; + + /// + public StreamReader? StandardOutput { get; private set; } + + /// + public StreamReader? StandardError { get; private set; } + + /// + public long? ExitCode { get; private set; } + + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await docker.Containers.StartContainerAsync(id, new() { }, cancellationToken).ConfigureAwait(false); + multiplexedStream = await docker.Containers.GetContainerLogsAsync(id, false, new() + { + Follow = true, + ShowStdout = true, + ShowStderr = true, + Timestamps = false + }, cancellationToken).ConfigureAwait(false); + standardOutputPipe = new(); + standardErrorPipe = new(); + copyTask = multiplexedStream.CopyOutputToAsync(Stream.Null, standardOutputPipe.Writer.AsStream(), standardErrorPipe.Writer.AsStream(), cancellationToken); + StandardOutput = new(standardOutputPipe.Reader.AsStream()); + StandardError = new(standardErrorPipe.Reader.AsStream()); + } + + /// + public async Task WaitForExitAsync(CancellationToken cancellationToken = default) + { + var response = await docker.Containers.WaitContainerAsync(id, cancellationToken).ConfigureAwait(false); + ExitCode = response.StatusCode; + if (copyTask != null) await copyTask.ConfigureAwait(false); + if (standardOutputPipe != null) await standardOutputPipe.Writer.CompleteAsync().ConfigureAwait(false); + if (standardErrorPipe != null) await standardErrorPipe.Writer.CompleteAsync().ConfigureAwait(false); + } + + /// + public Task StopAsync(CancellationToken cancellationToken = default) => docker.Containers.StopContainerAsync(id, new() { }, cancellationToken); + + /// + public async ValueTask DisposeAsync() + { + multiplexedStream?.Dispose(); + if (standardOutputPipe != null) + { + await standardOutputPipe.Writer.CompleteAsync().ConfigureAwait(false); + await standardOutputPipe.Reader.CompleteAsync().ConfigureAwait(false); + } + if (standardErrorPipe != null) + { + await standardErrorPipe.Writer.CompleteAsync().ConfigureAwait(false); + await standardErrorPipe.Reader.CompleteAsync().ConfigureAwait(false); + } + GC.SuppressFinalize(this); + } + + /// + public void Dispose() + { + multiplexedStream?.Dispose(); + GC.SuppressFinalize(this); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/Services/DockerContainerRuntime.cs b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Services/DockerContainerRuntime.cs new file mode 100644 index 0000000..1ea6ec0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Services/DockerContainerRuntime.cs @@ -0,0 +1,118 @@ +// 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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents a Docker implementation of the interface +/// +/// The service used to perform logging +/// The current +/// The current +public sealed class DockerContainerRuntime(ILogger logger, IHostEnvironment environment, IOptions options) + : IHostedService, IContainerRuntime, IDisposable, IAsyncDisposable +{ + + IDockerClient? docker; + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + var dockerConfiguration = new DockerClientConfiguration(options.Value.Api.Endpoint); + docker = dockerConfiguration.CreateClient(string.IsNullOrWhiteSpace(options.Value.Api.Version) ? null : System.Version.Parse(options.Value.Api.Version!)); + if (!environment.RunsInDocker()) return; + var containerShortId = Environment.MachineName; + var containerId = (await docker.Containers.InspectContainerAsync(containerShortId, cancellationToken)).ID; + var response = null as NetworkResponse; + try + { + response = await docker.Networks.InspectNetworkAsync(options.Value.Network, cancellationToken); + } + catch (DockerNetworkNotFoundException) + { + await docker.Networks.CreateNetworkAsync(new() + { + Name = options.Value.Network + }, cancellationToken); + } + finally + { + if (response == null || !response!.Containers.ContainsKey(containerId)) await docker.Networks.ConnectNetworkAsync(options.Value.Network, new() + { + Container = containerId + }, cancellationToken); + } + } + + /// + public async Task CreateAsync(ContainerProcessDefinition definition, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + if (docker == null) throw new NullReferenceException("The DockerContainerPlatform has not been properly initialized"); + try + { + await docker.Images.InspectImageAsync(definition.Image, cancellationToken).ConfigureAwait(false); + } + catch (DockerApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + var downloadProgress = new Progress(); + var imageComponents = definition.Image.Split(':'); + var imageName = imageComponents[0]; + var imageTag = imageComponents.Length > 1 ? imageComponents[1] : null; + await docker.Images.CreateImageAsync(new() + { + FromImage = imageName, + Tag = imageTag + }, new(), downloadProgress, cancellationToken).ConfigureAwait(false); + } + var parameters = new CreateContainerParameters() + { + Image = definition.Image, + Cmd = string.IsNullOrWhiteSpace(definition.Command) ? null : ["/bin/sh", "-c", definition.Command], + Env = definition.Environment?.Select(e => $"{e.Key}={e.Value}").ToList(), + HostConfig = new() + { + PortBindings = definition.Ports?.ToDictionary(kvp => kvp.Value.ToString(), kvp => (IList)[new PortBinding() { HostPort = kvp.Key.ToString() }]), + Binds = definition.Volumes?.Select(e => $"{e.Key}:{e.Value}")?.ToList() ?? [] + } + }; + var response = await docker.Containers.CreateContainerAsync(parameters, cancellationToken).ConfigureAwait(false); + if (environment.RunsInDocker()) await docker.Networks.ConnectNetworkAsync(options.Value.Network, new() + { + Container = response.ID + }, cancellationToken); + foreach (var warning in response.Warnings) + { + logger.LogWarning(warning); + } + return new DockerContainer(response.ID, docker); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + public async ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + } + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Docker/Usings.cs b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Usings.cs new file mode 100644 index 0000000..a4be88d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Docker/Usings.cs @@ -0,0 +1,25 @@ +// 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. + +global using Docker.DotNet; +global using Docker.DotNet.Models; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using ServerlessWorkflow.Sdk.Runtime.Configuration; +global using ServerlessWorkflow.Sdk.Runtime.Services; +global using System.IO.Pipelines; +global using System.Net; +global using System.Runtime.InteropServices; diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Configuration/KubernetesContainerRuntimeOptions.cs b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Configuration/KubernetesContainerRuntimeOptions.cs new file mode 100644 index 0000000..8bebf42 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Configuration/KubernetesContainerRuntimeOptions.cs @@ -0,0 +1,39 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.Runtime.Configuration; + +/// +/// Represents the options used to configure the +/// +public sealed class KubernetesContainerRuntimeOptions +{ + + /// + /// Gets/sets the path to the Kubeconfig file to use, if any. If not set, defaults to 'InCluster' configuration + /// + public string? Kubeconfig { get; set; } + + /// + /// Gets/sets the Kubernetes image pull policy. Supported values are 'Always', 'IfNotPresent' and 'Never'. Defaults to 'Always'. + /// + public string ImagePullPolicy { get; set; } = "Always"; + + /// + /// Gets/sets the Kubernetes namespace to use for the created pods. + /// + public string? Namespace { get; set; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Extensions/IWorkflowRuntimeBuilder.cs b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Extensions/IWorkflowRuntimeBuilder.cs new file mode 100644 index 0000000..dc06a86 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Extensions/IWorkflowRuntimeBuilder.cs @@ -0,0 +1,38 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s. +/// +public static class IWorkflowRuntimeBuilderExtensions +{ + + /// + /// Configures the to use the Docker container runtime. + /// + /// The to configure. + /// An used to configure the . + /// The configured . + public static IWorkflowRuntimeBuilder UseKubernetesContainerRuntime(this IWorkflowRuntimeBuilder builder, Action? setup = null) + { + if (setup is not null) builder.Services.Configure(setup); + builder.Services.TryAddSingleton(); + builder.Services.AddSingleton(provider => provider.GetRequiredService()); + builder.Services.AddSingleton(provider => provider.GetRequiredService()); + return builder; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/ServerlessWorkflow.Sdk.Runtime.Kubernetes.csproj b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/ServerlessWorkflow.Sdk.Runtime.Kubernetes.csproj new file mode 100644 index 0000000..8851279 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/ServerlessWorkflow.Sdk.Runtime.Kubernetes.csproj @@ -0,0 +1,46 @@ + + + + net10.0 + enable + enable + ServerlessWorkflow.Sdk.Runtime + true + true + 1.0.2 + $(VersionPrefix) + $(VersionPrefix) + en + true + True + true + Serverless Workflow SDK - Kubernetes Container Runtime + Contains the services used to execute ServerlessWorkflow workflow definitions in a Kubernetes environment + serverless-workflow;serverless;workflow;dsl;sdk;runtime;kubernetes + true + Apache-2.0 + README.md + Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. + https://github.com/serverlessworkflow/sdk-net + https://github.com/serverlessworkflow/sdk-net + git + embedded + + + + + + + + + + + + + + \ + True + + + + diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Services/KubernetesContainer.cs b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Services/KubernetesContainer.cs new file mode 100644 index 0000000..a9a1aee --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Services/KubernetesContainer.cs @@ -0,0 +1,108 @@ +// 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.Runtime.Services; + +/// +/// Represents a Kubernetes +/// +/// The the belongs to +/// The service used to perform logging +/// The service used to interact with the Docker API +public sealed class KubernetesContainer(V1Pod pod, ILogger logger, IKubernetes kubernetes) + : IContainer +{ + + CancellationTokenSource cancellationTokenSource = new(); + + /// + public StreamReader? StandardOutput { get; private set; } + + /// + public StreamReader? StandardError { get; private set; } + + /// + public long? ExitCode { get; private set; } + + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + try + { + if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Creating pod '{pod}'...", $"{pod.Name()}.{pod.Namespace()}"); + pod = await kubernetes.CoreV1.CreateNamespacedPodAsync(pod, pod.Namespace(), cancellationToken: cancellationToken); + if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("The pod '{pod}' has been successfully created", $"{pod.Name()}.{pod.Namespace()}"); + } + catch (Exception ex) + { + logger.LogError("An error occurred while creating the specified pod '{pod}': {ex}", $"{pod.Name()}.{pod.Namespace()}", ex); + } + await ReadPodLogsAsync(cancellationToken).ConfigureAwait(false); + } + + async Task ReadPodLogsAsync(CancellationToken cancellationToken) + { + await WaitForReadyAsync(cancellationToken); + var logStream = await kubernetes.CoreV1.ReadNamespacedPodLogAsync(pod.Name(), pod.Namespace(), cancellationToken: cancellationToken).ConfigureAwait(false); + StandardOutput = new StreamReader(logStream); + } + + async Task WaitForReadyAsync(CancellationToken cancellationToken) + { + logger.LogDebug("Waiting for pod '{pod}'...", $"{pod.Name()}.{pod.Namespace()}"); + pod = await kubernetes.CoreV1.ReadNamespacedPodAsync(pod.Name(), pod.Namespace(), cancellationToken: cancellationToken); + while (pod.Status.Phase == "Pending") + { + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + pod = await kubernetes.CoreV1.ReadNamespacedPodAsync(pod.Name(), pod.Namespace(), cancellationToken: cancellationToken); + } + logger.LogDebug("The pod '{pod}' is up and running", $"{pod.Name()}.{pod.Namespace()}"); + } + + /// + public async Task WaitForExitAsync(CancellationToken cancellationToken = default) + { + await foreach(var (_, item) in kubernetes.CoreV1.WatchListNamespacedPodAsync(pod.Namespace(), fieldSelector: $"metadata.name={pod.Name()}", cancellationToken: cancellationToken).WithCancellation(cancellationToken)) + { + if (item.Status.Phase != "Succeeded" && item.Status.Phase != "Failed") continue; + var containerStatus = item.Status.ContainerStatuses.FirstOrDefault(); + ExitCode = containerStatus?.State.Terminated?.ExitCode ?? -1; + break; + } + } + + /// + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await kubernetes.CoreV1.DeleteNamespacedPodAsync(pod.Name(), pod.Namespace(), cancellationToken: cancellationToken).ConfigureAwait(false); + await cancellationTokenSource.CancelAsync().ConfigureAwait(false); + } + + /// + public ValueTask DisposeAsync() + { + StandardOutput?.Dispose(); + StandardError?.Dispose(); + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + /// + public void Dispose() + { + StandardOutput?.Dispose(); + StandardError?.Dispose(); + GC.SuppressFinalize(this); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Services/KubernetesContainerRuntime.cs b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Services/KubernetesContainerRuntime.cs new file mode 100644 index 0000000..c085571 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Services/KubernetesContainerRuntime.cs @@ -0,0 +1,87 @@ +// 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.Runtime.Services; + +/// +/// Represents the Docker implementation of the interface +/// +/// The current +/// The current +public sealed class KubernetesContainerRuntime(IServiceProvider serviceProvider, IOptions options) + : IHostedService, IContainerRuntime, IDisposable, IAsyncDisposable +{ + + Kubernetes? kubernetes; + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + var kubeconfig = string.IsNullOrWhiteSpace(options.Value.Kubeconfig) + ? KubernetesClientConfiguration.InClusterConfig() + : await KubernetesClientConfiguration.BuildConfigFromConfigFileAsync(new FileInfo(options.Value.Kubeconfig)).ConfigureAwait(false); + kubernetes = new Kubernetes(kubeconfig); + } + + /// + public Task CreateAsync(ContainerProcessDefinition definition, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + if (kubernetes is null) throw new NullReferenceException("The KubernetesContainerPlatform has not been properly initialized"); + var pod = new V1Pod() + { + Metadata = new() + { + NamespaceProperty = options.Value.Namespace, + Name = $"{definition.Image}-{Guid.NewGuid().ToString("N")[..6].ToLowerInvariant()}" + }, + Spec = new() + { + RestartPolicy = "Never", + Containers = + [ + new() + { + Image = definition.Image, + ImagePullPolicy = options.Value.ImagePullPolicy, + Command = string.IsNullOrWhiteSpace(definition.Command) ? null : ["/bin/sh", "-c", definition.Command], + Env = definition.Environment?.Select(e => new V1EnvVar() + { + Name = e.Key, + Value = e.Value + }).ToList() + } + ] + } + }; + return Task.FromResult((IContainer)ActivatorUtilities.CreateInstance(serviceProvider, pod, kubernetes)); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + public async ValueTask DisposeAsync() + { + kubernetes?.Dispose(); + GC.SuppressFinalize(this); + } + + /// + public void Dispose() + { + kubernetes?.Dispose(); + GC.SuppressFinalize(this); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Usings.cs b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Usings.cs new file mode 100644 index 0000000..d747cc5 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime.Kubernetes/Usings.cs @@ -0,0 +1,23 @@ +// 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. + +global using k8s; +global using k8s.Models; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using ServerlessWorkflow.Sdk.Models.Processes; +global using ServerlessWorkflow.Sdk.Runtime.Configuration; +global using ServerlessWorkflow.Sdk.Runtime.Services; diff --git a/src/ServerlessWorkflow.Sdk.Runtime/AsyncLock.cs b/src/ServerlessWorkflow.Sdk.Runtime/AsyncLock.cs new file mode 100644 index 0000000..58874f3 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/AsyncLock.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.Runtime; + +/// +/// Represents an object used to lock asynchronous processes +/// +/// Code based on +public class AsyncLock +{ + + readonly SemaphoreSlim semaphore = new(1, 2); + readonly Task releaser; + + /// + /// Initializes a new + /// + public AsyncLock() + { + releaser = Task.FromResult((IDisposable)new Releaser(this)); + } + + /// + /// Locks asynchronously + /// + /// A + /// A new object which releases the lock upon disposal + public Task LockAsync(CancellationToken cancellationToken = default) + { + var waitTask = semaphore.WaitAsync(cancellationToken); + return waitTask.IsCompleted + ? releaser + : waitTask.ContinueWith((_, state) => (IDisposable)state!, releaser.Result, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + class Releaser(AsyncLock toRelease) + : IDisposable + { + + public void Dispose() => toRelease.semaphore.Release(); + + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/CloudEventAttributes.cs b/src/ServerlessWorkflow.Sdk.Runtime/CloudEventAttributes.cs new file mode 100644 index 0000000..81d2769 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/CloudEventAttributes.cs @@ -0,0 +1,75 @@ +// 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.Runtime; + +/// +/// Exposes constants about attributes +/// +public static class CloudEventAttributes +{ + + /// + /// Identifies the event + /// + public const string Id = "id"; + /// + /// Identifies the context in which an event happened + /// + public const string SpecVersion = "specversion"; + /// + /// Timestamp of when the occurrence happened + /// + public const string Time = "time"; + /// + /// Identifies the context in which an event happened + /// + public const string Source = "source"; + /// + /// Describes the type of event related to the originating occurrence + /// + public const string Type = "type"; + /// + /// Describes the subject of the event in the context of the event producer (identified by source) + /// + public const string Subject = "subject"; + /// + /// Content type of data value + /// + public const string DataContentType = "datacontenttype"; + /// + /// Identifies the schema that data adheres to + /// + public const string DataSchema = "dataschema"; + /// + /// The event payload. Only used when events are formatted using the structured mode + /// + public const string Data = "data"; + /// + /// The event payload, encoded in base 64. Only used when events are formatted using the binary mode + /// + public const string DataBase64 = "data_base64"; + + /// + /// Gets an that contains all required attributes + /// + /// A new that contains all required attributes + public static IEnumerable GetRequiredAttributes() + { + yield return Id; + yield return SpecVersion; + yield return Source; + yield return Type; + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Configuration/SecretManagerOptions.cs b/src/ServerlessWorkflow.Sdk.Runtime/Configuration/SecretManagerOptions.cs new file mode 100644 index 0000000..3522f0f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Configuration/SecretManagerOptions.cs @@ -0,0 +1,35 @@ +// 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.Runtime.Configuration; + +/// +/// Represents the options used to configure secret management +/// +[DataContract] +public sealed class SecretManagerOptions +{ + + /// + /// Gets the default directory where to locate secrets + /// + public static readonly string DefaultDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "secrets"); + + /// + /// Gets/sets the directory where secrets are located + /// + [Description("The directory where the runner's secrets are located")] + [DataMember(Order = 1, Name = "directory"), JsonPropertyOrder(1), JsonPropertyName("directory")] + public string? Directory { get; set; } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Extensions/AsyncObservableExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/AsyncObservableExtensions.cs new file mode 100644 index 0000000..aef1c45 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/AsyncObservableExtensions.cs @@ -0,0 +1,107 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for that support async callbacks +/// +public static class AsyncObservableExtensions +{ + + /// + /// Subscribes to the specified with async callbacks + /// + /// The type of the elements in the source sequence + /// The source sequence + /// Async action to invoke for each element in the observable sequence + /// Async action to invoke upon exceptional termination of the observable sequence + /// Async action to invoke upon graceful termination of the observable sequence + /// An used to unsubscribe from the observable sequence + public static IDisposable SubscribeAsync(this IObservable source, Func onNextAsync, Func? onErrorAsync = null, Func? onCompletedAsync = null) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(onNextAsync); + onErrorAsync ??= _ => Task.CompletedTask; + onCompletedAsync ??= () => Task.CompletedTask; + return source + .Select(item => Observable.FromAsync(() => onNextAsync(item))) + .Concat() + .Subscribe( + onNext: _ => { }, + onError: ex => + { + try { onErrorAsync(ex).ConfigureAwait(false).GetAwaiter().GetResult(); } + catch (Exception handlerEx) { Debug.Fail($"Unhandled exception in SubscribeAsync onError handler: {handlerEx}"); } + }, + onCompleted: () => + { + try { onCompletedAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } + catch (Exception handlerEx) { Debug.Fail($"Unhandled exception in SubscribeAsync onCompleted handler: {handlerEx}"); } + } + ); + } + + /// + /// Subscribes to the specified with async callbacks and a + /// + /// The type of the elements in the source sequence + /// The source sequence + /// Async action to invoke for each element in the observable sequence + /// Async action to invoke upon exceptional termination of the observable sequence + /// Async action to invoke upon graceful termination of the observable sequence + /// A + public static void SubscribeAsync(this IObservable source, Func onNextAsync, Func? onErrorAsync, Func? onCompletedAsync, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(onNextAsync); + onErrorAsync ??= _ => Task.CompletedTask; + onCompletedAsync ??= () => Task.CompletedTask; + source + .Select(item => Observable.FromAsync(() => onNextAsync(item))) + .Concat() + .Subscribe( + onNext: _ => { }, + onError: ex => + { + try { onErrorAsync(ex).ConfigureAwait(false).GetAwaiter().GetResult(); } + catch (Exception handlerEx) { Debug.Fail($"Unhandled exception in SubscribeAsync onError handler: {handlerEx}"); } + }, + onCompleted: () => + { + try { onCompletedAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } + catch (Exception handlerEx) { Debug.Fail($"Unhandled exception in SubscribeAsync onCompleted handler: {handlerEx}"); } + }, + token: cancellationToken + ); + } + + /// + /// Subscribes to the specified with an async onNext callback and a + /// + /// The type of the elements in the source sequence + /// The source sequence + /// Async action to invoke for each element in the observable sequence + /// A + public static void SubscribeAsync(this IObservable source, Func onNextAsync, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(onNextAsync); + source + .Select(item => Observable.FromAsync(() => onNextAsync(item))) + .Concat() + .Subscribe(cancellationToken); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Extensions/HttpClientExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/HttpClientExtensions.cs new file mode 100644 index 0000000..f4d4556 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/HttpClientExtensions.cs @@ -0,0 +1,41 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class HttpClientExtensions +{ + + /// + /// Configures the to use the specified authentication mechanism + /// + /// The to configure + /// An object that describes the authentication mechanism to use + /// The current + /// The , if any, that defines the authentication to configure + /// A + /// A new awaitable + public static async Task ConfigureAuthenticationAsync(this HttpClient httpClient, AuthenticationPolicyDefinition? policy, IServiceProvider serviceProvider, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + if (policy == null) return; + var authenticationHandler = serviceProvider.GetRequiredService(); + var authenticationResult = await authenticationHandler.HandleAsync(policy, workflow, cancellationToken).ConfigureAwait(false); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authenticationResult.Scheme, authenticationResult.Value); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Extensions/ServiceCollectionExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..d7e4cf4 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,103 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for +/// +public static class ServiceCollectionExtensions +{ + + /// + /// Adds and configures a Serverless Workflow runtime with default services + /// + /// The to configure + /// The application's + /// An used to configure the + /// The to use for all runtime services. Defaults to + /// The configured + public static IServiceCollection AddServerlessWorkflowRuntime(this IServiceCollection services, IConfiguration configuration, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) + { + var builder = new WorkflowRuntimeBuilder(services, configuration, lifetime); + services.AddMemoryCache(); + services.AddHttpClient(); + services.AddSingleton(); + RegisterDefaultServices(builder); + RegisterDefaultTaskExecutors(builder); + RegisterDefaultCallTaskExecutors(builder); + RegisterDefaultRunTaskExecutors(builder); + configure?.Invoke(builder); + return services; + } + + static void RegisterDefaultServices(WorkflowRuntimeBuilder builder) + { + builder.UseAuthenticationHandler(); + builder.UseCloudEventBus(); + builder.UseOAuth2TokenManager(); + builder.UseExternalResourceReader(); + builder.UseSecretsManager(); + builder.UseRuntimeExpressionEvaluator(); + builder.UseRuntimeExpressionEvaluator(); + builder.UseRuntimeExpressionEvaluatorProvider(); + builder.UseSchemaHandler(); + builder.UseSchemaHandler(); + builder.UseSchemaHandler(); + builder.UseSchemaHandlerProvider(); + builder.UseScriptExecutor(); + builder.UseScriptExecutor(); + builder.UseScriptExecutorProvider(); + builder.UseTaskExecutorFactory(); + builder.UseWorkflowProcessFactory(); + builder.UseWorkflowExecutionContextFactory(); + builder.UseTaskExecutionContextFactory(); + builder.UseWorkflowDefinitionStore(); + builder.UseWorkflowStateStore(); + builder.UseTaskStateStore(); + + } + + static void RegisterDefaultTaskExecutors(WorkflowRuntimeBuilder builder) + { + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + builder.UseTaskExecutor(); + } + + static void RegisterDefaultCallTaskExecutors(WorkflowRuntimeBuilder builder) + { + builder.UseCallTaskExecutor(Function.Http); + builder.UseCallTaskExecutor(Function.OpenApi); + builder.UseCallTaskExecutor(Function.AsyncApi); + builder.UseCallTaskExecutor(Function.Grpc); + } + + static void RegisterDefaultRunTaskExecutors(WorkflowRuntimeBuilder builder) + { + builder.UseRunTaskExecutor(ProcessType.Container); + builder.UseRunTaskExecutor(ProcessType.Shell); + builder.UseRunTaskExecutor(ProcessType.Script); + builder.UseRunTaskExecutor(ProcessType.Workflow); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Extensions/WorkflowDefinitionExtensions.cs b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/WorkflowDefinitionExtensions.cs new file mode 100644 index 0000000..bec12a0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Extensions/WorkflowDefinitionExtensions.cs @@ -0,0 +1,56 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk.Runtime; + +/// +/// Defines extensions for s +/// +public static class WorkflowDefinitionExtensions +{ + + /// + /// Gets the next to perform next, if any + /// + /// The extended + /// The to perform the next after + /// The next to perform next, if any + public static MapEntry? GetTaskAfter(this WorkflowDefinition workflow, ITaskInstance after) + { + ArgumentNullException.ThrowIfNull(after); + switch (after.Status == TaskStatus.Skipped ? FlowDirective.Continue : after.Next) + { + case FlowDirective.Continue: + var afterIndex = workflow.Do.Select(kvp => kvp.Key).ToList().IndexOf(after.Name!); + return workflow.Do.Skip(afterIndex + 1).FirstOrDefault(); + case FlowDirective.End: case FlowDirective.Exit: return default; + default: return new(after.Next!, workflow.Do[after.Next!]); + } + } + + /// + /// Attempts to get the next to perform next, if any + /// + /// The extended + /// The to perform the next after + /// The next to perform next, if any + /// A boolean indicating whether or not a next must be executed next + public static bool TryGetTaskAfter(this WorkflowDefinition workflow, ITaskInstance after, out MapEntry task) + { + ArgumentNullException.ThrowIfNull(after); + task = workflow.GetTaskAfter(after)!; + return task != null; + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/AuthenticationResult.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/AuthenticationResult.cs new file mode 100644 index 0000000..cd622e1 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/AuthenticationResult.cs @@ -0,0 +1,37 @@ +// 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.Runtime.Models; + +/// +/// Represents the result of an authentication operation +/// +/// The authentication scheme +/// The authentication value +[Description("Represents the result of an authentication operation")] +[DataContract] +public sealed record AuthenticationResult(string Scheme, string Value) + : IAuthenticationResult +{ + + /// + [Description("The OAUTH2 token")] + [DataMember(Order = 1, Name = "token"), JsonPropertyOrder(1), JsonPropertyName("token")] + public string Scheme { get; init; } = Scheme; + + /// + [Description("The OAUTH2 token")] + [DataMember(Order = 2, Name = "token"), JsonPropertyOrder(2), JsonPropertyName("token")] + public string Value { get; init; } = Value; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/CloudEvent.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/CloudEvent.cs new file mode 100644 index 0000000..415a525 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/CloudEvent.cs @@ -0,0 +1,147 @@ +// 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.Runtime.Models; + +/// +/// Represents a Cloud Event +/// +[Description("Represents a Cloud Event")] +[DataContract] +public sealed record CloudEvent + : ICloudEvent +{ + + /// + /// Gets the '1.0' version of the Cloud Event spec + /// + public const string DefaultVersion = "1.0"; + + /// + /// Gets/sets a string that uniquely identifies the cloud event in the scope of its source + /// + [Description("A string that uniquely identifies the cloud event in the scope of its source")] + [Required] + [DataMember(Order = 1, Name = "id"), JsonPropertyOrder(1), JsonPropertyName("id")] + public string Id { get; set; } = Guid.NewGuid().ToString("N"); + + /// + /// Gets/sets the version of the CloudEvents specification which the event uses. Defaults to + /// + [Description("The version of the CloudEvents specification which the event uses. Defaults to 1.0")] + [DefaultValue(DefaultVersion)] + [DataMember(Order = 2, Name = "specversion"), JsonPropertyOrder(2), JsonPropertyName("specversion")] + public string SpecVersion { get; set; } = DefaultVersion; + + /// + /// Gets/sets the date and time at which the event has been produced + /// + [Description("The date and time at which the event has been produced")] + [DataMember(Order = 3, Name = "time"), JsonPropertyOrder(3), JsonPropertyName("time")] + public DateTimeOffset? Time { get; set; } + + /// + /// Gets/sets the cloud event's type + /// + [Description("The cloud event's type")] + [Required] + [DataMember(Order = 4, Name = "source"), JsonPropertyOrder(4), JsonPropertyName("source")] + public required Uri Source { get; set; } + + /// + /// Gets/sets the cloud event's type + /// + [Description] + [Required] + [DataMember(Order = 5, Name = "type"), JsonPropertyOrder(5), JsonPropertyName("type")] + public required string Type { get; set; } + + /// + /// Gets/sets a value that describes the subject of the event in the context of the event producer. Used as correlation id by default. + /// + [Description("A value that describes the subject of the event in the context of the event producer. Used as correlation id by default.")] + [DataMember(Order = 6, Name = "subject"), JsonPropertyOrder(6), JsonPropertyName("subject")] + public string? Subject { get; set; } + + /// + /// Gets/sets the cloud event's data content type. Defaults to + /// + [Description("The cloud event's data content type. Defaults to application/json")] + [DefaultValue(MediaTypeNames.Application.Json)] + [DataMember(Order = 7, Name = "datacontenttype"), JsonPropertyOrder(7), JsonPropertyName("datacontenttype")] + public string? DataContentType { get; set; } = MediaTypeNames.Application.Json; + + /// + /// Gets/sets an that references the versioned schema of the event's data + /// + [Description("An URI that references the versioned schema of the event's data")] + [DataMember(Order = 8, Name = "dataschema"), JsonPropertyOrder(8), JsonPropertyName("dataschema")] + public Uri? DataSchema { get; set; } + + /// + /// Gets/sets the event's data, if any. Only used if the event has been formatted using the structured mode + /// + [Description("The event's data, if any. Only used if the event has been formatted using the structured mode")] + [DataMember(Order = 9, Name = "data"), JsonPropertyOrder(9), JsonPropertyName("data")] + public object? Data { get; set; } + + /// + /// Gets/sets the event's binary data, encoded in base 64. Only used if the event has been formatted using the binary mode + /// + [Description("The event's binary data, encoded in base 64. Only used if the event has been formatted using the binary mode")] + [DataMember(Order = 10, Name = "database64"), JsonPropertyOrder(10), JsonPropertyName("database64")] + public string? DataBase64 { get; set; } + + /// + /// Gets/sets an that contains the event's extension attributes + /// + [DataMember(Order = 11, Name = "extensionAttributes"), JsonExtensionData] + public IDictionary? ExtensionAttributes { get; set; } + + /// + /// Gets/sets the specified attribute + /// + /// The name of the attribute to set + /// The attribute's value + public object? this[string attributeName] => GetAttribute(attributeName); + + /// + /// Gets the specified attribute + /// + /// The name of the attribute to get + /// The value of the specified attribute + public object? GetAttribute(string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + switch (name) + { + case CloudEventAttributes.Id: return Id; + case CloudEventAttributes.SpecVersion: return SpecVersion; + case CloudEventAttributes.Time: return Time; + case CloudEventAttributes.Source: return Source; + case CloudEventAttributes.Type: return Type; + case CloudEventAttributes.Subject: return Subject; + case CloudEventAttributes.DataContentType: return DataContentType; + case CloudEventAttributes.DataSchema: return DataSchema; + case CloudEventAttributes.Data: return Data; + case CloudEventAttributes.DataBase64: return DataBase64; + default: + if (ExtensionAttributes?.TryGetValue(name, out var value) == true) return value; + else return null; + } + } + + /// + public override string ToString() => Id; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/CorrelationContext.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/CorrelationContext.cs new file mode 100644 index 0000000..2a44cc8 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/CorrelationContext.cs @@ -0,0 +1,53 @@ +// 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.Runtime.Models; + +/// +/// Represents an object used to describe the context of a correlation +/// +[DataContract] +public sealed record CorrelationContext +{ + + /// + /// Gets/sets the context's unique identifier + /// + [DataMember(Name = "id", Order = 1), JsonPropertyName("id"), JsonPropertyOrder(1)] + public required string Id { get; init; } + + /// + /// Gets/sets the context's status + /// + [DataMember(Name = "status", Order = 2), JsonPropertyName("status"), JsonPropertyOrder(2)] + public string Status { get; init; } = CorrelationContextStatus.Active; + + /// + /// Gets a key/value mapping of the context's correlation keys + /// + [DataMember(Name = "keys", Order = 3), JsonPropertyName("keys"), JsonPropertyOrder(3)] + public EquatableDictionary Keys { get; init; } = []; + + /// + /// Gets a key/value mapping of all correlated events, with the key being the index of the matched correlation filter + /// + [DataMember(Name = "events", Order = 4), JsonPropertyName("events"), JsonPropertyOrder(4)] + public EquatableDictionary Events { get; init; } = []; + + /// + /// Gets the offset that serves as the index of the event being processed by the consumer, if streaming has been enabled for the correlation associated with the context. + /// + [DataMember(Name = "offset", Order = 5), JsonPropertyName("offset"), JsonPropertyOrder(5)] + public uint? Offset { get; init; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/OAuth2Token.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/OAuth2Token.cs new file mode 100644 index 0000000..c4ee06b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/OAuth2Token.cs @@ -0,0 +1,80 @@ +// 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.Runtime.Models; + +/// +/// Represents an OAUTH2 token +/// +[Description("Represents an OAUTH2 token")] +[DataContract] +public sealed record OAuth2Token + : IOAuth2Token +{ + + /// + /// Gets the UTC date and time at which the has been created + /// + [Description("The UTC date and time at which the OAuth2Token has been created")] + [DataMember(Order = 1, Name = "createdAt"), JsonPropertyOrder(1), JsonPropertyName("createdAt")] + public DateTime CreatedAt { get; init; } = DateTime.UtcNow; + + /// + /// Gets the OAUTH2 token type + /// + [Description("The OAUTH2 token type")] + [DataMember(Order = 2, Name = "tokenType"), JsonPropertyOrder(2), JsonPropertyName("tokenType")] + public string? TokenType { get; init; } + + /// + /// Gets the OAUTH2 token id + /// + [Description("The OAUTH2 token id")] + [DataMember(Order = 3, Name = "tokenId"), JsonPropertyOrder(3), JsonPropertyName("tokenId")] + public string? TokenId { get; init; } + + /// + /// Gets the OAUTH2 access token + /// + [Description("The OAUTH2 access token")] + [DataMember(Order = 4, Name = "accessToken"), JsonPropertyOrder(4), JsonPropertyName("accessToken")] + public string? AccessToken { get; init; } + + /// + /// Gets the OAUTH2 refresh token + /// + [Description("The OAUTH2 refresh token")] + [DataMember(Order = 5, Name = "refreshToken"), JsonPropertyOrder(5), JsonPropertyName("refreshToken")] + public string? RefreshToken { get; init; } + + /// + /// Gets the Time To Live, in seconds + /// + [Description("The OAuth2Token Time To Live, in seconds")] + [DataMember(Order = 6, Name = "ttl"), JsonPropertyOrder(6), JsonPropertyName("ttl")] + public int Ttl { get; init; } + + /// + /// Gets the UTC date and time at which the expires + /// + [Description("The UTC date and time at which the OAuth2Token expires")] + [DataMember(Order = 7, Name = "expiresAt"), JsonPropertyOrder(7), JsonPropertyName("expiresAt")] + public DateTime? ExpiresAt { get; init; } + + /// + /// Gets a boolean indicating whether or not the has expired + /// + [IgnoreDataMember, JsonIgnore] + public bool HasExpired => ExpiresAt.HasValue ? DateTime.UtcNow > ExpiresAt : DateTime.UtcNow > CreatedAt.Add(TimeSpan.FromSeconds(Ttl)); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/SchemaValidationResult.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/SchemaValidationResult.cs new file mode 100644 index 0000000..ffed17d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/SchemaValidationResult.cs @@ -0,0 +1,66 @@ +// 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.Runtime.Models; + +/// +/// Represents an object used to describe the result of a schema validation operation +/// +[Description("Represents an object used to describe the result of a schema validation operation")] +[DataContract] +public sealed record SchemaValidationResult + : ISchemaValidationResult +{ + + /// + [Description("Indicates whether the validation operation was successful or not")] + [DataMember(Order = 1, Name = "isValid"), JsonPropertyOrder(1), JsonPropertyName("isValid")] + public required bool IsValid { get; init; } + + /// + [Description("A mapping containing validation errors, if any")] + [DataMember(Name = "errors", Order = 2), JsonPropertyOrder(2), JsonPropertyName("errors")] + public IReadOnlyDictionary>? Errors { get; init; } + + /// + /// Creates a new indicating a successful validation operation + /// + /// A new indicating a successful validation operation + public static SchemaValidationResult Succeeded() => new() + { + IsValid = true + }; + + /// + /// Creates a new indicating a failed validation operation + /// + /// A mapping containing validation errors, if any + /// A new indicating a failed validation operation + public static SchemaValidationResult Failed(IReadOnlyDictionary> errors) => new() + { + IsValid = false, + Errors = errors + }; + + /// + /// Creates a new indicating a failed validation operation + /// + /// A mapping containing validation errors, if any + /// A new indicating a failed validation operation + public static SchemaValidationResult Failed(IEnumerable>> errors) => new() + { + IsValid = false, + Errors = errors.ToDictionary(e => e.Key, e => e.Value.ToArray() as IReadOnlyList) + }; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskInstance.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskInstance.cs new file mode 100644 index 0000000..6feae2d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskInstance.cs @@ -0,0 +1,203 @@ +// 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.Runtime.Models; + +/// +/// Represents the default implementation of the interface +/// +[DataContract] +public sealed class TaskInstance + : ITaskInstance +{ + + List? runs; + List? retries; + + /// + [DataMember(Order = 1, Name = "id"), JsonPropertyOrder(1), JsonPropertyName("id")] + public string Id { get; init; } = Guid.NewGuid().ToString("N"); + + /// + [DataMember(Order = 2, Name = "workflowId"), JsonPropertyOrder(2), JsonPropertyName("workflowId")] + public required string WorkflowId { get; init; } + + /// + [DataMember(Order = 3, Name = "name"), JsonPropertyOrder(3), JsonPropertyName("name")] + public string? Name { get; init; } + + /// + [DataMember(Order = 4, Name = "reference"), JsonPropertyOrder(4), JsonPropertyName("reference")] + public required JsonPointer Reference { get; init; } + + /// + [DataMember(Order = 5, Name = "isExtension"), JsonPropertyOrder(5), JsonPropertyName("isExtension")] + public bool IsExtension { get; init; } + + /// + [DataMember(Order = 6, Name = "parentId"), JsonPropertyOrder(6), JsonPropertyName("parentId")] + public string? ParentId { get; init; } + + /// + [DataMember(Order = 7, Name = "createdAt"), JsonPropertyOrder(7), JsonPropertyName("createdAt")] + public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.Now; + + /// + [DataMember(Order = 8, Name = "startedAt"), JsonPropertyOrder(8), JsonPropertyName("startedAt")] + public DateTimeOffset? StartedAt { get; private set; } + + /// + [DataMember(Order = 9, Name = "endedAt"), JsonPropertyOrder(9), JsonPropertyName("endedAt")] + public DateTimeOffset? EndedAt { get; private set; } + + /// + [DataMember(Order = 10, Name = "status"), JsonPropertyOrder(10), JsonPropertyName("status")] + public string? Status { get; private set; } + + /// + [DataMember(Order = 11, Name = "statusReason"), JsonPropertyOrder(11), JsonPropertyName("statusReason")] + public string? StatusReason { get; private set; } + + /// + [DataMember(Order = 12, Name = "error"), JsonPropertyOrder(12), JsonPropertyName("error")] + public Error? Error { get; private set; } + + /// + [DataMember(Order = 13, Name = "input"), JsonPropertyOrder(13), JsonPropertyName("input")] + public required JsonNode Input { get; init; } + + /// + [DataMember(Order = 14, Name = "contextData"), JsonPropertyOrder(14), JsonPropertyName("contextData")] + public JsonNode? Output { get; private set; } + + /// + [DataMember(Order = 15, Name = "output"), JsonPropertyOrder(15), JsonPropertyName("output")] + public string? Next { get; private set; } + + /// + /// Gets a collection containing the task's runs + /// + [DataMember(Order = 16, Name = "runs"), JsonPropertyOrder(16), JsonPropertyName("runs")] + public IReadOnlyCollection? Runs => runs; + + /// + /// Gets a collection containing the task's retry attempts + /// + [DataMember(Order = 17, Name = "retries"), JsonPropertyOrder(17), JsonPropertyName("retries")] + public IReadOnlyCollection? Retries => retries; + + IReadOnlyCollection? ITaskInstance.Runs => Runs; + + IReadOnlyCollection? ITaskInstance.Retries => Retries; + + /// + public Task StartAsync(CancellationToken cancellationToken = default) + { + Status = TaskStatus.Running; + StartedAt = DateTimeOffset.Now; + runs ??= []; + runs.Add(new() + { + StartedAt = StartedAt.Value + }); + return Task.CompletedTask; + } + + /// + public Task SuspendAsync(CancellationToken cancellationToken = default) + { + Status = TaskStatus.Suspended; + var run = runs!.Last(); + run.EndedAt = DateTimeOffset.Now; + run.Outcome = Status; + return Task.CompletedTask; + } + + /// + public Task ResumeAsync(CancellationToken cancellationToken = default) + { + Status = TaskStatus.Running; + runs ??= []; + runs.Add(new() + { + StartedAt = DateTimeOffset.Now + }); + return Task.CompletedTask; + } + + /// + public Task RetryAsync(Error cause, CancellationToken cancellationToken = default) + { + retries ??= []; + retries.Add(new() + { + Number = (uint)retries.Count + 1, + Cause = cause + }); + Status = TaskStatus.Running; + StartedAt ??= DateTimeOffset.Now; + runs ??= []; + runs.Add(new() + { + StartedAt = DateTimeOffset.Now + }); + return Task.CompletedTask; + } + + /// + public Task SetErrorAsync(Error error, CancellationToken cancellationToken = default) + { + Status = TaskStatus.Faulted; + EndedAt = DateTimeOffset.Now; + var run = runs?.LastOrDefault(); + if (run != null) + { + run.EndedAt = DateTimeOffset.Now; + run.Outcome = Status; + } + return Task.CompletedTask; + } + + /// + public Task SkipAsync(JsonNode? result, string next, CancellationToken cancellationToken = default) + { + Status = TaskStatus.Skipped; + EndedAt = DateTimeOffset.Now; + Output = result; + Next = next; + return Task.CompletedTask; + } + + /// + public Task SetOutputAsync(JsonNode? output, string next, CancellationToken cancellationToken = default) + { + Status = TaskStatus.Completed; + EndedAt = DateTimeOffset.Now; + Output = output; + Next = next; + var run = runs?.LastOrDefault(); + run?.EndedAt = EndedAt.Value; + return Task.CompletedTask; + } + + /// + public Task CancelAsync(CancellationToken cancellationToken = default) + { + Status = TaskStatus.Cancelled; + EndedAt = DateTimeOffset.Now; + var run = runs?.LastOrDefault(); + run?.EndedAt = EndedAt.Value; + return Task.CompletedTask; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskLifeCycleEvent.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskLifeCycleEvent.cs new file mode 100644 index 0000000..d12b8cd --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskLifeCycleEvent.cs @@ -0,0 +1,31 @@ +// 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.Runtime.Models; + +/// +/// Represents the default implementation of the +/// +/// The 's type +/// The 's data, if any +public sealed record TaskLifeCycleEvent(string Type, object? Data = null) + : ITaskLifeCycleEvent +{ + + /// + public string Type { get; init; } = Type; + + /// + public object? Data { get; init; } = Data; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskRetryAttempt.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskRetryAttempt.cs new file mode 100644 index 0000000..8036978 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskRetryAttempt.cs @@ -0,0 +1,44 @@ +// 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.Runtime.Models; + +/// +/// Represents an object used to describe a retry attempt +/// +[DataContract] +public sealed class TaskRetryAttempt + : ITaskRetryAttempt +{ + + /// + /// Gets/sets the retry attempt number + /// + [Required] + [DataMember(Name = "number", Order = 1), JsonPropertyName("number"), JsonPropertyOrder(1)] + public required uint Number { get; set; } + + /// + /// Gets/sets the date and time at which the retry attempt was performed + /// + [DataMember(Name = "time", Order = 2), JsonPropertyName("time"), JsonPropertyOrder(2)] + public DateTimeOffset Time { get; set; } = DateTimeOffset.Now; + + /// + /// Gets/sets the that is the cause of the try attempt + /// + [Required] + [DataMember(Name = "cause", Order = 3), JsonPropertyName("cause"), JsonPropertyOrder(3)] + public required Error Cause { get; set; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskRun.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskRun.cs new file mode 100644 index 0000000..40125ba --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/TaskRun.cs @@ -0,0 +1,42 @@ +// 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.Runtime.Models; + +/// +/// Represents a single run of a task, including start and end times +/// +[DataContract] +public sealed class TaskRun + : ITaskRun +{ + + /// + /// Gets/sets the start time of the run + /// + [DataMember(Name = "startedAt", Order = 1), JsonPropertyName("startedAt"), JsonPropertyOrder(1)] + public required DateTimeOffset StartedAt { get; set; } + + /// + /// Gets/sets the end time of the run, if the task has completed + /// + [DataMember(Name = "endedAt", Order = 2), JsonPropertyName("endedAt"), JsonPropertyOrder(2)] + public DateTimeOffset? EndedAt { get; set; } + + /// + /// Gets/sets the run's outcome or, in other words, the status of the task when the run ended + /// + [DataMember(Name = "outcome", Order = 3), JsonPropertyName("outcome"), JsonPropertyOrder(2)] + public string? Outcome { get; set; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowInstance.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowInstance.cs new file mode 100644 index 0000000..b8db382 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowInstance.cs @@ -0,0 +1,146 @@ +// 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.Runtime.Models; + +/// +/// Represents the default implementation of the interface +/// +[DataContract] +public sealed class WorkflowInstance + : IWorkflowInstance +{ + + List? runs; + + /// + [DataMember(Order = 1, Name = "id"), JsonPropertyOrder(1), JsonPropertyName("id")] + public string Id { get; init; } = Guid.NewGuid().ToString("N"); + + /// + [DataMember(Order = 2, Name = "definition"), JsonPropertyOrder(2), JsonPropertyName("definition")] + public required WorkflowDefinitionReference Definition { get; init; } + + /// + [DataMember(Order = 3, Name = "status"), JsonPropertyOrder(3), JsonPropertyName("status")] + public string Status { get; private set; } = WorkflowStatus.Pending; + + /// + [DataMember(Order = 4, Name = "createdAt"), JsonPropertyOrder(4), JsonPropertyName("createdAt")] + public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.Now; + + /// + [DataMember(Order = 5, Name = "startedAt"), JsonPropertyOrder(5), JsonPropertyName("startedAt")] + public DateTimeOffset? StartedAt { get; private set; } + + /// + [DataMember(Order = 6, Name = "endedAt"), JsonPropertyOrder(6), JsonPropertyName("endedAt")] + public DateTimeOffset? EndedAt { get; private set; } + + /// + [DataMember(Order = 7, Name = "input"), JsonPropertyOrder(7), JsonPropertyName("input")] + public JsonObject? Input { get; init; } + + /// + [DataMember(Order = 8, Name = "contextData"), JsonPropertyOrder(8), JsonPropertyName("contextData")] + public JsonObject ContextData { get; private set; } = []; + + /// + [DataMember(Order = 9, Name = "output"), JsonPropertyOrder(9), JsonPropertyName("output")] + public JsonNode? Output { get; private set; } + + /// + [DataMember(Order = 10, Name = "error"), JsonPropertyOrder(10), JsonPropertyName("error")] + public Error? Error { get; private set; } + + /// + /// Gets a collection containing the workflow's runs + /// + [DataMember(Order = 11, Name = "runs"), JsonPropertyOrder(11), JsonPropertyName("runs")] + public IReadOnlyCollection? Runs => runs; + + IReadOnlyCollection? IWorkflowInstance.Runs => Runs; + + /// + public Task StartAsync(CancellationToken cancellationToken = default) + { + Status = WorkflowStatus.Running; + StartedAt = DateTimeOffset.Now; + runs ??= []; + runs.Add(new() { StartedAt = DateTimeOffset.Now }); + return Task.CompletedTask; + } + + /// + public Task SuspendAsync(CancellationToken cancellationToken = default) + { + Status = WorkflowStatus.Suspended; + var run = runs?.LastOrDefault(); + run?.EndedAt = DateTimeOffset.Now; + return Task.CompletedTask; + } + + /// + public Task ResumeAsync(CancellationToken cancellationToken = default) + { + Status = WorkflowStatus.Running; + runs ??= []; + runs.Add(new() + { + StartedAt = DateTimeOffset.Now + }); + return Task.CompletedTask; + } + + /// + public Task SetOutputAsync(JsonNode? output, CancellationToken cancellationToken = default) + { + Status = WorkflowStatus.Completed; + EndedAt = DateTimeOffset.Now; + Output = output; + var run = runs?.LastOrDefault(); + run?.EndedAt = DateTimeOffset.Now; + return Task.CompletedTask; + } + + /// + public Task SetContextDataAsync(JsonObject contextData, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contextData); + ContextData = contextData; + return Task.CompletedTask; + } + + /// + public Task SetErrorAsync(Error error, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(error); + Status = WorkflowStatus.Faulted; + EndedAt = DateTimeOffset.Now; + Error = error; + var run = runs?.LastOrDefault(); + run?.EndedAt = DateTimeOffset.Now; + return Task.CompletedTask; + } + + /// + public Task CancelAsync(CancellationToken cancellationToken = default) + { + Status = WorkflowStatus.Cancelled; + EndedAt = DateTimeOffset.Now; + var run = runs?.LastOrDefault(); + run?.EndedAt = DateTimeOffset.Now; + return Task.CompletedTask; + } + +} diff --git a/src/ServerlessWorkflow.Sdk/Validation/ValidationResult.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowLifeCycleEvent.cs similarity index 55% rename from src/ServerlessWorkflow.Sdk/Validation/ValidationResult.cs rename to src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowLifeCycleEvent.cs index 5c5266a..f1bcc13 100644 --- a/src/ServerlessWorkflow.Sdk/Validation/ValidationResult.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowLifeCycleEvent.cs @@ -11,22 +11,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Validation; +namespace ServerlessWorkflow.Sdk.Runtime.Models; /// -/// Represents the result of a validation attempt +/// Represents the default implementation of the /// -[DataContract] -public record ValidationResult - : IValidationResult +/// The 's type +/// The 's data, if any +public sealed record WorkflowLifeCycleEvent(string Type, object? Data = null) + : IWorkflowLifeCycleEvent { /// - [DataMember(Name = "isValid", Order = 1), JsonPropertyName("isValid"), JsonPropertyOrder(1), YamlMember(Alias = "isValid", Order = 1)] - public virtual bool IsValid => this.Errors?.Count < 1; + public string Type { get; init; } = Type; /// - [DataMember(Name = "errors", Order = 2), JsonPropertyName("errors"), JsonPropertyOrder(2), YamlMember(Alias = "errors", Order = 2)] - public virtual IReadOnlyCollection? Errors { get; set; } + public object? Data { get; init; } = Data; } diff --git a/src/ServerlessWorkflow.Sdk/Models/ContextDataModelDefinition.cs b/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowRun.cs similarity index 52% rename from src/ServerlessWorkflow.Sdk/Models/ContextDataModelDefinition.cs rename to src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowRun.cs index 274a53c..5d83864 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ContextDataModelDefinition.cs +++ b/src/ServerlessWorkflow.Sdk.Runtime/Models/WorkflowRun.cs @@ -11,25 +11,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Models; +namespace ServerlessWorkflow.Sdk.Runtime.Models; /// -/// Represents the definition of a data context +/// Represents a single run of a workflow, including start and end times /// [DataContract] -public record ContextDataModelDefinition +public sealed class WorkflowRun + : IWorkflowRun { /// - /// Gets/sets the schema, if any, that defines and describes the context data + /// Gets/sets the start time of the run /// - [DataMember(Name = "schema", Order = 1), JsonPropertyName("schema"), JsonPropertyOrder(1), YamlMember(Alias = "schema", Order = 1)] - public virtual SchemaDefinition? Schema { get; set; } + [DataMember(Name = "startedAt", Order = 1), JsonPropertyName("startedAt"), JsonPropertyOrder(1)] + public required DateTimeOffset StartedAt { get; set; } /// - /// Gets/sets a runtime expression, if any, used to export specific data to the context data + /// Gets/sets the end time of the run, if the workflow has completed /// - [DataMember(Name = "as", Order = 3), JsonPropertyName("as"), JsonPropertyOrder(3), YamlMember(Alias = "as", Order = 3)] - public virtual object? As { get; set; } + [DataMember(Name = "endedAt", Order = 2), JsonPropertyName("endedAt"), JsonPropertyOrder(2)] + public DateTimeOffset? EndedAt { get; set; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/README.md b/src/ServerlessWorkflow.Sdk.Runtime/README.md new file mode 100644 index 0000000..48801d6 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/README.md @@ -0,0 +1,65 @@ +# ServerlessWorkflow.Sdk.Runtime + +A .NET runtime for executing [Serverless Workflow DSL](https://github.com/serverlessworkflow/specification/blob/main/dsl.md) definitions. + +This package ships the services required to load, schedule, and execute workflow definitions, including task executors, expression evaluation (JQ and JavaScript via Jint), authentication, secret management, schema validation (JSON Schema, Avro, XML), and CloudEvents integration. + +## Installation + +```bash +dotnet add package ServerlessWorkflow.Sdk.Runtime +``` + +## Usage + +Register the runtime with `Microsoft.Extensions.DependencyInjection`: + +```csharp +using ServerlessWorkflow.Sdk.Runtime; + +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.AddServerlessWorkflowRuntime(builder.Configuration); +``` + +Then resolve and use `IWorkflowRuntime` to run a workflow: + +```csharp +var runtime = host.Services.GetRequiredService(); + +var process = await runtime.RunAsync(definition, input, executionOptions: null, cancellationToken); +await process.WaitAsync(cancellationToken); +``` + +You can also run a workflow by reference: + +```csharp +var process = await runtime.RunAsync( + @namespace: "samples", + name: "fake-workflow", + version: "0.1.0", + input: input, + executionOptions: null, + cancellationToken); +``` + +## Key services + +| Service | Role | +|---|---| +| `IWorkflowRuntime` | Entry point for running workflows. | +| `IWorkflowDefinitionStore` | Persists and retrieves workflow definitions. | +| `IWorkflowStateStore` | Persists workflow execution state. | +| `IWorkflowProcessFactory` | Creates `IWorkflowProcess` instances for execution. | +| `ITaskExecutor` / `TaskExecutorFactory` | Execute individual DSL tasks. | +| `RuntimeExpressionEvaluator` | Evaluates runtime expressions (JQ / JavaScript). | +| `AuthenticationHandler` / `OAuth2TokenManager` | Handles workflow authentication. | +| `SecretsManager` | Resolves secret references at runtime. | +| `ICloudEventBus` | Publishes and subscribes to CloudEvents. | + +## Related packages + +- [ServerlessWorkflow.Sdk](../ServerlessWorkflow.Sdk) — core DSL models +- [ServerlessWorkflow.Sdk.Builders](../ServerlessWorkflow.Sdk.Builders) — fluent builders +- [ServerlessWorkflow.Sdk.Runtime.Cli](../ServerlessWorkflow.Sdk.Runtime.Cli) — `swf` command-line runner +- [Project root](../../README.md) diff --git a/src/ServerlessWorkflow.Sdk.Runtime/RuntimeErrorException.cs b/src/ServerlessWorkflow.Sdk.Runtime/RuntimeErrorException.cs new file mode 100644 index 0000000..e49c6aa --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/RuntimeErrorException.cs @@ -0,0 +1,29 @@ +// 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.Runtime; + +/// +/// Represents the thrown when an has been raised during the execution of a workflow +/// +/// The that has been raised +public sealed class RuntimeErrorException(Error error) + : Exception +{ + + /// + /// Gets the that has been raised + /// + public Error Error { get; } = error; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Serialization/Json/JsonSerializationContext.cs b/src/ServerlessWorkflow.Sdk.Runtime/Serialization/Json/JsonSerializationContext.cs new file mode 100644 index 0000000..0deba3e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Serialization/Json/JsonSerializationContext.cs @@ -0,0 +1,38 @@ +// 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.Runtime.Serialization.Json; + +/// +/// Represents the source generation context for JSON serialization and deserialization of the Serverless Workflow SDK runtime types. +/// +[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(AuthenticationResult))] +[JsonSerializable(typeof(CloudEvent))] +[JsonSerializable(typeof(CorrelationContext))] +[JsonSerializable(typeof(OAuth2Token))] +[JsonSerializable(typeof(SchemaValidationResult))] +[JsonSerializable(typeof(TaskLifeCycleEvent))] +[JsonSerializable(typeof(TaskRetryAttempt))] +[JsonSerializable(typeof(TaskRun))] +[JsonSerializable(typeof(TaskInstance))] +[JsonSerializable(typeof(WorkflowLifeCycleEvent))] +[JsonSerializable(typeof(WorkflowRun))] +[JsonSerializable(typeof(WorkflowInstance))] +public partial class JsonSerializationContext + : JsonSerializerContext +{ + + + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/ServerlessWorkflow.Sdk.Runtime.csproj b/src/ServerlessWorkflow.Sdk.Runtime/ServerlessWorkflow.Sdk.Runtime.csproj new file mode 100644 index 0000000..ba8a6ff --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/ServerlessWorkflow.Sdk.Runtime.csproj @@ -0,0 +1,59 @@ + + + + net10.0 + enable + enable + true + true + 1.0.2 + $(VersionPrefix) + $(VersionPrefix) + en + true + True + true + Serverless Workflow SDK - Runtime + Contains services used to execute ServerlessWorkflow workflow definitions + serverless-workflow;serverless;workflow;dsl;sdk;runtime + true + Apache-2.0 + README.md + Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. + https://github.com/serverlessworkflow/sdk-net + https://github.com/serverlessworkflow/sdk-net + git + embedded + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ + True + + + + diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/AuthenticationHandler.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/AuthenticationHandler.cs new file mode 100644 index 0000000..435633b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/AuthenticationHandler.cs @@ -0,0 +1,83 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The service used to perform logging +/// The service used to manage secrets +/// The service used to manage OAuth2 tokens +public sealed class AuthenticationHandler(ILogger logger, ISecretsManager secretsManager, IOAuth2TokenManager oauth2TokenManager) + : IAuthenticationHandler +{ + + /// + public async Task HandleAsync(AuthenticationPolicyDefinition policy, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(policy); + if (!string.IsNullOrWhiteSpace(policy.Use)) + { + if (workflow?.Use?.Authentications?.TryGetValue(policy.Use, out AuthenticationPolicyDefinition? referencedAuthentication) != true || referencedAuthentication == null) throw new NullReferenceException($"Failed to find the specified authentication policy '{policy.Use}'"); + else policy = referencedAuthentication; + } + var isSecretBased = policy.TryGetBaseSecret(out var secretName); + JsonNode? authenticationProperties = null; + if (isSecretBased && !string.IsNullOrWhiteSpace(secretName)) + { + logger.LogDebug("Authentication is secret based"); + var secrets = await secretsManager.GetAsync(cancellationToken).ConfigureAwait(false); + if (!secrets.TryGetValue(secretName, out authenticationProperties) || authenticationProperties is null) + { + logger.LogError("Failed to resolve the specified secret '{secret}'", secretName); + throw new NullReferenceException($"Failed to resolve the specified secret '{secretName}'"); + } + logger.LogDebug("Authentication secret loaded"); + } + string scheme, parameter; + switch (policy.Scheme) + { + case AuthenticationScheme.Basic: + if (policy.Basic == null) throw new Exception("Missing or invalid configuration of the specified authentication scheme"); + var basic = authenticationProperties is null ? policy.Basic : JsonSerializer.Deserialize(authenticationProperties, Sdk.Serialization.Json.JsonSerializationContext.Default.BasicAuthenticationSchemeDefinition)!; + scheme = AuthenticationScheme.Basic; + parameter = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{basic.Username}:{basic.Password}")); + break; + case AuthenticationScheme.Bearer: + if (policy.Bearer == null) throw new Exception("Missing or invalid configuration of the specified authentication scheme"); + var bearer = authenticationProperties is null ? policy.Bearer : JsonSerializer.Deserialize(authenticationProperties, Sdk.Serialization.Json.JsonSerializationContext.Default.BearerAuthenticationSchemeDefinition)!; + scheme = AuthenticationScheme.Bearer; + parameter = bearer.Token ?? throw new Exception("The Bearer token must be set"); + break; + case AuthenticationScheme.OAuth2: + if (policy.OAuth2 == null) throw new Exception("Missing or invalid configuration of the specified authentication scheme"); + var oauth2 = authenticationProperties is null ? policy.OAuth2 : JsonSerializer.Deserialize(authenticationProperties, Sdk.Serialization.Json.JsonSerializationContext.Default.OAuth2AuthenticationSchemeDefinition)!; + scheme = AuthenticationScheme.Bearer; + var token = await oauth2TokenManager.GetTokenAsync(oauth2, cancellationToken).ConfigureAwait(false) ?? throw new NullReferenceException("Failed to generate an OAUTH2 token"); + parameter = token.AccessToken!; + break; + case AuthenticationScheme.OpenIDConnect: + if (policy.Oidc == null) throw new Exception("Missing or invalid configuration of the specified authentication scheme"); + var oidc = authenticationProperties is null ? policy.Oidc : JsonSerializer.Deserialize(authenticationProperties, Sdk.Serialization.Json.JsonSerializationContext.Default.OpenIDConnectSchemeDefinition)!; + scheme = AuthenticationScheme.Bearer; + token = await oauth2TokenManager.GetTokenAsync(oidc, cancellationToken).ConfigureAwait(false) ?? throw new NullReferenceException("Failed to generate an OIDC token"); + parameter = token.AccessToken!; + break; + default: + throw new NotSupportedException($"The specified authentication schema '{policy.Scheme}' is not supported"); + } + return new AuthenticationResult(scheme, parameter); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/AvroSchemaHandler.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/AvroSchemaHandler.cs new file mode 100644 index 0000000..3408cd0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/AvroSchemaHandler.cs @@ -0,0 +1,143 @@ +// 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 Avro; +using Avro.Generic; +using Avro.IO; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents the implementation used to handle Avro schemas +/// +/// The service used to read external resources +public sealed class AvroSchemaHandler(IExternalResourceReader externalResourceReader) + : ISchemaHandler +{ + + /// + public bool Supports(string format) => format.Equals(SchemaFormat.Avro, StringComparison.OrdinalIgnoreCase); + + /// + public async Task ValidateAsync(JsonNode graph, SchemaDefinition schema, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(graph); + ArgumentNullException.ThrowIfNull(schema); + if (!Supports(schema.Format)) throw new NotSupportedException($"The specified schema format '{schema.Format}' is not supported in this context"); + Schema avroSchema; + try + { + var json = string.Empty; + if (schema.Resource == null) + { + if (schema.Document == null) throw new InvalidOperationException("The specified schema definition does not contain a valid resource reference or an embedded document"); + json = schema.Document.Match + ( + jsonObject => JsonSerializer.Serialize(schema.Document, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonObject), + str => str + ); + } + else + { + using var stream = await externalResourceReader.ReadAsync(schema.Resource, cancellationToken: cancellationToken).ConfigureAwait(false); + using var streamReader = new StreamReader(stream); + json = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + } + avroSchema = Schema.Parse(json); + } + catch (Exception ex) + { + return SchemaValidationResult.Failed(new Dictionary>() + { + [string.Empty] = [$"An error occurred while parsing the specified schema: {ex}"] + }); + } + byte[] avroData; + try + { + using var memoryStream = new MemoryStream(); + var writer = new BinaryEncoder(memoryStream); + var datumWriter = new GenericDatumWriter(avroSchema); + var avroObject = ConvertToAvroCompatible(graph, avroSchema); + datumWriter.Write(avroObject!, writer); + avroData = memoryStream.ToArray(); + } + catch (Exception ex) + { + return SchemaValidationResult.Failed(new Dictionary>() + { + [string.Empty] = [$"An error occurred while serializing the specified data to Avro: {ex}"] + }); + } + try + { + using var memoryStream = new MemoryStream(avroData); + var reader = new BinaryDecoder(memoryStream); + var datumReader = new GenericDatumReader(avroSchema, avroSchema); + datumReader.Read(null!, reader); + return SchemaValidationResult.Succeeded(); + } + catch (Exception ex) + { + return SchemaValidationResult.Failed(new Dictionary>() + { + [string.Empty] = [ex.Message] + }); + } + } + + static object? ConvertToAvroCompatible(JsonNode? graph, Schema schema) + { + if (graph is null) return null; + return schema switch + { + RecordSchema recordSchema => ConvertToGenericRecord(graph, recordSchema), + ArraySchema arraySchema => ConvertToArray(graph, arraySchema), + MapSchema mapSchema => ConvertToMap(graph, mapSchema), + UnionSchema unionSchema => ConvertToUnion(graph, unionSchema), + FixedSchema or EnumSchema or PrimitiveSchema => JsonSerializer.Deserialize(graph), + _ => throw new NotSupportedException($"Unsupported schema type: {schema.GetType().Name}") + }; + } + + static GenericRecord? ConvertToGenericRecord(JsonNode? graph, RecordSchema recordSchema) + { + if (graph is null || graph is not JsonObject jsonObject) return null; + var record = new GenericRecord(recordSchema); + foreach (var field in recordSchema.Fields) if (jsonObject.TryGetPropertyValue(field.Name, out var value)) record.Add(field.Name, ConvertToAvroCompatible(value, field.Schema)); + return record; + } + + static List? ConvertToArray(JsonNode? graph, ArraySchema arraySchema) + { + if (graph is null || graph is not JsonArray jsonArray) return null; + return [.. jsonArray.Select(item => ConvertToAvroCompatible(item, arraySchema.ItemSchema))]; + } + + static Dictionary? ConvertToMap(JsonNode? graph, MapSchema mapSchema) + { + if (graph is null || graph is not JsonObject jsonObject) return null; + return jsonObject.ToDictionary(kvp => kvp.Key, kvp => ConvertToAvroCompatible(kvp.Value, mapSchema.ValueSchema)); + } + + static object? ConvertToUnion(JsonNode? graph, UnionSchema unionSchema) + { + foreach (var schema in unionSchema.Schemas) + { + try { return ConvertToAvroCompatible(graph, schema); } + catch { } + } + throw new InvalidOperationException("Provided object does not match any schema in the union."); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/CallTaskExecutorRegistry.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/CallTaskExecutorRegistry.cs new file mode 100644 index 0000000..d9007e3 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/CallTaskExecutorRegistry.cs @@ -0,0 +1,47 @@ +// 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.Runtime.Services; + +/// +/// Represents a registry used to map call type discriminators (e.g. "http", "openapi") to their corresponding types +/// +public sealed class CallTaskExecutorRegistry +{ + + readonly Dictionary registry = []; + + /// + /// Registers the specified for the specified call type + /// + /// The call type discriminator to register the executor for (e.g. "http", "openapi", "asyncapi", "grpc") + /// The type of to register + public void Register(string callType) + where TExecutor : class, ITaskExecutor + { + ArgumentException.ThrowIfNullOrWhiteSpace(callType); + registry[callType] = typeof(TExecutor); + } + + /// + /// Resolves the type registered for the specified call type + /// + /// The call type discriminator to resolve the executor for + /// The resolved executor type, if any + public Type? Resolve(string callType) + { + ArgumentException.ThrowIfNullOrWhiteSpace(callType); + return registry.TryGetValue(callType, out var executorType) ? executorType : null; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/AsyncApiCallTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/AsyncApiCallTaskExecutor.cs new file mode 100644 index 0000000..4f1aaad --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/AsyncApiCallTaskExecutor.cs @@ -0,0 +1,293 @@ +// 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. + +extern alias LinqAsync; +using Neuroglia.AsyncApi; +using Neuroglia.AsyncApi.Client; +using Neuroglia.AsyncApi.Client.Services; +using Neuroglia.AsyncApi.IO; +using Neuroglia.AsyncApi.v3; +using ServerlessWorkflow.Sdk.Models.Calls; + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute AsyncAPI s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to create s +/// The service used to handle authentication policies +/// The service used to read s +/// The service used to create s +/// The current +public sealed class AsyncApiCallTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IHttpClientFactory httpClientFactory, IAuthenticationHandler authenticationHandler, IAsyncApiDocumentReader asyncApiDocumentReader, IAsyncApiClientFactory asyncApiClientFactory, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + AsyncApiCallDefinition? asyncApi; + V3AsyncApiDocument? document; + KeyValuePair operation; + object? messagePayload; + object? messageHeaders; + IDisposable? subscription; + uint? offset; + bool keepConsume = true; + + static JsonPointer GetPathFor(uint offset) => JsonPointer.Parse($"foreach/{offset}/do"); + + /// + protected override async Task CreateTaskExecutorAsync(ITaskInstance state, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var executor = await base.CreateTaskExecutorAsync(state, definition, contextData, arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnMessageProcessingErrorAsync(executor, CancellationTokenSource!.Token).ConfigureAwait(false), + async () => await OnMessageProcessingCompletedAsync(executor, CancellationTokenSource!.Token).ConfigureAwait(false) + ); + return executor; + } + + /// + protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) + { + asyncApi = JsonSerializer.Deserialize(Task.Definition.With!, Sdk.Serialization.Json.JsonSerializationContext.Default.AsyncApiCallDefinition) ?? throw new InvalidOperationException("Failed to deserialize AsyncAPI call definition from 'with'"); + var documentEndpointUri = asyncApi.Document.Endpoint.Match( + endpoint => endpoint.Uri, + uri => uri + ); + var documentAuthentication = asyncApi.Document.Endpoint.Match( + endpoint => endpoint.Authentication, + _ => (AuthenticationPolicyDefinition?)null + ); + using var httpClient = httpClientFactory.CreateClient(); + if (documentAuthentication != null) + { + var authResult = await authenticationHandler.HandleAsync(documentAuthentication, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.Scheme, authResult.Value); + } + using var request = new HttpRequestMessage(HttpMethod.Get, documentEndpointUri); + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + if (!response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + logger.LogError("Failed to retrieve the AsyncAPI document at location '{uri}'. The remote server responded with a non-success status code '{statusCode}'.", documentEndpointUri, response.StatusCode); + if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Response content:\r\n{responseContent}", responseContent ?? "None"); + response.EnsureSuccessStatusCode(); + } + using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var doc = await asyncApiDocumentReader.ReadAsync(responseStream, cancellationToken).ConfigureAwait(false); + if (doc is not V3AsyncApiDocument v3Document) throw new NotSupportedException("Only AsyncAPI v3.0.0 is supported at this time"); + document = v3Document; + if (string.IsNullOrWhiteSpace(asyncApi.Operation)) throw new NullReferenceException("The 'operation' parameter must be set when performing an AsyncAPI v3 call"); + var operationId = asyncApi.Operation; + if (operationId.IsRuntimeExpression()) operationId = await Task.Workflow.Expressions.EvaluateAsync(operationId, Task.Instance.Input ?? new JsonObject(), GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(operationId)) throw new NullReferenceException("The operation ref cannot be null or empty"); + operation = document.Operations.FirstOrDefault(o => o.Key == operationId); + if (operation.Value == null) throw new NullReferenceException($"Failed to find an operation with id '{operationId}' in AsyncAPI document at '{documentEndpointUri}'"); + if (asyncApi.Authentication != null) + { + //todo: handle AsyncAPI-specific authentication + } + switch (operation.Value.Action) + { + case V3OperationAction.Receive: + await BuildMessagePayloadAsync(cancellationToken).ConfigureAwait(false); + await BuildMessageHeadersAsync(cancellationToken).ConfigureAwait(false); + break; + case V3OperationAction.Send: break; + default: throw new NotSupportedException($"The specified operation action '{operation.Value.Action}' is not supported"); + } + } + + async Task BuildMessagePayloadAsync(CancellationToken cancellationToken = default) + { + if (asyncApi == null || operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution"); + if (asyncApi.Message?.Payload == null) return; + var arguments = GetExpressionEvaluationArguments(); + messagePayload = await Task.Workflow.Expressions.EvaluateAsync(asyncApi.Message.Payload, Task.Instance.Input ?? new JsonObject(), arguments, cancellationToken).ConfigureAwait(false); + } + + async Task BuildMessageHeadersAsync(CancellationToken cancellationToken = default) + { + if (asyncApi == null || operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution"); + if (asyncApi.Message?.Headers == null) return; + var arguments = GetExpressionEvaluationArguments(); + messageHeaders = await Task.Workflow.Expressions.EvaluateAsync(asyncApi.Message.Headers, Task.Instance.Input ?? new JsonObject(), arguments, cancellationToken).ConfigureAwait(false); + } + + /// + protected override Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (asyncApi == null || document == null || operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution"); + return operation.Value.Action switch + { + V3OperationAction.Receive => DoExecutePublishOperationAsync(cancellationToken), + V3OperationAction.Send => DoExecuteSubscribeOperationAsync(cancellationToken), + _ => throw new NotSupportedException($"The specified operation action '{operation.Value.Action}' is not supported"), + }; + } + + async Task DoExecutePublishOperationAsync(CancellationToken cancellationToken) + { + if (asyncApi == null || document == null || operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution"); + await using var asyncApiClient = asyncApiClientFactory.CreateFor(document); + var parameters = new AsyncApiPublishOperationParameters(operation.Key, asyncApi.Server, asyncApi.Protocol) + { + Payload = messagePayload, + Headers = messageHeaders + }; + await using var result = await asyncApiClient.PublishAsync(parameters, cancellationToken).ConfigureAwait(false); + if (!result.IsSuccessful) throw new Exception("Failed to execute the AsyncAPI publish operation"); + await SetResultAsync(null, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + + async Task DoExecuteSubscribeOperationAsync(CancellationToken cancellationToken) + { + if (asyncApi == null || document == null || operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution"); + if (asyncApi.Subscription == null) throw new NullReferenceException("The 'subscription' must be set when performing an AsyncAPI v3 subscribe operation"); + await using var asyncApiClient = asyncApiClientFactory.CreateFor(document); + var subscribeParams = new AsyncApiSubscribeOperationParameters(operation.Key, asyncApi.Server, asyncApi.Protocol); + var result = await asyncApiClient.SubscribeAsync(subscribeParams, cancellationToken).ConfigureAwait(false); + if (!result.IsSuccessful) throw new Exception("Failed to execute the AsyncAPI subscribe operation"); + if (result.Messages == null) + { + await SetResultAsync(null, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + var observable = result.Messages; + if (asyncApi.Subscription.Consume.For != null) observable = observable.TakeUntil(Observable.Timer(asyncApi.Subscription.Consume.For.ToTimeSpan())); + if (asyncApi.Subscription.Consume.Amount.HasValue) observable = observable.Take(asyncApi.Subscription.Consume.Amount.Value); + if (asyncApi.Subscription.Foreach == null) + { + var messages = new List(); + await foreach (var m in LinqAsync.System.Linq.AsyncEnumerable.ToAsyncEnumerable(observable).WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (!string.IsNullOrWhiteSpace(asyncApi.Subscription.Consume.While) && !await Task.Workflow.Expressions.EvaluateConditionAsync(asyncApi.Subscription.Consume.While, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false)) break; + if (!string.IsNullOrWhiteSpace(asyncApi.Subscription.Consume.Until) && await Task.Workflow.Expressions.EvaluateConditionAsync(asyncApi.Subscription.Consume.Until, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false)) break; + messages.Add(m); + } + var messagesJson = JsonSerializer.SerializeToNode(messages); + await SetResultAsync(messagesJson, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + else + { + subscription = observable.TakeWhile(_ => keepConsume).SelectMany(m => + { + OnStreamingMessageAsync(m).GetAwaiter().GetResult(); + return Observable.Return(m); + }).SubscribeAsync(_ => System.Threading.Tasks.Task.CompletedTask, OnStreamingErrorAsync, OnStreamingCompletedAsync); + } + } + + async Task OnStreamingMessageAsync(IAsyncApiMessage message) + { + if (asyncApi == null || document == null || operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution"); + if (asyncApi.Subscription == null) throw new NullReferenceException("The 'subscription' must be set when performing an AsyncAPI v3 subscribe operation"); + if (!string.IsNullOrWhiteSpace(asyncApi.Subscription.Consume.While) && !await Task.Workflow.Expressions.EvaluateConditionAsync(asyncApi.Subscription.Consume.While, Task.Instance.Input, GetExpressionEvaluationArguments(), CancellationTokenSource!.Token).ConfigureAwait(false)) + { + keepConsume = false; + return; + } + if (asyncApi.Subscription.Foreach?.Do != null) + { + var taskDefinition = new DoTaskDefinition() + { + Do = asyncApi.Subscription.Foreach.Do + }; + var messageData = message as object; + var currentOffset = offset ?? 0; + if (!offset.HasValue) offset = 0; + var arguments = GetExpressionEvaluationArguments(); + arguments ??= []; + arguments[asyncApi.Subscription.Foreach.Item ?? RuntimeExpressions.Arguments.Each] = JsonSerializer.SerializeToNode(messageData); + arguments[asyncApi.Subscription.Foreach.At ?? RuntimeExpressions.Arguments.Index] = JsonValue.Create(currentOffset); + if (asyncApi.Subscription.Foreach.Output?.As != null) + { + var messageNode = JsonSerializer.SerializeToNode(messageData) ?? new JsonObject(); + var outputExpression = asyncApi.Subscription.Foreach.Output.As!.Match(obj => obj, str => (JsonNode)JsonValue.Create(str)!); + messageData = await Task.Workflow.Expressions.EvaluateAsync(outputExpression, messageNode, arguments, CancellationTokenSource!.Token).ConfigureAwait(false); + } + if (asyncApi.Subscription.Foreach.Export?.As != null) + { + var messageNode = JsonSerializer.SerializeToNode(messageData) ?? new JsonObject(); + var exportExpression = asyncApi.Subscription.Foreach.Export.As!.Match(obj => obj, str => (JsonNode)JsonValue.Create(str)!); + var context = await Task.Workflow.Expressions.EvaluateAsync(exportExpression, messageNode, arguments, CancellationTokenSource!.Token).ConfigureAwait(false); + if (context is JsonObject contextObj) await Task.SetContextDataAsync(contextObj, CancellationTokenSource!.Token).ConfigureAwait(false); + } + var taskInstance = await Task.Workflow.CreateTaskAsync(taskDefinition, GetPathFor(currentOffset), Task.Instance.Input, Task, false, CancellationTokenSource!.Token).ConfigureAwait(false); + var taskExecutor = await CreateTaskExecutorAsync(taskInstance, taskDefinition, Task.Workflow.Instance.ContextData, arguments, CancellationTokenSource!.Token).ConfigureAwait(false); + await taskExecutor.ExecuteAsync(CancellationTokenSource!.Token).ConfigureAwait(false); + if (Task.Workflow.Instance.ContextData != taskExecutor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(taskExecutor.Task.Workflow.Instance.ContextData, CancellationTokenSource!.Token).ConfigureAwait(false); + offset++; + } + if (!string.IsNullOrWhiteSpace(asyncApi.Subscription.Consume.Until) && await Task.Workflow.Expressions.EvaluateConditionAsync(asyncApi.Subscription.Consume.Until, Task.Instance.Input, GetExpressionEvaluationArguments(), CancellationTokenSource!.Token).ConfigureAwait(false)) + { + keepConsume = false; + } + } + + Task OnStreamingErrorAsync(Exception ex) => SetErrorAsync(new Error() + { + Type = Sdk.ErrorType.Communication, + Title = ErrorTitle.Communication, + Status = ErrorStatus.Communication, + Detail = ex.Message, + Instance = new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute) + }, CancellationTokenSource!.Token); + + async Task OnStreamingCompletedAsync() + { + ITaskInstance? last = null; + await foreach (var t in Task.GetSubTasksAsync(CancellationTokenSource!.Token).ConfigureAwait(false)) + { + if (last == null || t.StartedAt > last.StartedAt) last = t; + } + JsonNode? output = null; + if (last?.Output != null) output = last.Output; + await SetResultAsync(output, Task.Definition.Then, CancellationTokenSource!.Token).ConfigureAwait(false); + } + + async Task OnMessageProcessingErrorAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(executor); + var error = executor.Task.Instance.Error ?? throw new NullReferenceException(); + Executors.Remove(executor); + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + + async Task OnMessageProcessingCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(executor); + Executors.Remove(executor); + if (Task.Workflow.Instance.ContextData != executor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(executor.Task.Workflow.Instance.ContextData, cancellationToken).ConfigureAwait(false); + } + + /// + protected override ValueTask DisposeAsync(bool disposing) + { + if (disposing) subscription?.Dispose(); + return base.DisposeAsync(disposing); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) subscription?.Dispose(); + base.Dispose(disposing); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ContainerRunTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ContainerRunTaskExecutor.cs new file mode 100644 index 0000000..176faf5 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ContainerRunTaskExecutor.cs @@ -0,0 +1,66 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute container s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to manage s +/// The current +public sealed class ContainerRunTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, + ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IContainerRuntime containerRuntime, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + IContainer? container; + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var processDefinition = Task.Definition.Run.Container!; + container = await containerRuntime.CreateAsync(processDefinition, cancellationToken).ConfigureAwait(false); + try + { + await container.StartAsync(cancellationToken).ConfigureAwait(false); + if (Task.Definition.Run.Await == false) + { + await SetResultAsync(new JsonObject(), Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + await container.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + var standardOutput = container.StandardOutput == null ? null : (await container.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).Trim(); + var result = new JsonObject { ["output"] = standardOutput }; + await SetResultAsync(result, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError("An error occurred while executing the container process: {ex}", ex); + var message = ex.Message; + try { if (container.StandardError != null) message = await container.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); } catch { } + await SetErrorAsync(Error.Runtime(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), message), cancellationToken).ConfigureAwait(false); + } + } + + /// + protected override Task SuspendCoreAsync(CancellationToken cancellationToken) => container?.StopAsync(cancellationToken) ?? System.Threading.Tasks.Task.CompletedTask; + + /// + protected override Task CancelCoreAsync(CancellationToken cancellationToken) => container?.StopAsync(cancellationToken) ?? System.Threading.Tasks.Task.CompletedTask; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/CustomFunctionCallTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/CustomFunctionCallTaskExecutor.cs new file mode 100644 index 0000000..857d820 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/CustomFunctionCallTaskExecutor.cs @@ -0,0 +1,171 @@ +// 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 YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute custom function s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to create s +/// The service used to handle authentication policies +/// The current +public sealed class CustomFunctionCallTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IHttpClientFactory httpClientFactory, IAuthenticationHandler authenticationHandler, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + const string CustomFunctionDefinitionFile = "function.yaml"; + const string GitHubHost = "github.com"; + const string GitLabHost = "gitlab"; + + TaskDefinition? function; + + /// + protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) + { + if (Task.Workflow.Definition.Use?.Functions?.TryGetValue(Task.Definition.Call, out var fn) == true && fn != null) function = fn; + else if (Uri.TryCreate(Task.Definition.Call, UriKind.Absolute, out var uri) && (uri.IsFile || !string.IsNullOrWhiteSpace(uri.Host))) function = await GetCustomFunctionAsync(new EndpointDefinition { Uri = uri }, cancellationToken).ConfigureAwait(false); + else if (Task.Definition.Call.Contains('@')) + { + var components = Task.Definition.Call.Split('@', StringSplitOptions.RemoveEmptyEntries); + if (components.Length != 2) throw new NotSupportedException($"Unknown/unsupported function '{Task.Definition.Call}'"); + function = await GetCustomFunctionFromCatalogAsync(components[0], components[1], cancellationToken).ConfigureAwait(false); + } + else if (Task.Definition.Call.Contains(':')) + { + var components = Task.Definition.Call.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (components.Length != 2) throw new Exception($"The specified value '{Task.Definition.Call}' is not a valid custom function qualified name ({{name}}:{{version}})"); + var functionName = components[0]; + var functionVersion = components[1]; + uri = new Uri($"https://github.com/serverlessworkflow/catalog/tree/main/functions/{functionName}/{functionVersion}/{CustomFunctionDefinitionFile}"); + function = await GetCustomFunctionAsync(new EndpointDefinition { Uri = uri }, cancellationToken).ConfigureAwait(false); + } + else throw new NotSupportedException($"Unknown/unsupported function '{Task.Definition.Call}'"); + } + + async Task GetCustomFunctionAsync(EndpointDefinition endpoint, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(endpoint); + var uri = endpoint.Uri; + if (!uri.OriginalString.EndsWith(CustomFunctionDefinitionFile)) uri = new Uri(uri, CustomFunctionDefinitionFile); + if (uri.Host.Equals(GitHubHost, StringComparison.OrdinalIgnoreCase)) uri = TransformGithubUriToRawUri(uri); + else if (uri.Host.Contains(GitLabHost)) uri = TransformGitlabUriToRawUri(uri); + using var httpClient = httpClientFactory.CreateClient(); + if (endpoint.Authentication != null) + { + var authResult = await authenticationHandler.HandleAsync(endpoint.Authentication, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.Scheme, authResult.Value); + } + try + { + using var response = await httpClient.GetAsync(uri, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var yaml = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + var serializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + var functionDef = serializer.Deserialize>(yaml); + var json = JsonSerializer.Serialize(functionDef); + return JsonSerializer.Deserialize(json) ?? throw new InvalidOperationException($"Failed to deserialize custom function definition from '{uri}'"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to load the custom function defined at '{uri}': {ex.Message}", ex); + } + } + + async Task GetCustomFunctionFromCatalogAsync(string functionName, string catalogName, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(functionName); + ArgumentException.ThrowIfNullOrWhiteSpace(catalogName); + var components = functionName.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (components.Length != 2) throw new Exception($"The specified value '{functionName}' is not a valid custom function qualified name ({{name}}:{{version}})"); + var name = components[0]; + var version = components[1]; + if (Task.Workflow.Definition.Use?.Catalogs?.TryGetValue(catalogName, out var catalog) != true || catalog == null) throw new NullReferenceException($"Failed to find a catalog with the specified name '{catalogName}'"); + var catalogUri = catalog.Endpoint.Match( + endpoint => endpoint.Uri, + uri => uri + ); + var catalogAuthentication = catalog.Endpoint.Match( + endpoint => endpoint.Authentication, + _ => (AuthenticationPolicyDefinition?)null + ); + return await GetCustomFunctionAsync(new EndpointDefinition + { + Uri = new Uri(catalogUri, $"/functions/{name}/{version}"), + Authentication = catalogAuthentication + }, cancellationToken).ConfigureAwait(false); + } + + static Uri TransformGithubUriToRawUri(Uri uri) + { + if (!uri.Host.Equals(GitHubHost, StringComparison.OrdinalIgnoreCase)) return uri; + var rawUri = uri.AbsoluteUri.Replace(GitHubHost, "raw.githubusercontent.com", StringComparison.OrdinalIgnoreCase); + rawUri = rawUri.Replace("/tree/", "/refs/heads/", StringComparison.OrdinalIgnoreCase); + return new(rawUri, UriKind.Absolute); + } + + static Uri TransformGitlabUriToRawUri(Uri uri) + { + if (!uri.Host.Contains(GitLabHost, StringComparison.OrdinalIgnoreCase)) return uri; + var rawUri = uri.AbsoluteUri.Replace("/-/blob/", "/-/raw/", StringComparison.OrdinalIgnoreCase); + return new(rawUri, UriKind.Absolute); + } + + /// + protected override async Task CreateTaskExecutorAsync(ITaskInstance state, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var executor = await base.CreateTaskExecutorAsync(state, definition, contextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnSubTaskFaultAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false)); + return executor; + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (function == null) throw new InvalidOperationException("The executor must be initialized before execution"); + JsonNode? input; + if (Task.Definition.With != null) + { + var evaluated = await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.With, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + input = evaluated ?? new JsonObject(); + } + else + { + input = new JsonObject(); + } + var taskInstance = await Task.Workflow.CreateTaskAsync(function, JsonPointer.Empty, input, Task, false, cancellationToken).ConfigureAwait(false); + var executor = await CreateTaskExecutorAsync(taskInstance, function, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + await SetResultAsync(executor.Task.Instance.Output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + + async Task OnSubTaskFaultAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(executor); + var error = executor.Task.Instance.Error ?? throw new NullReferenceException(); + Executors.Remove(executor); + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/DoTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/DoTaskExecutor.cs new file mode 100644 index 0000000..744224d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/DoTaskExecutor.cs @@ -0,0 +1,125 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class DoTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + Map Tasks => Task.Definition.Do; + + JsonPointer GetPathFor(int index, string name) => JsonPointer.Parse($"{Task.Instance.Reference}/{index}/{name}"); + + MapEntry? GetNextTask(string? currentName) + { + if (currentName == null) return null; + var keys = Tasks.Keys; + var index = keys.ToList().IndexOf(currentName); + if (index < 0 || index >= keys.Count - 1) return null; + var nextKey = keys[index + 1]; + return new(nextKey, Tasks[nextKey]); + } + + /// + protected override async Task CreateTaskExecutorAsync(ITaskInstance state, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var executor = await base.CreateTaskExecutorAsync(state, definition, contextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnSubTaskFaultAsync(executor, ex, CancellationTokenSource?.Token ?? default).ConfigureAwait(false), + async () => await OnSubtaskCompletedAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false) + ); + return executor; + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + ITaskInstance? last = null; + await foreach (var subtask in Task.GetSubTasksAsync(cancellationToken).ConfigureAwait(false)) last = subtask; + MapEntry? nextEntry; + if (last == null) nextEntry = Tasks.FirstOrDefault(); + else if (last.Status == null || last.IsOperative || last.Status == TaskStatus.Suspended) nextEntry = Tasks.FirstOrDefault(e => e.Key == last.Name) ?? throw new NullReferenceException($"Failed to find a task with the specified name '{last.Name}' at '{Task.Instance.Reference}'"); + else nextEntry = GetNextTask(last.Name); + if (last != null && (last.Status == null || last.IsOperative || last.Status == TaskStatus.Suspended)) + { + ITaskInstance? lastCompleted = null; + await foreach (var subtask in Task.GetSubTasksAsync(cancellationToken).ConfigureAwait(false)) if (subtask.Status != null && !subtask.IsOperative && subtask.Status != TaskStatus.Suspended) lastCompleted = subtask; + if (lastCompleted != null) last = lastCompleted; + } + if (nextEntry == null) + { + await SetResultAsync(last?.Output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + var nextIndex = Tasks.Keys.ToList().IndexOf(nextEntry.Key); + var input = last == null ? Task.Instance.Input : last.Output ?? new JsonObject(); + var next = await Task.Workflow.CreateTaskAsync(nextEntry.Value, GetPathFor(nextIndex, nextEntry.Key), input, Task, Task.Instance.IsExtension, cancellationToken).ConfigureAwait(false); + var executor = await CreateTaskExecutorAsync(next, nextEntry.Value, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + async Task OnSubTaskFaultAsync(ITaskExecutor executor, Exception ex, CancellationToken cancellationToken) + { + var error = executor.Task.Instance.Error; + if (error is null) + { + if (ex is RuntimeErrorException rex) error = rex.Error; + else error = Error.Runtime(new(executor.Task.Instance.Reference.ToString(), UriKind.Relative), $"An unhandled exception was thrown during the execution of task '{executor.Task.Instance.Reference}': {ex}"); + } + Executors.Remove(executor); + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + + async Task OnSubtaskCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + var lastState = executor.Task.Instance; + var output = executor.Task.Instance.Output ?? new JsonObject(); + Executors.Remove(executor); + if (Task.Workflow.Instance.ContextData != executor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(executor.Task.Workflow.Instance.ContextData, cancellationToken).ConfigureAwait(false); + var nextEntry = GetNextTask(lastState.Name); + if (nextEntry == null) + { + var then = lastState.Status != TaskStatus.Skipped && lastState.Next == FlowDirective.End ? FlowDirective.End : Task.Definition.Then; + await SetResultAsync(output, then, cancellationToken).ConfigureAwait(false); + return; + } + var nextIndex = Tasks.Keys.ToList().IndexOf(nextEntry.Key); + var flowDirective = lastState.Status == TaskStatus.Skipped ? FlowDirective.Continue : lastState.Next; + switch (flowDirective) + { + case FlowDirective.End: + await SetResultAsync(output, FlowDirective.End, cancellationToken).ConfigureAwait(false); + break; + case FlowDirective.Exit: + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + break; + default: + var next = await Task.Workflow.CreateTaskAsync(nextEntry.Value, GetPathFor(nextIndex, nextEntry.Key), output, Task, false, cancellationToken).ConfigureAwait(false); + var nextExecutor = await CreateTaskExecutorAsync(next, nextEntry.Value, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + await nextExecutor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + break; + } + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/EmitTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/EmitTaskExecutor.cs new file mode 100644 index 0000000..34e18ed --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/EmitTaskExecutor.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. + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to publish and subscribe to s +/// The current +public sealed class EmitTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ICloudEventBus cloudEventBus, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var attributes = Task.Definition.Emit.Event.With.DeepClone().AsObject() ?? new JsonObject(); + if (!attributes.ContainsKey(CloudEventAttributes.Id)) attributes[CloudEventAttributes.Id] = Guid.NewGuid().ToString(); + if (!attributes.ContainsKey(CloudEventAttributes.SpecVersion)) attributes[CloudEventAttributes.SpecVersion] = CloudEvent.DefaultVersion; + if (!attributes.ContainsKey(CloudEventAttributes.Time)) attributes[CloudEventAttributes.Time] = DateTimeOffset.Now.ToString("o"); + var result = await Task.Workflow.Expressions.EvaluateAsync(attributes, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + var cloudEvent = JsonSerializer.Deserialize(result, Serialization.Json.JsonSerializationContext.Default.CloudEvent); + await cloudEventBus.PublishAsync(cloudEvent!, cancellationToken).ConfigureAwait(false); + await SetResultAsync(result, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ExtensionTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ExtensionTaskExecutor.cs new file mode 100644 index 0000000..4acd88e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ExtensionTaskExecutor.cs @@ -0,0 +1,32 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class ExtensionTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override Task ExecuteCoreAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ForTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ForTaskExecutor.cs new file mode 100644 index 0000000..744fca9 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ForTaskExecutor.cs @@ -0,0 +1,129 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class ForTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + JsonArray? collection; + + JsonPointer GetPathFor(string subTaskName) => JsonPointer.Parse($"{Task.Instance.Reference}/{subTaskName}/do"); + + /// + protected override async Task CreateTaskExecutorAsync(ITaskInstance state, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var executor = await base.CreateTaskExecutorAsync(state, definition, contextData, arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnIterationFaultAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false), + async () => await OnIterationCompletedAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false) + ); + return executor; + } + + /// + protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) + { + var result = await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.For.In, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + collection = result?.AsArray() ?? throw new InvalidOperationException("The 'for.in' expression must evaluate to an array"); + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (collection == null) throw new InvalidOperationException("The executor must be initialized before execution"); + ITaskInstance? lastSubtask = null; + await foreach (var subtask in Task.GetSubTasksAsync(cancellationToken).ConfigureAwait(false)) lastSubtask = subtask; + var index = 0; + if (lastSubtask != null) + { + var parts = lastSubtask.Reference.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 0 && int.TryParse(parts[^1], out var lastIndex)) index = lastIndex; + if (index == collection.Count - 1) + { + await SetResultAsync(Task.Instance.Input, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + if (!lastSubtask.IsOperative) index++; + } + var item = collection[index]; + var taskDefinition = new DoTaskDefinition() { Do = Task.Definition.Do }; + var taskInstance = await Task.Workflow.CreateTaskAsync(taskDefinition, GetPathFor(index.ToString()), Task.Instance.Input, Task, false, cancellationToken).ConfigureAwait(false); + var contextData = Task.Workflow.Instance.ContextData.DeepClone().AsObject()!; + var arguments = Task.Arguments?.DeepClone().AsObject()! ?? []; + arguments[Task.Definition.For.Each] = item?.DeepClone(); + arguments[Task.Definition.For.At ?? RuntimeExpressions.Arguments.Index] = index; + var executor = await CreateTaskExecutorAsync(taskInstance, taskDefinition, contextData, arguments, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + async Task OnIterationFaultAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + if (collection == null) throw new InvalidOperationException("The executor must be initialized before execution"); + var error = executor.Task.Instance.Error ?? throw new NullReferenceException(); + Executors.Remove(executor); + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + + async Task OnIterationCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + if (collection == null) throw new InvalidOperationException("The executor must be initialized before execution"); + var output = executor.Task.Instance.Output ?? new JsonObject(); + Executors.Remove(executor); + if (Task.Workflow.Instance.ContextData != executor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(executor.Task.Workflow.Instance.ContextData, cancellationToken).ConfigureAwait(false); + var lastReference = executor.Task.Instance.Reference.ToString(); + var parts = lastReference.Split('/', StringSplitOptions.RemoveEmptyEntries); + var index = 0; + if (parts.Length >= 2 && int.TryParse(parts[^2], out var parsedIndex)) index = parsedIndex + 1; + if (index >= collection.Count) + { + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + switch (executor.Task.Instance.Next) + { + case FlowDirective.Continue: + var taskDefinition = new DoTaskDefinition() { Do = Task.Definition.Do }; + var next = await Task.Workflow.CreateTaskAsync(taskDefinition, GetPathFor(index.ToString()), output, Task, false, cancellationToken).ConfigureAwait(false); + var item = collection[index]; + var contextData = Task.Workflow.Instance.ContextData.DeepClone().AsObject()!; + var arguments = Task.Arguments?.DeepClone().AsObject()! ?? []; + arguments[Task.Definition.For.Each] = item?.DeepClone(); + arguments[Task.Definition.For.At ?? RuntimeExpressions.Arguments.Index] = index; + var nextExecutor = await CreateTaskExecutorAsync(next, taskDefinition, contextData, arguments, cancellationToken).ConfigureAwait(false); + await nextExecutor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + break; + case FlowDirective.End: + await SetResultAsync(output, FlowDirective.End, cancellationToken).ConfigureAwait(false); + break; + case FlowDirective.Exit: + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + break; + default: + await SetErrorAsync(Error.Configuration(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), "Unable to continue with a specific task within a loop"), cancellationToken).ConfigureAwait(false); + break; + } + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ForkTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ForkTaskExecutor.cs new file mode 100644 index 0000000..2296fba --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ForkTaskExecutor.cs @@ -0,0 +1,107 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class ForkTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + static JsonPointer GetPathFor(int index, string name) => JsonPointer.Create("fork", "branches", index, name); + + /// + protected override async Task CreateTaskExecutorAsync(ITaskInstance state, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var executor = await base.CreateTaskExecutorAsync(state, definition, contextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnSubTaskFaultAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false), + async () => await OnSubTaskCompletedAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false) + ); + return executor; + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var branches = Task.Definition.Fork.Branches; + var executionTasks = new List(); + var index = 0; + foreach (var branch in branches) + { + var branchInstance = await Task.Workflow.CreateTaskAsync(branch.Value, GetPathFor(index, branch.Key), Task.Instance.Input, Task, false, cancellationToken).ConfigureAwait(false); + var executor = await CreateTaskExecutorAsync(branchInstance, branch.Value, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + executionTasks.Add(executor.ExecuteAsync(cancellationToken)); + index++; + } + await System.Threading.Tasks.Task.WhenAll(executionTasks).ConfigureAwait(false); + } + + async Task OnSubTaskFaultAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + using var @lock = await Lock.LockAsync(cancellationToken).ConfigureAwait(false); + var error = executor.Task.Instance.Error ?? throw new NullReferenceException(); + Executors.Remove(executor); + foreach (var subExecutor in Executors.ToList()) + { + await subExecutor.CancelAsync(cancellationToken).ConfigureAwait(false); + Executors.Remove(subExecutor); + } + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + + async Task OnSubTaskCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + using var @lock = await Lock.LockAsync(cancellationToken).ConfigureAwait(false); + if (Task.Instance.Status != TaskStatus.Running) + { + if (Executors.Remove(executor)) await executor.CancelAsync(cancellationToken).ConfigureAwait(false); + return; + } + if (Task.Definition.Fork.Compete) + { + var output = executor.Task.Instance.Output ?? new JsonObject(); + foreach (var concurrentExecutor in Executors.ToList()) + { + Executors.Remove(concurrentExecutor); + await concurrentExecutor.CancelAsync(cancellationToken).ConfigureAwait(false); + } + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + else + { + Executors.Remove(executor); + await executor.DisposeAsync().ConfigureAwait(false); + var allDone = true; + await foreach (var subtask in Task.GetSubTasksAsync(cancellationToken).ConfigureAwait(false)) + { + if (subtask.Status != TaskStatus.Skipped && subtask.Status != TaskStatus.Completed && subtask.Status != TaskStatus.Cancelled && subtask.Status != TaskStatus.Faulted) + { + allDone = false; + break; + } + } + if (allDone) await SetResultAsync(new JsonObject(), Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/GrpcCallTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/GrpcCallTaskExecutor.cs new file mode 100644 index 0000000..84752ca --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/GrpcCallTaskExecutor.cs @@ -0,0 +1,115 @@ +// 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 DynamicGrpc; +using Google.Protobuf.Reflection; +using Grpc.Net.Client; +using ServerlessWorkflow.Sdk.Models.Calls; + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute gRPC s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to read external resources +/// The current +public sealed class GrpcCallTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IExternalResourceReader externalResourceReader, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + GrpcCallDefinition? grpc; + DynamicGrpcClient? grpcClient; + + /// + protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) + { + try + { + grpc = JsonSerializer.Deserialize(Task.Definition.With!, Sdk.Serialization.Json.JsonSerializationContext.Default.GrpcCallDefinition) ?? throw new InvalidOperationException("Failed to deserialize gRPC call definition from 'with'"); + var fileDescriptor = await GetProtoFileDescriptorAsync(grpc.Proto, cancellationToken).ConfigureAwait(false); + var address = grpc.Service.Port.HasValue + ? $"http://{grpc.Service.Host}:{grpc.Service.Port.Value}" + : $"http://{grpc.Service.Host}"; + var channel = GrpcChannel.ForAddress(address); + var callInvoker = channel.CreateCallInvoker(); + grpcClient = DynamicGrpcClient.FromDescriptorProtos(callInvoker: callInvoker, [fileDescriptor]); + } + catch (Exception ex) + { + logger.LogError("An error occurred while initializing the gRPC call task '{task}': {ex}", Task.Instance.Reference, ex); + await SetErrorAsync(Error.Validation(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), $"Invalid/missing call parameters for function 'grpc': {ex.Message}"), cancellationToken).ConfigureAwait(false); + } + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (grpc == null || grpcClient == null) throw new InvalidOperationException("The executor must be initialized before execution"); + if (!grpcClient.TryFindMethod(grpc.Service.Name, grpc.Method, out _)) + { + await SetErrorAsync(Error.Configuration(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), $"Failed to find a method with name '{grpc.Method}' in GRPC service with name '{grpc.Service.Name}'"), cancellationToken).ConfigureAwait(false); + return; + } + var arguments = GetExpressionEvaluationArguments(); + var requestArgs = grpc.Arguments != null + ? await Task.Workflow.Expressions.EvaluateAsync(grpc.Arguments, Task.Instance.Input, arguments, cancellationToken).ConfigureAwait(false) + : null; + var requestDictionary = requestArgs is JsonObject jsonObj + ? jsonObj.Deserialize>() ?? [] + : new Dictionary(); + IDictionary response; + try + { + response = await grpcClient.AsyncUnaryCall(grpc.Service.Name, grpc.Method, requestDictionary).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError("Failed to call the gRPC method '{method}' on '{service}' service at '{host}:{port}': {ex}", grpc.Method, grpc.Service.Name, grpc.Service.Host, grpc.Service.Port, ex.Message); + await SetErrorAsync(Error.Communication(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), ErrorStatus.Communication, ex.Message), cancellationToken).ConfigureAwait(false); + return; + } + var result = JsonSerializer.SerializeToNode(response); + await SetResultAsync(result, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + + async Task GetProtoFileDescriptorAsync(ExternalResourceDefinition resource, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(resource); + var protoFile = new FileInfo(System.IO.Path.GetTempFileName()); + var protoDescriptorFileName = System.IO.Path.Combine(protoFile.Directory!.FullName, $"{System.IO.Path.GetFileNameWithoutExtension(protoFile.Name)}.desc"); + using var stream = await externalResourceReader.ReadAsync(resource, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + { + using var protoFileStream = new FileStream(protoFile.FullName, FileMode.Create); + { + await stream.CopyToAsync(protoFileStream, cancellationToken).ConfigureAwait(false); + await protoFileStream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + } + var processInfo = new ProcessStartInfo("protoc", $"{protoFile.FullName} --proto_path={protoFile.Directory!.FullName} --descriptor_set_out={protoDescriptorFileName}") + { + RedirectStandardError = true, + RedirectStandardOutput = true + }; + using var process = Process.Start(processInfo)!; + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + using var protoDescriptorFileStream = new FileStream(protoDescriptorFileName, FileMode.Open); + var fileDescriptorSet = FileDescriptorSet.Parser.ParseFrom(protoDescriptorFileStream); + return fileDescriptorSet.File.First(); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/HttpCallTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/HttpCallTaskExecutor.cs new file mode 100644 index 0000000..6e7a4ca --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/HttpCallTaskExecutor.cs @@ -0,0 +1,155 @@ +// 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 ServerlessWorkflow.Sdk.Models.Calls; +using System.Reflection; + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute HTTP s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to create s +/// The service used to handle authentication policies +/// The current +public sealed class HttpCallTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IHttpClientFactory httpClientFactory, IAuthenticationHandler authenticationHandler, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + HttpCallDefinition? http; + AuthenticationPolicyDefinition? authentication; + + /// + protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) + { + try + { + http = JsonSerializer.Deserialize(Task.Definition.With!, Sdk.Serialization.Json.JsonSerializationContext.Default.HttpCallDefinition) ?? throw new InvalidOperationException("Failed to deserialize HTTP call definition from 'with'"); + authentication = http.Endpoint.Match( + endpoint => endpoint.Authentication, + _ => null + ); + } + catch (Exception ex) + { + logger.LogError("An error occurred while initializing the HTTP call task '{task}': {ex}", Task.Instance.Reference, ex); + await SetErrorAsync(Error.Validation(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), $"Invalid/missing call parameters for function 'http': {ex.Message}"), cancellationToken).ConfigureAwait(false); + } + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (http == null) throw new InvalidOperationException("The executor must be initialized before execution"); + var arguments = GetExpressionEvaluationArguments(); + var defaultMediaType = http.Body is JsonValue value && value.GetValueKind() == JsonValueKind.String ? MediaTypeNames.Text.Plain : MediaTypeNames.Application.Json; + var mediaType = defaultMediaType; + if (http.Headers != null && http.Headers.TryGetValue("Content-Type", out var contentType) && !string.IsNullOrWhiteSpace(contentType)) mediaType = contentType.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Trim(); + HttpContent? requestContent = null; + if (http.Body != null) + { + if (mediaType.StartsWith("text")) + { + var rawContent = http.Body.ToString(); + if (!string.IsNullOrWhiteSpace(rawContent) && rawContent.IsRuntimeExpression()) rawContent = await Task.Workflow.Expressions.EvaluateAsync(rawContent, Task.Instance.Input, arguments, cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(rawContent)) requestContent = new StringContent(rawContent, Encoding.UTF8, mediaType); + } + else if (mediaType == MediaTypeNames.Application.Octet) + { + var buffer = Convert.FromBase64String(http.Body.ToString()!); + requestContent = new StreamContent(new MemoryStream(buffer)); + } + else + { + var evaluatedBody = await Task.Workflow.Expressions.EvaluateAsync(http.Body, Task.Instance.Input, arguments, cancellationToken).ConfigureAwait(false); + if (evaluatedBody != null) requestContent = new StringContent(evaluatedBody.ToJsonString(), Encoding.UTF8, mediaType); + } + } + var endpointUri = http.Endpoint.Match( + endpoint => endpoint.Uri, + uri => uri + ); + using var httpClient = httpClientFactory.CreateClient(); + if (authentication != null) + { + var authResult = await authenticationHandler.HandleAsync(authentication, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.Scheme, authResult.Value); + } + var parameters = Task.Instance.Input is JsonObject jsonObject ? jsonObject.Where(kvp => kvp.Value is JsonValue).ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.GetValue()) : []; + var uri = StringFormatter.Format(endpointUri.OriginalString, parameters); + if (uri.IsRuntimeExpression()) uri = await Task.Workflow.Expressions.EvaluateAsync(uri, Task.Instance.Input, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + using var request = new HttpRequestMessage(new HttpMethod(http.Method), uri) { Content = requestContent }; + if (http.Headers != null) + { + foreach (var header in http.Headers) + { + var headerValue = header.Value; + if (headerValue.IsRuntimeExpression()) headerValue = await Task.Workflow.Expressions.EvaluateAsync(headerValue, Task.Instance.Input, arguments, cancellationToken).ConfigureAwait(false); + request.Headers.TryAddWithoutValidation(header.Key, headerValue); + } + } + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + var successRange = http.Redirect ? 399 : 299; + if ((int)response.StatusCode < 200 || (int)response.StatusCode > successRange) + { + var detail = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + logger.LogError("Failed to request '{method} {uri}'. The remote server responded with a non-success status code '{statusCode}'.", http.Method, endpointUri, response.StatusCode); + if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Response content:\r\n{responseContent}", detail ?? "None"); + await SetErrorAsync(Error.Communication(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), (ushort)response.StatusCode, detail), cancellationToken).ConfigureAwait(false); + return; + } + JsonNode? content = null; + var responseMediaType = response.Content.Headers.ContentType?.MediaType; + if (responseMediaType != null) + { + if (responseMediaType.Contains("json")) + { + var text = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + try { content = JsonNode.Parse(text); } + catch { content = JsonValue.Create(text); } + } + else + { + content = JsonValue.Create(await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)); + } + } + else + { + content = JsonValue.Create(await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)); + } + var result = http.Output switch + { + HttpOutputFormat.Response => JsonSerializer.SerializeToNode(new HttpResponse() + { + Request = new() + { + Method = request.Method.Method, + Uri = request.RequestUri!, + Headers = [.. request.Headers.ToDictionary(h => h.Key, h => string.Join(',', h.Value))] + }, + StatusCode = (int)response.StatusCode, + Headers = [.. response.Headers.ToDictionary(h => h.Key, h => string.Join(',', h.Value))], + Content = content + })?.AsObject(), + _ => content + }; + await SetResultAsync(result, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + +} + diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ListenTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ListenTaskExecutor.cs new file mode 100644 index 0000000..ffb3c34 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ListenTaskExecutor.cs @@ -0,0 +1,169 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to publish and subscribe to cloud events +/// The current +public sealed class ListenTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ICloudEventBus cloudEventBus, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + IDisposable? subscription; + uint eventOffset; + + static JsonPointer GetPathFor(uint offset) => JsonPointer.Create("foreach", $"{offset - 1}", "do"); + + /// + protected override async Task CreateTaskExecutorAsync(ITaskInstance state, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + var executor = await base.CreateTaskExecutorAsync(state, definition, contextData, arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnEventProcessingErrorAsync(executor, CancellationTokenSource!.Token).ConfigureAwait(false), + async () => await OnEventProcessingCompletedAsync(executor, CancellationTokenSource!.Token).ConfigureAwait(false) + ); + return executor; + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (Task.Definition.Foreach == null) + { + var events = await cloudEventBus.SubscribeAsync(cancellationToken).ConfigureAwait(false); + var collected = new List(); + var tcs = new TaskCompletionSource(); + events.Subscribe( + onNext: e => + { + var eventData = Task.Definition.Listen.Read switch + { + EventReadMode.Envelope => JsonSerializer.SerializeToNode(e), + _ => JsonSerializer.SerializeToNode(e) + }; + collected.Add(eventData); + tcs.TrySetResult(); + }, + onError: ex => tcs.TrySetException(ex), + onCompleted: () => tcs.TrySetResult() + ); + await tcs.Task.ConfigureAwait(false); + var result = new JsonObject { ["events"] = new JsonArray([.. collected]) }; + await SetResultAsync(result, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + else + { + ITaskInstance? lastSubtask = null; + await foreach (var subtask in Task.GetSubTasksAsync(cancellationToken).ConfigureAwait(false)) lastSubtask = subtask; + if (lastSubtask != null && lastSubtask.IsOperative && Task.Definition.Foreach.Do != null) + { + var taskDefinition = new DoTaskDefinition() + { + Do = Task.Definition.Foreach.Do + }; + var arguments = GetExpressionEvaluationArguments(); + var taskExecutor = await CreateTaskExecutorAsync(lastSubtask, taskDefinition, Task.Workflow.Instance.ContextData, arguments, CancellationTokenSource!.Token).ConfigureAwait(false); + await taskExecutor.ExecuteAsync(CancellationTokenSource!.Token).ConfigureAwait(false); + } + var events = await cloudEventBus.SubscribeAsync(cancellationToken).ConfigureAwait(false); + subscription = events.SubscribeAsync(OnStreamingEventAsync, OnStreamingErrorAsync, OnStreamingCompletedAsync); + } + } + + async Task OnStreamingEventAsync(ICloudEvent e) + { + eventOffset++; + if (Task.Definition.Foreach?.Do == null) + { + return; + } + var taskDefinition = new DoTaskDefinition() + { + Do = Task.Definition.Foreach.Do + }; + var arguments = GetExpressionEvaluationArguments() ?? []; + JsonNode? eventData = Task.Definition.Listen.Read switch + { + EventReadMode.Envelope => JsonSerializer.SerializeToNode(e), + _ => JsonSerializer.SerializeToNode(e) + }; + if (Task.Definition.Foreach.Output?.As != null) + { + eventData = await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.Foreach.Output.As, eventData ?? new JsonObject(), arguments, CancellationTokenSource!.Token).ConfigureAwait(false); + } + if (Task.Definition.Foreach.Export?.As != null) + { + var context = (await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.Foreach.Export.As, eventData ?? new JsonObject(), arguments, CancellationTokenSource!.Token).ConfigureAwait(false))?.AsObject(); + if (context != null) await Task.SetContextDataAsync(context, CancellationTokenSource!.Token).ConfigureAwait(false); + } + arguments[Task.Definition.Foreach.Item ?? RuntimeExpressions.Arguments.Each] = eventData!; + arguments[Task.Definition.Foreach.At ?? RuntimeExpressions.Arguments.Index] = eventOffset - 1; + var taskInstance = await Task.Workflow.CreateTaskAsync(taskDefinition, GetPathFor(eventOffset), Task.Instance.Input, Task, false, CancellationTokenSource!.Token).ConfigureAwait(false); + var taskExecutor = await CreateTaskExecutorAsync(taskInstance, taskDefinition, Task.Workflow.Instance.ContextData, arguments, CancellationTokenSource!.Token).ConfigureAwait(false); + await taskExecutor.ExecuteAsync(CancellationTokenSource!.Token).ConfigureAwait(false); + } + + Task OnStreamingErrorAsync(Exception ex) => SetErrorAsync(new Error() + { + Type = ErrorType.Communication, + Title = ErrorTitle.Communication, + Status = ErrorStatus.Communication, + Detail = ex.Message, + Instance = new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute) + }, CancellationTokenSource!.Token); + + async Task OnStreamingCompletedAsync() + { + ITaskInstance? last = null; + await foreach (var subtask in Task.GetSubTasksAsync(CancellationTokenSource!.Token).ConfigureAwait(false)) last = subtask; + var output = last?.Output; + await SetResultAsync(output, Task.Definition.Then, CancellationTokenSource!.Token).ConfigureAwait(false); + } + + async Task OnEventProcessingErrorAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + var error = executor.Task.Instance.Error ?? throw new NullReferenceException(); + Executors.Remove(executor); + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + + async Task OnEventProcessingCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + Executors.Remove(executor); + if (Task.Workflow.Instance.ContextData != executor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(executor.Task.Workflow.Instance.ContextData, cancellationToken).ConfigureAwait(false); + } + + /// + protected override ValueTask DisposeAsync(bool disposing) + { + if (disposing) subscription?.Dispose(); + return base.DisposeAsync(disposing); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) subscription?.Dispose(); + base.Dispose(disposing); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/OpenApiCallTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/OpenApiCallTaskExecutor.cs new file mode 100644 index 0000000..7a3f398 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/OpenApiCallTaskExecutor.cs @@ -0,0 +1,219 @@ +// 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 Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using ServerlessWorkflow.Sdk.Models.Calls; + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute OpenAPI s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to create s +/// The service used to handle authentication policies +/// The current +public sealed class OpenApiCallTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IHttpClientFactory httpClientFactory, IAuthenticationHandler authenticationHandler, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + OpenApiCallDefinition? openApi; + OpenApiDocument? document; + OpenApiOperation? operation; + HttpMethod httpMethod = null!; + List servers = null!; + IDictionary? parameters; + string? path; + string? query; + readonly Dictionary headers = []; + readonly Dictionary cookies = []; + object? body; + + /// + protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) + { + openApi = JsonSerializer.Deserialize(Task.Definition.With!, Sdk.Serialization.Json.JsonSerializationContext.Default.OpenApiCallDefinition) ?? throw new InvalidOperationException("Failed to deserialize OpenAPI call definition from 'with'"); + var documentEndpointUri = openApi.Document.Endpoint.Match( + endpoint => endpoint.Uri, + uri => uri + ); + var documentAuthentication = openApi.Document.Endpoint.Match( + endpoint => endpoint.Authentication, + _ => null + ); + using var httpClient = httpClientFactory.CreateClient(); + if (documentAuthentication != null) + { + var authResult = await authenticationHandler.HandleAsync(documentAuthentication, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.Scheme, authResult.Value); + } + using var request = new HttpRequestMessage(HttpMethod.Get, documentEndpointUri); + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + if (!response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + logger.LogError("Failed to retrieve the OpenAPI document at location '{uri}'. The remote server responded with a non-success status code '{statusCode}'.", documentEndpointUri, response.StatusCode); + if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Response content:\r\n{responseContent}", responseContent ?? "None"); + response.EnsureSuccessStatusCode(); + } + using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + document = new OpenApiStreamReader().Read(responseStream, out _); + var operationId = openApi.OperationId; + if (operationId.IsRuntimeExpression()) operationId = await Task.Workflow.Expressions.EvaluateAsync(operationId, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + var op = document.Paths + .SelectMany(p => p.Value.Operations) + .FirstOrDefault(o => o.Value.OperationId == operationId); + if (op.Value == null) throw new NullReferenceException($"Failed to find an operation with id '{operationId}' in OpenAPI document at '{documentEndpointUri}'"); + httpMethod = ToHttpMethod(op.Key); + operation = op.Value; + servers = document.Servers.Select(s => s.Url).ToList(); + if (servers.Count == 0) servers.Add(documentEndpointUri.GetLeftPart(UriPartial.Authority)); + var pathEntry = document.Paths.Single(p => p.Value.Operations.Any(o => o.Value.OperationId == operation.OperationId)); + path = pathEntry.Key; + await BuildParametersAsync(cancellationToken).ConfigureAwait(false); + if (parameters == null || parameters.Count < 1) return; + var allParameters = pathEntry.Value.Parameters.ToList(); + allParameters.AddRange(operation.Parameters); + foreach (var param in allParameters.Where(p => p.In == ParameterLocation.Cookie)) + { + if (parameters.TryGetValue(param.Name, out var value) && value != null) cookies.Add(param.Name, value.ToString()!); + else if (param.Required) throw new NullReferenceException($"Failed to find the definition of the required parameter '{param.Name}' in the OpenAPI operation with id '{operationId}'"); + } + foreach (var param in allParameters.Where(p => p.In == ParameterLocation.Header)) + { + if (parameters.TryGetValue(param.Name, out var value) && value != null) headers.Add(param.Name, value.ToString()!); + else if (param.Required) throw new NullReferenceException($"Failed to find the definition of the required parameter '{param.Name}' in the OpenAPI operation with id '{operationId}'"); + } + foreach (var param in allParameters.Where(p => p.In == ParameterLocation.Path)) + { + if (parameters.TryGetValue(param.Name, out var value) && value != null) path = path.Replace($"{{{param.Name}}}", value.ToString()); + else if (param.Required) throw new NullReferenceException($"Failed to find the definition of the required parameter '{param.Name}' in the OpenAPI operation with id '{operationId}'"); + } + var queryParameters = new Dictionary(); + foreach (var param in allParameters.Where(p => p.In == ParameterLocation.Query)) + { + if (parameters.TryGetValue(param.Name, out var value) && value != null) queryParameters.Add(param.Name, value.ToString()!); + else if (param.Required) throw new NullReferenceException($"Failed to find the definition of the required parameter '{param.Name}' in the OpenAPI operation with id '{operationId}'"); + } + query = string.Join("&", queryParameters.Select(kvp => $"{kvp.Key}={kvp.Value}")); + if (operation.RequestBody != null) + { + if (parameters.TryGetValue("body", out var bodyValue) && bodyValue != null) + { + body = bodyValue; + } + else + { + body = parameters; + } + if (body == null && operation.RequestBody.Required) throw new NullReferenceException($"Failed to determine the required body parameter for the OpenAPI operation with id '{operationId}'"); + } + } + + async Task BuildParametersAsync(CancellationToken cancellationToken = default) + { + if (openApi == null || operation == null) throw new InvalidOperationException("The executor must be initialized before execution"); + if (openApi.Parameters == null) return; + var arguments = GetExpressionEvaluationArguments(); + var evaluated = await Task.Workflow.Expressions.EvaluateAsync(openApi.Parameters, Task.Instance.Input, arguments, cancellationToken).ConfigureAwait(false); + if (evaluated is JsonObject jsonObject) + { + parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var property in jsonObject) parameters[property.Key] = property.Value?.Deserialize()!; + } + } + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + if (openApi == null || operation == null) throw new InvalidOperationException("The executor must be initialized before execution"); + JsonNode? output = null; + var success = false; + foreach (var server in servers) + { + var requestUri = $"{server}{path}"; + if (requestUri.StartsWith("//")) requestUri = $"https:{requestUri}"; + if (!string.IsNullOrWhiteSpace(query)) requestUri += $"?{query}"; + using var request = new HttpRequestMessage(httpMethod, requestUri); + foreach (var header in headers) request.Headers.TryAddWithoutValidation(header.Key, header.Value); + if (cookies.Count > 0) request.Headers.Add("Cookie", string.Join(";", cookies.Select(kvp => $"{kvp.Key}={kvp.Value}"))); + if (body != null) + { + var bodyJson = body is JsonNode jsonNode ? jsonNode.ToJsonString() : JsonSerializer.Serialize(body); + request.Content = new StringContent(bodyJson, Encoding.UTF8, MediaTypeNames.Application.Json); + } + using var httpClient = httpClientFactory.CreateClient(); + if (openApi.Authentication != null) + { + var authResult = await authenticationHandler.HandleAsync(openApi.Authentication, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.Scheme, authResult.Value); + } + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + if (response.StatusCode == HttpStatusCode.ServiceUnavailable) continue; + var successRange = openApi.Redirect ? 399 : 299; + if ((int)response.StatusCode < 200 || (int)response.StatusCode > successRange) + { + var detail = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + logger.LogError("Failed to execute the OpenAPI operation '{operationId}' at '{uri}'. The remote server responded with a non-success status code '{statusCode}'.", operation.OperationId, response.RequestMessage!.RequestUri, response.StatusCode); + if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Response content:\r\n{responseContent}", detail ?? "None"); + await SetErrorAsync(Error.Communication(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), (ushort)response.StatusCode, detail), cancellationToken).ConfigureAwait(false); + return; + } + var responseText = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(responseText)) + { + try { output = JsonNode.Parse(responseText); } + catch { output = JsonValue.Create(responseText); } + } + output = openApi.Output switch + { + HttpOutputFormat.Response => JsonSerializer.SerializeToNode(new HttpResponse() + { + Request = new() + { + Method = request.Method.Method, + Uri = request.RequestUri!, + Headers = [.. request.Headers.ToDictionary(h => h.Key, h => string.Join(',', h.Value))] + }, + Headers = [.. response.Headers.ToDictionary(h => h.Key, h => string.Join(',', h.Value))], + StatusCode = (int)response.StatusCode, + Content = output + })?.AsObject(), + _ => output + }; + success = true; + break; + } + if (!success) throw new HttpRequestException($"Failed to execute the Open API operation with id '{operation.OperationId}': No service available", null, HttpStatusCode.ServiceUnavailable); + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + + static HttpMethod ToHttpMethod(OperationType operationType) => operationType switch + { + OperationType.Get => HttpMethod.Get, + OperationType.Post => HttpMethod.Post, + OperationType.Put => HttpMethod.Put, + OperationType.Delete => HttpMethod.Delete, + OperationType.Options => HttpMethod.Options, + OperationType.Head => HttpMethod.Head, + OperationType.Patch => HttpMethod.Patch, + OperationType.Trace => HttpMethod.Trace, + _ => throw new NotSupportedException($"The specified operation type '{operationType}' is not supported") + }; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/RaiseTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/RaiseTaskExecutor.cs new file mode 100644 index 0000000..ba19a7c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/RaiseTaskExecutor.cs @@ -0,0 +1,66 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class RaiseTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var input = Task.Instance.Input; + var errorDefinition = Task.Definition.Raise.Error.Match( + e => e, + reference => + { + if (string.IsNullOrWhiteSpace(reference)) throw new NullReferenceException("The error to raise must be defined (or referenced)"); + if (Task.Workflow.Definition.Use is null || Task.Workflow.Definition.Use.Errors is null || !Task.Workflow.Definition.Use.Errors!.TryGetValue(reference, out var error) || error is null) throw new NullReferenceException($"Failed to find the referenced error definition '{reference}'"); + return error; + }); + var status = errorDefinition.Status is string expression + ? expression.IsRuntimeExpression() + ? await Task.Workflow.Expressions.EvaluateAsync(errorDefinition.Status, input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false) + : ushort.Parse(expression) + : ushort.Parse(errorDefinition.Status.ToString()!); + var type = errorDefinition.Type.IsRuntimeExpression() + ? (await Task.Workflow.Expressions.EvaluateAsync(errorDefinition.Type, input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false))! + : new(errorDefinition.Type, UriKind.RelativeOrAbsolute); + var title = errorDefinition.Title.IsRuntimeExpression() + ? (await Task.Workflow.Expressions.EvaluateAsync(errorDefinition.Title, input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false))! + : errorDefinition.Title; + var detail = string.IsNullOrWhiteSpace(errorDefinition.Detail) ? null : errorDefinition.Detail!.IsRuntimeExpression() + ? await Task.Workflow.Expressions.EvaluateAsync(errorDefinition.Detail!, input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false) + : errorDefinition.Detail; + var errorInstance = new Error() + { + Status = status, + Type = type, + Title = title, + Detail = detail, + Instance = new(Task.Instance.Reference.ToString(), UriKind.Relative) + }; + await SetErrorAsync(errorInstance, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ScriptRunTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ScriptRunTaskExecutor.cs new file mode 100644 index 0000000..e1de17b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ScriptRunTaskExecutor.cs @@ -0,0 +1,93 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute script s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The service used to read external resources +/// The service used to provide s +/// The current +public sealed class ScriptRunTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, IExternalResourceReader externalResourceReader, IScriptExecutorProvider scriptExecutorProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var processDefinition = Task.Definition.Run.Script!; + var executor = scriptExecutorProvider.GetExecutor(processDefinition.Language) ?? throw new NullReferenceException($"Failed to find a script executor for the specified language '{processDefinition.Language}'"); + var script = processDefinition.Code; + if (string.IsNullOrWhiteSpace(script)) + { + if (processDefinition.Source == null) throw new NullReferenceException("The script's code or source must be set"); + using var stream = await externalResourceReader.ReadAsync(processDefinition.Source, Task.Workflow.Definition, cancellationToken).ConfigureAwait(false); + using var streamReader = new StreamReader(stream); + script = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + } + var expressionArguments = GetExpressionEvaluationArguments(); + List? arguments = null; + if (processDefinition.Arguments != null) + { + arguments = []; + foreach (var kvp in processDefinition.Arguments) + { + var value = await EvaluateAndSerializeAsync(kvp.Value, expressionArguments, cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(value) && value != "True" && value != "False") arguments.AddRange([$"--{kvp.Key}", value]); + } + } + Dictionary? environment = null; + if (processDefinition.Environment != null) + { + environment = []; + foreach (var kvp in processDefinition.Environment) + { + var value = await EvaluateAndSerializeAsync(kvp.Value, expressionArguments, cancellationToken).ConfigureAwait(false); + if (value != null) environment[kvp.Key] = value; + } + } + var process = await executor.ExecuteAsync(script, arguments, environment, cancellationToken).ConfigureAwait(false); + if (Task.Definition.Run.Await == false) + { + await SetResultAsync(new JsonObject(), Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + var rawOutput = (await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).Trim(); + var errorMessage = (await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).Trim(); + if (process.ExitCode == 0) await SetResultAsync(new JsonObject { ["output"] = rawOutput }, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + else await SetErrorAsync(Error.Runtime(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), errorMessage), cancellationToken).ConfigureAwait(false); + process.Dispose(); + } + + async Task EvaluateAndSerializeAsync(object? value, JsonObject? expressionArguments, CancellationToken cancellationToken) + { + if (value == null) return null; + if (value is string str && str.IsRuntimeExpression()) + { + var evaluated = await Task.Workflow.Expressions.EvaluateAsync(str, Task.Instance.Input, expressionArguments, cancellationToken).ConfigureAwait(false); + if (evaluated == null) return null; + if (evaluated is JsonValue jsonValue) return jsonValue.ToString(); + return evaluated.ToJsonString(); + } + if (value.GetType().IsValueType || value is string) return value.ToString(); + return JsonSerializer.Serialize(value); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/SetTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/SetTaskExecutor.cs new file mode 100644 index 0000000..11bb406 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/SetTaskExecutor.cs @@ -0,0 +1,38 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class SetTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var output = Task.Instance.Input is JsonObject inputObject ? (JsonObject)inputObject.DeepClone() : []; + var result = await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.Set, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false); + if (result is JsonObject resultObject) foreach (var (key, value) in resultObject) output[key] = value?.DeepClone(); + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ShellRunTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ShellRunTaskExecutor.cs new file mode 100644 index 0000000..907ae74 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/ShellRunTaskExecutor.cs @@ -0,0 +1,66 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute shell s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class ShellRunTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var processDefinition = Task.Definition.Run.Shell!; + var fileInfo = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/bin/bash" : "cmd.exe"; + var shellArgs = new List(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) shellArgs.Add("-c"); + else shellArgs.Add("/c"); + shellArgs.Add(processDefinition.Command); + if (processDefinition.Arguments != null) shellArgs.AddRange(processDefinition.Arguments); + var startInfo = new ProcessStartInfo(fileInfo, shellArgs) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + if (processDefinition.Environment != null) foreach (var kvp in processDefinition.Environment) startInfo.EnvironmentVariables[kvp.Key] = kvp.Value; + var process = Process.Start(startInfo) ?? throw new NullReferenceException($"Failed to create the shell process defined at '{Task.Instance.Reference}'"); + try + { + if (Task.Definition.Run.Await == false) + { + await SetResultAsync(new JsonObject(), Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + var rawOutput = (await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).Trim(); + var errorMessage = (await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).Trim(); + if (process.ExitCode == 0) await SetResultAsync(new JsonObject { ["output"] = rawOutput }, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + else await SetErrorAsync(Error.Runtime(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), errorMessage), cancellationToken).ConfigureAwait(false); + } + finally + { + process.Dispose(); + } + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/SwitchTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/SwitchTaskExecutor.cs new file mode 100644 index 0000000..269e57f --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/SwitchTaskExecutor.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. + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class SwitchTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + MapEntry? match = null; + var defaultCase = Task.Definition.Switch.FirstOrDefault(kvp => string.IsNullOrWhiteSpace(kvp.Value.When)); + foreach (var @case in Task.Definition.Switch.Where(c => !string.IsNullOrWhiteSpace(c.Value.When))) + { + if (!await Task.Workflow.Expressions.EvaluateConditionAsync(@case.Value.When!, Task.Instance.Input, GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false)) continue; + match = @case; + break; + } + if (match != null) await SetResultAsync(Task.Instance.Input, match.Value.Then, cancellationToken).ConfigureAwait(false); + else if (defaultCase != null) await SetResultAsync(Task.Instance.Input, defaultCase.Value.Then, cancellationToken).ConfigureAwait(false); + else await SetResultAsync(Task.Instance.Input, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/TryTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/TryTaskExecutor.cs new file mode 100644 index 0000000..2b3434b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/TryTaskExecutor.cs @@ -0,0 +1,137 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class TryTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var taskDefinition = new DoTaskDefinition() { Do = Task.Definition.Try }; + var tryInstance = await Task.Workflow.CreateTaskAsync(taskDefinition, JsonPointer.Create("try"), Task.Instance.Input, Task, false, cancellationToken).ConfigureAwait(false); + var executor = await CreateTryExecutorAsync(tryInstance, taskDefinition, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + /// + protected override async Task RetryCoreAsync(Error cause, CancellationToken cancellationToken) + { + var taskDefinition = new DoTaskDefinition() { Do = Task.Definition.Try }; + var retryInstance = await Task.Workflow.CreateTaskAsync(taskDefinition, JsonPointer.Create("retry", "try"), Task.Instance.Input, Task, false, cancellationToken).ConfigureAwait(false); + var executor = await CreateTryExecutorAsync(retryInstance, taskDefinition, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + async Task CreateTryExecutorAsync(ITaskInstance instance, TaskDefinition definition, CancellationToken cancellationToken) + { + var executor = await base.CreateTaskExecutorAsync(instance, definition, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + executor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async ex => await OnTryFaultedAsync(executor, ex, CancellationTokenSource?.Token ?? default).ConfigureAwait(false), + async () => await OnTryCompletedAsync(executor, CancellationTokenSource?.Token ?? default).ConfigureAwait(false) + ); + return executor; + } + + async Task OnTryFaultedAsync(ITaskExecutor executor, Exception ex, CancellationToken cancellationToken) + { + var error = ex is RuntimeErrorException errorEx ? errorEx.Error : new Error() + { + Status = ErrorStatus.Runtime, + Type = ErrorType.Runtime, + Title = ErrorTitle.Runtime, + Detail = ex.Message + }; + Executors.Remove(executor); + var hasRetryPolicy = Task.Definition.Catch.Retry != null; + if (hasRetryPolicy) + { + var retryPolicy = Task.Definition.Catch.Retry!.Match( + policy => policy, + reference => + { + if (Task.Workflow.Definition.Use?.Retries?.TryGetValue(reference, out var referencedRetry) == true) return referencedRetry; + return null; + } + ); + if (retryPolicy != null) + { + var limit = retryPolicy.Limit; + var limitReached = false; + if (limit?.Attempt?.Count != null) + { + var retryCount = 0; + await foreach (var subtask in Task.GetSubTasksAsync(cancellationToken).ConfigureAwait(false)) if (subtask.Name?.StartsWith("retry") == true) retryCount++; + if (retryCount >= limit.Attempt.Count) limitReached = true; + } + if (limitReached) await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + else await RetryAsync(error, cancellationToken).ConfigureAwait(false); + return; + } + } + if (Task.Definition.Catch.Do != null) + { + var handlerDefinition = new DoTaskDefinition() { Do = Task.Definition.Catch.Do }; + var handlerInstance = await Task.Workflow.CreateTaskAsync(handlerDefinition, JsonPointer.Create("catch", "do"), Task.Instance.Input, Task, false, cancellationToken).ConfigureAwait(false); + var arguments = Task.Arguments?.DeepClone().AsObject()! ?? []; + arguments[Task.Definition.Catch.As ?? RuntimeExpressions.Arguments.Error] = JsonSerializer.SerializeToNode(error)!; + var handlerExecutor = await base.CreateTaskExecutorAsync(handlerInstance, handlerDefinition, Task.Workflow.Instance.ContextData, arguments, cancellationToken).ConfigureAwait(false); + handlerExecutor.SubscribeAsync( + _ => System.Threading.Tasks.Task.CompletedTask, + async handlerEx => await OnHandlerFaultAsync(handlerExecutor, cancellationToken).ConfigureAwait(false), + async () => await OnHandlerCompletedAsync(handlerExecutor, cancellationToken).ConfigureAwait(false) + ); + await handlerExecutor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + return; + } + await SetResultAsync(null, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + + async Task OnTryCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + if (Task.Workflow.Instance.ContextData != executor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(executor.Task.Workflow.Instance.ContextData, cancellationToken).ConfigureAwait(false); + var output = executor.Task.Instance.Output ?? new JsonObject(); + Executors.Remove(executor); + var then = executor.Task.Instance.Next == FlowDirective.End ? FlowDirective.End : Task.Definition.Then; + await SetResultAsync(output, then, cancellationToken).ConfigureAwait(false); + } + + async Task OnHandlerFaultAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + var error = executor.Task.Instance.Error ?? throw new NullReferenceException(); + Executors.Remove(executor); + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + } + + async Task OnHandlerCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + if (Task.Workflow.Instance.ContextData != executor.Task.Workflow.Instance.ContextData) await Task.SetContextDataAsync(executor.Task.Workflow.Instance.ContextData, cancellationToken).ConfigureAwait(false); + var output = executor.Task.Instance.Output ?? new JsonObject(); + Executors.Remove(executor); + var then = executor.Task.Instance.Next == FlowDirective.End ? FlowDirective.End : Task.Definition.Then; + await SetResultAsync(output, then, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/WaitTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/WaitTaskExecutor.cs new file mode 100644 index 0000000..2840cda --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/WaitTaskExecutor.cs @@ -0,0 +1,36 @@ +// 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.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +public sealed class WaitTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + await System.Threading.Tasks.Task.Delay(Task.Definition.Wait.ToTimeSpan(), cancellationToken).ConfigureAwait(false); + await SetResultAsync(Task.Instance.Input, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/WorkflowRunTaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/WorkflowRunTaskExecutor.cs new file mode 100644 index 0000000..d17ace0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/Executors/WorkflowRunTaskExecutor.cs @@ -0,0 +1,108 @@ +// 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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.Runtime.Services.Executors; + +/// +/// Represents an implementation used to execute workflow s +/// +/// The current +/// The service used to perform logging +/// The service used to create s +/// The service used to create s +/// The service used to provide implementations +/// The current +/// The service used to manage s +public sealed class WorkflowRunTaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, + ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task, IWorkflowDefinitionStore definitions) + : TaskExecutor(serviceProvider, logger, executionContextFactory, executorFactory, schemaHandlerProvider, task) +{ + + IWorkflowProcess? subflow; + bool cancelling; + + WorkflowProcessDefinition ProcessDefinition => Task.Definition.Run.Workflow!; + + /// + protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) + { + var processDefinition = ProcessDefinition; + var workflowDefinition = await definitions.GetAsync(processDefinition.Namespace, processDefinition.Name, processDefinition.Version, cancellationToken).ConfigureAwait(false) + ?? throw new NullReferenceException($"Failed to find the specified workflow definition '{processDefinition.Namespace}.{processDefinition.Name}:{processDefinition.Version ?? "latest"}'"); + var input = processDefinition.Input == null + ? Task.Instance.Input as JsonObject ?? [] + : (await Task.Workflow.Expressions.EvaluateAsync(processDefinition.Input, Task.Instance.Input ?? new JsonObject(), GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false))?.AsObject() ?? []; + subflow = await Task.Workflow.Runtime.RunAsync(workflowDefinition, input, executionOptions: null, cancellationToken).ConfigureAwait(false); + if (Task.Definition.Run.Await == false) + { + await SetResultAsync(new JsonObject(), Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + JsonNode? output = null; + Error? error = null; + using var subscription = subflow.Subscribe(e => + { + switch (e.Type) + { + case WorkflowLifeCycleEventType.Completed: + output = e.Data as JsonNode; + break; + case WorkflowLifeCycleEventType.Faulted: + error = e.Data as Error; + break; + } + }); + try + { + await subflow.WaitAsync(cancellationToken).ConfigureAwait(false); + } + catch (RuntimeErrorException ex) + { + await SetErrorAsync(error ?? ex.Error, cancellationToken).ConfigureAwait(false); + return; + } + catch (OperationCanceledException) + { + if (cancelling) return; + await SetErrorAsync(Error.Runtime(new Uri(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute), $"The execution of the subflow '{ProcessDefinition.Namespace}.{ProcessDefinition.Name}:{ProcessDefinition.Version ?? "latest"}' has been cancelled"), cancellationToken).ConfigureAwait(false); + return; + } + if (error != null) + { + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + return; + } + await SetResultAsync(output, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + + /// + protected override async Task CancelCoreAsync(CancellationToken cancellationToken) + { + if (subflow != null && Task.Definition.Run.Await != false) + { + try + { + cancelling = true; + await subflow.CancelAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogError("An error occurred while cancelling the subflow '{subflow}': {ex}", $"{ProcessDefinition.Namespace}.{ProcessDefinition.Name}:{ProcessDefinition.Version ?? "latest"}", ex); + } + } + await base.CancelCoreAsync(cancellationToken).ConfigureAwait(false); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/ExternalResourceReader.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/ExternalResourceReader.cs new file mode 100644 index 0000000..ba772c3 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/ExternalResourceReader.cs @@ -0,0 +1,47 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The current +/// The used to read external resources over http +public sealed class ExternalResourceReader(IServiceProvider serviceProvider, HttpClient httpClient) + : IExternalResourceReader +{ + + /// + public async Task ReadAsync(ExternalResourceDefinition resource, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(resource); + var endpointUri = resource.Endpoint.Match( + ep => ep.Uri, + uri => uri); + return endpointUri.Scheme switch + { + "file" => new FileStream(endpointUri.LocalPath, FileMode.Open), + "http" or "https" => await ReadOverHttpAsync(endpointUri, resource, workflow, cancellationToken).ConfigureAwait(false), + _ => throw new NotSupportedException($"Cannot retrieve resource at uri '{endpointUri}': the scheme '{endpointUri.Scheme}' is not supported") + }; + } + + async Task ReadOverHttpAsync(Uri endpointUri, ExternalResourceDefinition resource, WorkflowDefinition? workflow, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(resource); + if (resource.Endpoint.TryGetAsT1(out var endpoint) && endpoint is not null && endpoint.Authentication is not null) await httpClient.ConfigureAuthenticationAsync(endpoint.Authentication, serviceProvider, workflow, cancellationToken).ConfigureAwait(false); + return await httpClient.GetStreamAsync(endpointUri, cancellationToken).ConfigureAwait(false); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryCloudEventBus.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryCloudEventBus.cs new file mode 100644 index 0000000..2fe00b6 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryCloudEventBus.cs @@ -0,0 +1,38 @@ +// 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.Runtime.Services; + +/// +/// Represents an in-memory implementation of the interface +/// +public sealed class InMemoryCloudEventBus + : ICloudEventBus, IDisposable +{ + + readonly Subject subject = new(); + + /// + public Task PublishAsync(ICloudEvent e, CancellationToken cancellationToken = default) + { + subject.OnNext(e); + return Task.CompletedTask; + } + + /// + public Task> SubscribeAsync(CancellationToken cancellationToken = default) => Task.FromResult(subject.AsObservable()); + + /// + public void Dispose() => subject.Dispose(); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryTaskStateStore.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryTaskStateStore.cs new file mode 100644 index 0000000..587a085 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryTaskStateStore.cs @@ -0,0 +1,66 @@ +// 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.Runtime.Services; + +/// +/// Represents an in-memory implementation of the interface +/// +public sealed class InMemoryTaskStateStore + : ITaskStore +{ + + readonly ConcurrentDictionary tasks = []; + + /// + public Task AddAsync(ITaskInstance instance, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(instance); + tasks[GetCacheKey(instance.WorkflowId, instance.Id)] = instance; + return Task.FromResult(instance); + } + + /// + public Task GetAsync(string workflowId, string taskId, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(workflowId); + ArgumentException.ThrowIfNullOrWhiteSpace(taskId); + return tasks.TryGetValue(GetCacheKey(workflowId, taskId), out var state) && state is not null ? Task.FromResult(state) : throw new KeyNotFoundException($"Task with id '{taskId}' not found in workflow '{workflowId}'"); + } + + /// + public IAsyncEnumerable ListAsync(string workflowId, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(workflowId); + return tasks.Values.Where(t => t.WorkflowId == workflowId).ToAsyncEnumerable(); + } + + /// + public IAsyncEnumerable ListAsync(string workflowId, string taskId, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(workflowId); + ArgumentException.ThrowIfNullOrWhiteSpace(taskId); + return tasks.Values.Where(t => t.WorkflowId == workflowId && t.ParentId == taskId).ToAsyncEnumerable(); + } + + /// + public Task UpdateAsync(ITaskInstance instance, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(instance); + tasks[GetCacheKey(instance.WorkflowId, instance.Id)] = instance; + return Task.FromResult(instance); + } + + static string GetCacheKey(string workflowId, string taskId) => $"{workflowId}:{taskId}"; + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryWorkflowDefinitionStore.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryWorkflowDefinitionStore.cs new file mode 100644 index 0000000..4b5278b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryWorkflowDefinitionStore.cs @@ -0,0 +1,50 @@ +// 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.Runtime.Services; + +/// +/// Represents an in-memory implementation of the interface +/// +public sealed class InMemoryWorkflowDefinitionStore + : IWorkflowDefinitionStore +{ + + readonly List definitions = []; + + /// + public Task AddAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + definitions.Add(definition); + return Task.CompletedTask; + } + + /// + public Task GetAsync(string @namespace, string name, string? version = null, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(@namespace); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return Task.FromResult(definitions.FirstOrDefault(d => d.Document.Namespace.Equals(@namespace, StringComparison.OrdinalIgnoreCase) && d.Document.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && (version is null || d.Document.Version.Equals(version, StringComparison.OrdinalIgnoreCase)))); + } + + /// + public IAsyncEnumerable ListAsync(CancellationToken cancellationToken = default) => definitions.ToAsyncEnumerable(); + + /// + public IAsyncEnumerable ListAsync(string @namespace, CancellationToken cancellationToken = default) => definitions.Where(d => d.Document.Namespace.Equals(@namespace, StringComparison.OrdinalIgnoreCase)).ToAsyncEnumerable(); + + /// + public IAsyncEnumerable ListAsync(string @namespace, string name, CancellationToken cancellationToken = default) => definitions.Where(d => d.Document.Namespace.Equals(@namespace, StringComparison.OrdinalIgnoreCase) && d.Document.Name.Equals(name, StringComparison.OrdinalIgnoreCase)).ToAsyncEnumerable(); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryWorkflowStore.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryWorkflowStore.cs new file mode 100644 index 0000000..91b6012 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/InMemoryWorkflowStore.cs @@ -0,0 +1,53 @@ +// 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 Microsoft.Extensions.Caching.Memory; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents an in-memory implementation of the interface +/// +/// The instance used to store workflow states +public sealed class InMemoryWorkflowStore(IMemoryCache cache) + : IWorkflowStore +{ + + /// + public Task AddAsync(WorkflowDefinition definition, JsonObject? input = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + var state = new WorkflowInstance() + { + Definition = definition.GetReference(), + Input = input + }; + cache.Set(state.Id, state); + return Task.FromResult((IWorkflowInstance)state); + } + + /// + public Task GetAsync(string workflowId, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(workflowId); + return cache.TryGetValue(workflowId, out IWorkflowInstance? state) && state is not null ? Task.FromResult(state) : throw new KeyNotFoundException($"Workflow with id '{workflowId}' not found"); + } + + /// + public Task UpdateAsync(IWorkflowInstance state, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(state); + return Task.FromResult(cache.Set(state.Id, state)); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/JQRuntimeExpressionEvaluator.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/JQRuntimeExpressionEvaluator.cs new file mode 100644 index 0000000..434a032 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/JQRuntimeExpressionEvaluator.cs @@ -0,0 +1,97 @@ +// 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.Runtime.Services; + +/// +/// Represents an that uses the JQ language to evaluate expressions +/// +public sealed class JQRuntimeExpressionEvaluator + : IRuntimeExpressionEvaluator +{ + + /// + public bool Supports(string language) => language.Trim().Equals(RuntimeExpressions.Languages.JQ, StringComparison.OrdinalIgnoreCase); + + /// + public async Task EvaluateAsync(string expression, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + ArgumentNullException.ThrowIfNull(input); + expression = expression.Trim(); + if (expression.StartsWith("${")) expression = expression[2..^1].Trim(); + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + var startInfo = new ProcessStartInfo() + { + FileName = "jq", + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + startInfo.ArgumentList.Add(expression); + if (arguments is not null) foreach (var property in arguments) + { + startInfo.ArgumentList.Add("--argjson"); + startInfo.ArgumentList.Add(property.Key); + startInfo.ArgumentList.Add(JsonSerializer.Serialize(property.Value, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonNode)); + } + var files = new List(); + var maxLength = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 8000 : 32699; + if (startInfo.ArgumentList.Any(a => a.Length >= maxLength)) + { + startInfo.ArgumentList.Clear(); + var filterFile = Path.GetTempFileName(); + File.WriteAllText(filterFile, expression); + files.Add(filterFile); + startInfo.ArgumentList.Add("-f"); + startInfo.ArgumentList.Add(filterFile); + if (arguments is not null) foreach (var property in arguments) + { + var argFile = Path.GetTempFileName(); + File.WriteAllText(argFile, JsonSerializer.Serialize(property.Value, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonNode)); + files.Add(argFile); + startInfo.ArgumentList.Add("--argfile"); + startInfo.ArgumentList.Add(property.Key); + startInfo.ArgumentList.Add(argFile); + } + } + startInfo.ArgumentList.Add("-c"); + using var process = new Process() + { + StartInfo = startInfo + }; + var cancellationRegistration = cancellationToken.Register(() => + { + try + { + process.Kill(); + } + catch { } + }); + process.Start(); + process.StandardInput.Write(JsonSerializer.Serialize(input, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonNode)); + process.StandardInput.Close(); + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + cancellationRegistration.Unregister(); + cancellationRegistration.Dispose(); + foreach (var file in files) try { File.Delete(file); } catch { } + if (process.ExitCode != 0) throw new Exception($"An error occurred while evaluating the specified expression: {error}"); + if (string.IsNullOrWhiteSpace(output)) return null; + try { return JsonSerializer.Deserialize(output, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonNode); } + catch (JsonException ex) { throw new Exception($"An error occurred while deserializing the output of the expression evaluation: {ex.Message}"); } + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/JSRuntimeExpressionEvaluator.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/JSRuntimeExpressionEvaluator.cs new file mode 100644 index 0000000..23969ba --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/JSRuntimeExpressionEvaluator.cs @@ -0,0 +1,81 @@ +// 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 Jint; +using Jint.Runtime.Interop; +using System.Collections; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents an that uses the JavaScript language to evaluate expressions +/// +public sealed class JSRuntimeExpressionEvaluator + : IRuntimeExpressionEvaluator +{ + + /// + public bool Supports(string language) => language.Trim().Equals(RuntimeExpressions.Languages.JavaScript, StringComparison.OrdinalIgnoreCase); + + /// + public async Task EvaluateAsync(string expression, JsonNode input, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + ArgumentNullException.ThrowIfNull(input); + expression = expression.Trim(); + if (expression.StartsWith("${")) expression = expression[2..^1].Trim(); + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + var js = new Engine(options => + { + options.LimitMemory(1_000_000) + .TimeoutInterval(TimeSpan.FromMilliseconds(500)) + .MaxStatements(500) + .LimitRecursion(16) + .CancellationToken(cancellationToken) + .SetWrapObjectHandler((engine, target, type) => + { + var instance = ObjectWrapper.Create(engine, target); + if (DetermineIfObjectIsArrayLikeClrCollection(target.GetType())) instance.Prototype = engine.Intrinsics.Array.PrototypeObject; + return instance; + }) + ; + }); + js.SetValue("$", input); + if (arguments != null) foreach (var property in arguments) js.SetValue(property.Key, property.Value); + var jsValue = await js.Evaluate(expression).UnwrapIfPromiseAsync(cancellationToken); + js.SetValue("__result", jsValue); + var json = js.Evaluate("JSON.stringify(__result)").AsString(); + try + { + return JsonNode.Parse(json); + } + catch (JsonException ex) + { + throw new Exception($"An error occurred while deserializing the output of the expression evaluation: {ex.Message}"); + } + } + + static bool DetermineIfObjectIsArrayLikeClrCollection(Type type) + { + var isDictionary = typeof(IDictionary).IsAssignableFrom(type); + if (isDictionary) return false; + if (typeof(ICollection).IsAssignableFrom(type)) return true; + foreach (var interfaceType in type.GetInterfaces()) + { + if (!interfaceType.IsGenericType) continue; + if (interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>) || interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>)) return true; + } + return false; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/JsonSchemaHandler.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/JsonSchemaHandler.cs new file mode 100644 index 0000000..481fd6c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/JsonSchemaHandler.cs @@ -0,0 +1,60 @@ +// 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.Runtime.Services; + +/// +/// Represents the implementation used to handle JSON schemas +/// +/// The service used to read external resources +public sealed class JsonSchemaHandler(IExternalResourceReader externalResourceReader) + : ISchemaHandler +{ + + /// + public bool Supports(string format) => format.Equals(SchemaFormat.Json, StringComparison.OrdinalIgnoreCase); + + /// + public async Task ValidateAsync(JsonNode graph, SchemaDefinition schema, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(graph); + ArgumentNullException.ThrowIfNull(schema); + if (!Supports(schema.Format)) throw new NotSupportedException($"The specified schema format '{schema.Format}' is not supported in this context"); + var json = string.Empty; + if (schema.Resource is null) + { + if (schema.Document is null) throw new InvalidOperationException("The specified schema definition does not contain a valid resource reference or an embedded document"); + json = schema.Document.Match + ( + jsonObject => JsonSerializer.Serialize(schema.Document, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonObject), + str => str + ); + } + else + { + using var stream = await externalResourceReader.ReadAsync(schema.Resource, cancellationToken: cancellationToken).ConfigureAwait(false); + using var streamReader = new StreamReader(stream); + json = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + } + var jsonSchema = JsonSchema.FromText(json); + var jsonDocument = JsonSerializer.SerializeToElement(graph, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonObject)!; + var options = new EvaluationOptions() + { + OutputFormat = OutputFormat.List + }; + var results = jsonSchema.Evaluate(jsonDocument, options); + if (results.IsValid) return SchemaValidationResult.Succeeded(); + return SchemaValidationResult.Failed(results.Details?.Where(d => d.Errors is not null).SelectMany(d => d.Errors!).GroupBy(e => e.Key).Select(e => new KeyValuePair>(e.Key, e.Select(e => e.Value))) ?? []); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/NodeJSScriptExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/NodeJSScriptExecutor.cs new file mode 100644 index 0000000..9ebe623 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/NodeJSScriptExecutor.cs @@ -0,0 +1,48 @@ +// 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.Runtime.Services; + +/// +/// Represents the NodeJS implementation of the interface +/// +public sealed class NodeJSScriptExecutor + : IScriptExecutor +{ + + /// + public bool Supports(string language) => language.Equals("js", StringComparison.OrdinalIgnoreCase); + + /// + public async Task ExecuteAsync(string script, IEnumerable? arguments = null, IDictionary? environment = null, CancellationToken cancellationToken = default) + { + var guid = Guid.NewGuid().ToString("N")[15..]; + var directory = Path.Combine(Path.GetTempPath(), guid); + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); + var fileName = "script.js"; + await File.WriteAllTextAsync(Path.Combine(directory, fileName), script, cancellationToken).ConfigureAwait(false); + var processStart = new ProcessStartInfo() + { + FileName = "node", + WorkingDirectory = directory, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + processStart.ArgumentList.Add(fileName); + arguments?.ToList().ForEach(processStart.ArgumentList.Add); + environment?.ToList().ForEach(e => processStart.Environment[e.Key] = e.Value); + return Process.Start(processStart) ?? throw new NullReferenceException("Failed to create a new process to evaluate the script"); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/OAuth2TokenManager.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/OAuth2TokenManager.cs new file mode 100644 index 0000000..81e972b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/OAuth2TokenManager.cs @@ -0,0 +1,159 @@ +// 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 Duende.IdentityModel; +using Duende.IdentityModel.Client; +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Tokens; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The service used to perform logging +/// The service used to perform HTTP requests +public sealed class OAuth2TokenManager(ILogger logger, HttpClient httpClient) + : IOAuth2TokenManager +{ + + readonly ConcurrentDictionary tokens = []; + + /// + public async Task GetTokenAsync(OAuth2AuthenticationSchemeDefinitionBase configuration, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(configuration); + var tokenKey = $"{configuration.Client?.Id}@{configuration.Authority}"; + if (tokens.TryGetValue(tokenKey, out var token) && token != null && !token.HasExpired) return token; + Uri tokenEndpoint; + if (configuration is OpenIDConnectSchemeDefinition) + { + var discoveryRequest = new DiscoveryDocumentRequest() + { + Address = configuration.Authority!.OriginalString, + Policy = new() + { + ValidateIssuerName = false, + ValidateEndpoints = false, + RequireHttps = false + } + }; + var discoveryDocument = await httpClient.GetDiscoveryDocumentAsync(discoveryRequest, cancellationToken).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(discoveryDocument.TokenEndpoint)) throw new NullReferenceException($"The token endpoint is not documented by the OIDC discovery document.{(discoveryDocument.IsError ? $" Discovery error [{discoveryDocument.ErrorType}]: {discoveryDocument.Error}" : string.Empty)}"); + tokenEndpoint = new(discoveryDocument.TokenEndpoint!); + } + else if (configuration is OAuth2AuthenticationSchemeDefinition oauth2) tokenEndpoint = oauth2.Endpoints?.Token ?? OAuth2AuthenticationEndpointsDefinition.TokenEndpoint; + else throw new NotSupportedException($"The specified scheme type '{configuration.GetType().FullName}' is not supported in this context"); + var properties = new Dictionary() + { + { "grant_type", configuration.Grant! } + }; + switch (configuration.Client?.Authentication) + { + case null: + if (!string.IsNullOrWhiteSpace(configuration.Client?.Id) && !string.IsNullOrWhiteSpace(configuration.Client?.Secret)) + { + properties["client_id"] = configuration.Client.Id!; + properties["client_secret"] = configuration.Client.Secret!; + } + break; + case OAuth2ClientAuthenticationMethod.Post: + ThrowIfInvalidClientCredentials(configuration.Client); + properties["client_id"] = configuration.Client.Id!; + properties["client_secret"] = configuration.Client.Secret!; + break; + case OAuth2ClientAuthenticationMethod.JwT: + ThrowIfInvalidClientCredentials(configuration.Client); + properties["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + properties["client_assertion"] = CreateClientAssertionJwt(configuration.Client.Id!, tokenEndpoint.OriginalString, new(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.Client.Secret!)), SecurityAlgorithms.HmacSha256)); + break; + case OAuth2ClientAuthenticationMethod.PrivateKey: + ThrowIfInvalidClientCredentials(configuration.Client); + throw new NotImplementedException(); //todo + case OAuth2ClientAuthenticationMethod.Basic: + break; + default: throw new NotSupportedException($"The specified OAUTH2 client authentication method '{configuration.Client?.Authentication}' is not supported"); + } + if (configuration.Scopes?.Count > 0) properties["scope"] = string.Join(" ", configuration.Scopes); + if (configuration.Audiences?.Count > 0) properties["audience"] = string.Join(" ", configuration.Audiences); + if (!string.IsNullOrWhiteSpace(configuration.Username)) properties["username"] = configuration.Username; + if (!string.IsNullOrWhiteSpace(configuration.Password)) properties["password"] = configuration.Password; + if (configuration.Subject != null) + { + properties["subject_token"] = configuration.Subject.Token; + properties["subject_token_type"] = configuration.Subject.Type; + } + if (configuration.Actor != null) + { + properties["actor_token"] = configuration.Actor.Token; + properties["actor_token_type"] = configuration.Actor.Type; + } + if (token != null && token.HasExpired && !string.IsNullOrWhiteSpace(token.RefreshToken)) + { + properties["grant_type"] = "refresh_token"; + properties["refresh_token"] = token.RefreshToken; + } + using var content = configuration.Request?.Encoding switch + { + null or OAuth2RequestEncoding.FormUrl => (HttpContent)new FormUrlEncodedContent(properties), + OAuth2RequestEncoding.Json => new StringContent(JsonSerializer.Serialize(properties, Sdk.Serialization.Json.JsonSerializationContext.Default.DictionaryStringString), Encoding.UTF8, MediaTypeNames.Application.Json), + _ => throw new NotSupportedException($"The specified OAUTH2 request encoding '{configuration.Request?.Encoding ?? OAuth2RequestEncoding.FormUrl}' is not supported") + }; + using var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint) { Content = content }; + if (configuration.Client?.Authentication == OAuth2ClientAuthenticationMethod.Basic) + { + ThrowIfInvalidClientCredentials(configuration.Client); + request.Headers.Authorization = new("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{configuration.Client.Id}:{configuration.Client.Secret}"))); + } + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + var json = await response.Content?.ReadAsStringAsync(cancellationToken)!; + if (!response.IsSuccessStatusCode) + { + logger.LogError("An error occurred while generating a new JWT token: {details}", json); + response.EnsureSuccessStatusCode(); + } + token = JsonSerializer.Deserialize(json, Serialization.Json.JsonSerializationContext.Default.OAuth2Token)!; + tokens[tokenKey] = token; + return token; + } + + static void ThrowIfInvalidClientCredentials(OAuth2AuthenticationClientDefinition? client) + { + if (string.IsNullOrWhiteSpace(client?.Id) || string.IsNullOrWhiteSpace(client?.Secret)) throw new NullReferenceException($"The client id and client secret must be configured when using the '{client?.Authentication}' OAUTH2 authentication method"); + } + + static string CreateClientAssertionJwt(string clientId, string audience, SigningCredentials signingCredentials) + { + ArgumentException.ThrowIfNullOrWhiteSpace(clientId); + ArgumentException.ThrowIfNullOrWhiteSpace(audience); + ArgumentNullException.ThrowIfNull(signingCredentials); + var claims = new List + { + new(JwtClaimTypes.Subject, clientId), + new(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), + new(JwtClaimTypes.IssuedAt, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) + }; + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims), + Issuer = clientId, + Audience = audience, + NotBefore = DateTime.UtcNow, + Expires = DateTime.UtcNow.AddMinutes(5), + SigningCredentials = signingCredentials + }; + var tokenHandler = new JsonWebTokenHandler(); + return tokenHandler.CreateToken(tokenDescriptor); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/PythonScriptExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/PythonScriptExecutor.cs new file mode 100644 index 0000000..900f511 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/PythonScriptExecutor.cs @@ -0,0 +1,48 @@ +// 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.Runtime.Services; + +/// +/// Represents the NodeJS implementation of the interface +/// +public sealed class PythonScriptExecutor + : IScriptExecutor +{ + + /// + public bool Supports(string language) => language.Equals("python", StringComparison.OrdinalIgnoreCase); + + /// + public async Task ExecuteAsync(string script, IEnumerable? arguments = null, IDictionary? environment = null, CancellationToken cancellationToken = default) + { + var guid = Guid.NewGuid().ToString("N")[15..]; + var directory = Path.Combine(Path.GetTempPath(), guid); + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); + var fileName = "script.py"; + await File.WriteAllTextAsync(Path.Combine(directory, fileName), script, cancellationToken).ConfigureAwait(false); + var processStart = new ProcessStartInfo() + { + FileName = "py", + WorkingDirectory = directory, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + processStart.ArgumentList.Add(fileName); + arguments?.ToList().ForEach(processStart.ArgumentList.Add); + environment?.ToList().ForEach(e => processStart.Environment[e.Key] = e.Value); + return Process.Start(processStart) ?? throw new NullReferenceException("Failed to create a new process to evaluate the script"); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/RunTaskExecutorRegistry.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/RunTaskExecutorRegistry.cs new file mode 100644 index 0000000..5f30903 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/RunTaskExecutorRegistry.cs @@ -0,0 +1,47 @@ +// 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.Runtime.Services; + +/// +/// Represents a registry used to map process type discriminators (e.g. "container", "shell") to their corresponding types +/// +public sealed class RunTaskExecutorRegistry +{ + + readonly Dictionary registry = []; + + /// + /// Registers the specified for the specified process type + /// + /// The process type discriminator to register the executor for (e.g. "container", "shell", "script", "workflow") + /// The type of to register + public void Register(string processType) + where TExecutor : class, ITaskExecutor + { + ArgumentException.ThrowIfNullOrWhiteSpace(processType); + registry[processType] = typeof(TExecutor); + } + + /// + /// Resolves the type registered for the specified process type + /// + /// The process type discriminator to resolve the executor for + /// The resolved executor type, if any + public Type? Resolve(string processType) + { + ArgumentException.ThrowIfNullOrWhiteSpace(processType); + return registry.TryGetValue(processType, out var executorType) ? executorType : null; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/RuntimeExpressionEvaluatorProvider.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/RuntimeExpressionEvaluatorProvider.cs new file mode 100644 index 0000000..88b421c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/RuntimeExpressionEvaluatorProvider.cs @@ -0,0 +1,32 @@ +// 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.Runtime.Services; + +/// +/// Represents an used to provide instances of +/// +/// An containing all registered s +public sealed class RuntimeExpressionEvaluatorProvider(IEnumerable evaluators) + : IRuntimeExpressionEvaluatorProvider +{ + + /// + public IRuntimeExpressionEvaluator? GetEvaluator(string language) + { + ArgumentException.ThrowIfNullOrWhiteSpace(language); + language = language.Trim(); + return evaluators.FirstOrDefault(e => e.Supports(language)); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/SchemaHandlerProvider.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/SchemaHandlerProvider.cs new file mode 100644 index 0000000..8dbfbed --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/SchemaHandlerProvider.cs @@ -0,0 +1,32 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// An containing all registered s +public sealed class SchemaHandlerProvider(IEnumerable handlers) + : ISchemaHandlerProvider +{ + + /// + public ISchemaHandler? GetHandler(string format) + { + ArgumentException.ThrowIfNullOrWhiteSpace(format); + format = format.Trim(); + return handlers.FirstOrDefault(h => h.Supports(format)); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/ScriptExecutorProvider.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/ScriptExecutorProvider.cs new file mode 100644 index 0000000..c1bb27c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/ScriptExecutorProvider.cs @@ -0,0 +1,31 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// An containing all registered implementations +public sealed class ScriptExecutorProvider(IEnumerable executors) + : IScriptExecutorProvider +{ + + /// + public IScriptExecutor? GetExecutor(string language) + { + ArgumentException.ThrowIfNullOrWhiteSpace(language); + return executors.FirstOrDefault(e => e.Supports(language)); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/SecretsManager.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/SecretsManager.cs new file mode 100644 index 0000000..579b2c0 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/SecretsManager.cs @@ -0,0 +1,59 @@ +// 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.Runtime.Services; + +/// +/// Represents a file-based implementation of the interface +/// +/// The service used to perform logging +/// The service used to access the current +public sealed class SecretsManager(ILogger logger, IOptions options) + : BackgroundService, ISecretsManager +{ + + readonly Dictionary secrets = []; + + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + var path = string.IsNullOrWhiteSpace(options.Value.Directory) ? SecretManagerOptions.DefaultDirectory : options.Value.Directory; + var directory = new DirectoryInfo(path); + if (!directory.Exists) directory.Create(); + foreach (var file in directory.GetFiles()) + { + using var stream = file.OpenRead(); + try + { + var secret = (await JsonSerializer.DeserializeAsync(stream, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonObject, stoppingToken))!; + secrets.Add(file.Name, secret); + } + catch (Exception ex) + { + logger.LogWarning("Skipped loading secret '{secretFile}': an exception occurred while deserializing the secret object: {ex}", file.Name, ex.Message); + continue; + } + } + } + catch (Exception ex) + { + logger.LogWarning("Failed to load secrets because there are none or because they are improperly configured. Error: {ex}", ex.Message); + } + } + + /// + public Task> GetAsync(CancellationToken cancellationToken = default) => Task.FromResult((IDictionary)secrets); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutionContext.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutionContext.cs new file mode 100644 index 0000000..018689d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutionContext.cs @@ -0,0 +1,246 @@ +// 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 ServerlessWorkflow.Sdk.Events.Tasks; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The type of the to execute +/// The service used to perform logging +/// The workflow the task to execute belongs to +/// The service used to publish and subscribe to s +/// The service used to manage s +/// The definition of the task to execute +/// The initial state of the task to execute +/// A name/value mapping of the task's arguments, if any +public sealed class TaskExecutionContext(ILogger> logger, IWorkflowExecutionContext workflow, ICloudEventBus eventBus, ITaskStore tasks, TDefinition definition, ITaskInstance instance, JsonObject? arguments) + : ITaskExecutionContext + where TDefinition : TaskDefinition +{ + + readonly AsyncLock asyncLock = new(); + + /// + public IWorkflowExecutionContext Workflow => workflow; + + /// + public TDefinition Definition => definition; + + TaskDefinition ITaskExecutionContext.Definition => Definition; + + /// + public ITaskInstance Instance => instance; + + /// + public JsonObject? Arguments => arguments; + + /// + public IAsyncEnumerable GetSubTasksAsync(CancellationToken cancellationToken = default) => tasks.ListAsync(instance.WorkflowId, instance.Id, cancellationToken); + + /// + public Task> StreamAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); //todo: implement + } + + /// + public Task CorrelateAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); //todo: implement + } + + /// + public Task PublishAsync(ICloudEvent e, CancellationToken cancellationToken = default) => eventBus.PublishAsync(e, cancellationToken); + + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Starting task with id '{TaskId}'...", instance.Id); + await instance.StartAsync(cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Started.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskStartedEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + StartedAt = instance.StartedAt!.Value + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Task with id '{TaskId}' started", instance.Id); + } + + /// + public async Task SuspendAsync(CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Suspending task with id '{TaskId}'...", instance.Id); + await instance.SuspendAsync(cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Suspended.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskSuspendedEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + SuspendedAt = instance.Runs?.LastOrDefault()?.EndedAt ?? DateTimeOffset.Now, + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Task with id '{TaskId}' suspended", instance.Id); + } + + /// + public async Task RetryAsync(Error cause, CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Retrying task with id '{TaskId}'...", instance.Id); + await instance.RetryAsync(cause, cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Retrying.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new RetryingTaskEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + RetryingAt = instance.Runs?.LastOrDefault()?.StartedAt ?? DateTimeOffset.Now + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Task with id '{TaskId}' retried", instance.Id); + } + + /// + public async Task SetErrorAsync(Error error, CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Faulting task with id '{TaskId}'...", instance.Id); + await instance.SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Faulted.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskFaultedEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + Error = error, + FaultedAt = instance.EndedAt!.Value + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Task with id '{TaskId}' faulted", instance.Id); + } + + /// + public async Task SetResultAsync(JsonNode? result, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + await instance.SetOutputAsync(result, then ?? FlowDirective.Continue, cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Completed.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskCompletedEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + CompletedAt = instance.EndedAt!.Value + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Task with id '{TaskId}' ran to completion", instance.Id); + } + + /// + public Task SetContextDataAsync(JsonObject contextData, CancellationToken cancellationToken = default) => workflow.SetContextDataAsync(contextData, cancellationToken); + + /// + public async Task SkipAsync(JsonNode? result, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Skipping the execution of the task with id '{TaskId}'...", instance.Id); + await instance.SkipAsync(result, then ?? FlowDirective.Continue, cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Skipped.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskSkippedEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + SkippedAt = instance.EndedAt!.Value + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("The execution of the task with id '{TaskId}' has been skipped", instance.Id); + } + + /// + public async Task CancelAsync(CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Faulting task with id '{TaskId}'...", instance.Id); + await instance.CancelAsync(cancellationToken).ConfigureAwait(false); + if (workflow.Options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = workflow.Options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Cancelled.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskCancelledEvent() + { + Workflow = workflow.Instance.GetQualifiedName(), + Task = instance.Reference, + CancelledAt = instance.EndedAt!.Value + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Task with id '{TaskId}' faulted", instance.Id); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutionContextFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutionContextFactory.cs new file mode 100644 index 0000000..4f8e789 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutionContextFactory.cs @@ -0,0 +1,34 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The current +public sealed class TaskExecutionContextFactory(IServiceProvider serviceProvider) + : ITaskExecutionContextFactory +{ + + /// + public ITaskExecutionContext Create(IWorkflowExecutionContext workflow, TaskDefinition definition, ITaskInstance state, JsonObject? arguments = null) + { + ArgumentNullException.ThrowIfNull(workflow); + ArgumentNullException.ThrowIfNull(state); + ArgumentNullException.ThrowIfNull(definition); + var contextType = typeof(TaskExecutionContext<>).MakeGenericType(definition.GetType()); + return (ITaskExecutionContext)ActivatorUtilities.CreateInstance(serviceProvider, contextType, workflow, definition, state, arguments ?? new JsonObject()); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutor.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutor.cs new file mode 100644 index 0000000..1a8b181 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutor.cs @@ -0,0 +1,494 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The current . +/// The service used to perform logging +/// The service used to create es +/// The service used to create s +/// The service used to provider s +/// The to run +public abstract class TaskExecutor(IServiceProvider serviceProvider, ILogger logger, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory, ISchemaHandlerProvider schemaHandlerProvider, ITaskExecutionContext task) + : ITaskExecutor + where TDefinition : TaskDefinition +{ + + readonly JsonNode descriptor = JsonSerializer.SerializeToNode(task.GetDescriptor(), Sdk.Serialization.Json.JsonSerializationContext.Default.TaskDescriptor)!; + bool disposed; + + + /// + /// Gets the current + /// + protected IServiceProvider ServiceProvider { get; } = serviceProvider; + + /// + /// Gets the service used to perform logging + /// + protected ILogger Logger { get; } = logger; + + /// + /// Gets the service used to create s + /// + protected ITaskExecutionContextFactory ExecutionContextFactory { get; } = executionContextFactory; + + /// + /// Gets the service used to create s + /// + protected ITaskExecutorFactory ExecutorFactory { get; } = executorFactory; + + /// + /// Gets the service used to provide s + /// + protected ISchemaHandlerProvider SchemaHandlerProvider { get; } = schemaHandlerProvider; + + /// + public ITaskExecutionContext Task { get; } = task; + + ITaskExecutionContext ITaskExecutor.Task => Task; + + /// + /// Gets the used to stream s + /// + protected Subject Subject { get; } = new(); + + /// + /// Gets a containing all child s + /// + protected ConcurrentHashSet Executors { get; } = []; + + /// + /// Gets the 's + /// + protected TaskCompletionSource TaskCompletionSource { get; } = new(); + + /// + /// Gets the 's + /// + protected CancellationTokenSource? CancellationTokenSource { get; set; } + + /// + /// Gets the object used to asynchronously lock the + /// + protected AsyncLock Lock { get; } = new(); + + /// + /// Gets the 's , used to clock the 's execution + /// + protected Stopwatch Stopwatch { get; } = new(); + + /// + /// Gets a key/definition mapping of the extensions, if any, that apply to the task to run + /// + protected IEnumerable>? Extensions => Task.Workflow.Definition.Use?.Extensions?.Where(ex => ex.Value.Extend == "all" || ex.Value.Extend == Task.Definition.Type); + + /// + public async Task InitializeAsync(CancellationToken cancellationToken = default) + { + if (Task.Instance.Status != null && !Task.Instance.IsOperative) return; + try + { + await InitializeCoreAsync(cancellationToken).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Initialized)); + } + catch (HttpRequestException ex) + { + Logger.LogError("An error occurred while initializing the task '{task}': {ex}", Task.Instance.Reference, ex); + await ((ITaskExecutor)this).SetErrorAsync(new Error() + { + Type = ErrorType.Communication, + Title = ErrorTitle.Communication, + Status = ex.StatusCode.HasValue ? (ushort)ex.StatusCode : (ushort)ErrorStatus.Communication, + Detail = ex.Message, + Instance = new(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute) + }, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogError("An error occurred while initializing the task '{task}': {ex}", Task.Instance.Reference, ex); + await ((ITaskExecutor)this).SetErrorAsync(new Error() + { + Type = ErrorType.Runtime, + Title = ErrorTitle.Runtime, + Status = ErrorStatus.Runtime, + Detail = ex.Message, + Instance = new(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute) + }, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Initializes the + /// + /// A new awaitable + protected virtual Task InitializeCoreAsync(CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + public async Task ExecuteAsync(CancellationToken cancellationToken = default) + { + if (Task.Instance.Status != null && !Task.Instance.IsOperative) return; + CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var expressionEvaluationArguments = GetExpressionEvaluationArguments(); + var timeout = await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.Timeout, Task.Instance.Input, expressionEvaluationArguments, cancellationToken).ConfigureAwait(false); + if (timeout is not null) CancellationTokenSource.CancelAfter(timeout.ToTimeSpan()); + try + { + if (!string.IsNullOrWhiteSpace(Task.Definition.If) && !await Task.Workflow.Expressions.EvaluateConditionAsync(Task.Definition.If, Task.Instance.Input, expressionEvaluationArguments, cancellationToken).ConfigureAwait(false)) + { + await SkipAsync(Task.Instance.Input, Task.Definition.Then, cancellationToken).ConfigureAwait(false); + } + else + { + if (Task.Definition.Input?.Schema is not null) + { + var schemaFormat = Task.Definition.Input.Schema!.Format ?? SchemaFormat.Json; + var schemaHandler = SchemaHandlerProvider.GetHandler(schemaFormat) ?? throw new ArgumentNullException($"Failed to find an handler that supports the specified schema format '{schemaFormat}'"); + var validationResult = await schemaHandler.ValidateAsync(Task.Instance.Input, Task.Definition.Input.Schema!, cancellationToken).ConfigureAwait(false); + if (!validationResult.IsValid) + { + await SetErrorAsync(new Error() + { + Type = ErrorType.Validation, + Status = ErrorStatus.Validation, + Title = ErrorTitle.Validation, + Instance = new($"{Task.Instance.Reference}/input", UriKind.RelativeOrAbsolute), + Detail = $"Failed to validate the task's input:\n{string.Join('\n', validationResult.Errors?.Select(e => $"- {e.Key}:\n • {string.Join("\n • ", e.Value)}") ?? [])}" + }, cancellationToken).ConfigureAwait(false); + return; + } + } + await Task.StartAsync(CancellationTokenSource.Token).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Running)); + Stopwatch.Start(); + await BeforeExecuteAsync(cancellationToken).ConfigureAwait(false); //todo: act upon last directive + await ExecuteCoreAsync(CancellationTokenSource.Token).ConfigureAwait(false); + } + await TaskCompletionSource.Task; + } + catch (OperationCanceledException) when (timeout is not null && !cancellationToken.IsCancellationRequested) + { + Logger.LogError("The task '{task}' timed out after {timeout} milliseconds", Task.Instance.Reference, timeout.TotalMilliseconds); + await SetErrorAsync(new Error() + { + Status = (int)HttpStatusCode.RequestTimeout, + Type = ErrorType.Timeout, + Title = ErrorTitle.Timeout, + Detail = $"The task '{Task.Instance.Reference}' timed out after {timeout.TotalMilliseconds } milliseconds" + }, default).ConfigureAwait(false); + } + catch (OperationCanceledException) { } + catch (HttpRequestException ex) + { + Logger.LogError("An error occurred while executing the task '{task}': {ex}", Task.Instance.Reference, ex); + await SetErrorAsync(new Error() + { + Type = ErrorType.Communication, + Title = ErrorTitle.Communication, + Status = ex.StatusCode.HasValue ? (ushort)ex.StatusCode : (ushort)ErrorStatus.Communication, + Detail = ex.Message, + Instance = new(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute) + }, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogError("An error occurred while executing the task '{task}': {ex}", Task.Instance.Reference, ex); + await SetErrorAsync(new Error() + { + Type = ErrorType.Runtime, + Title = ErrorTitle.Runtime, + Status = ErrorStatus.Runtime, + Detail = ex.Message, + Instance = new(Task.Instance.Reference.ToString(), UriKind.RelativeOrAbsolute) + }, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Executes code before the task executes, typically extensions, if any + /// + /// A + /// A new awaitable + protected virtual async Task BeforeExecuteAsync(CancellationToken cancellationToken) + { + if (Task.Instance.IsExtension || Extensions is null) return; + var input = Task.Instance.Input; + foreach (var extension in Extensions.Where(ex => ex.Value.Before != null).Reverse()) + { + var taskDefinition = new DoTaskDefinition() + { + Do = extension.Value.Before! + }; + var task = await Task.Workflow.CreateTaskAsync(taskDefinition, JsonPointer.Create("before", extension.Key), input, Task, true, cancellationToken).ConfigureAwait(false); + var executor = await CreateTaskExecutorAsync(task, taskDefinition, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + if (executor.Task.Instance.Next == FlowDirective.Exit) + { + await SetResultAsync(executor.Task.Instance.Output, executor.Task.Definition.Then, cancellationToken).ConfigureAwait(false); + return; + } + input = executor.Task.Instance.Output ?? new JsonObject(); + Executors.Remove(executor); + } + } + + /// + /// Executes the + /// + /// A new awaitable + protected virtual Task ExecuteCoreAsync(CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + /// Executes code after the task executes, typically extensions, if any + /// + /// A + /// A new awaitable + protected virtual async Task AfterExecuteAsync(CancellationToken cancellationToken) + { + if (Task.Instance.IsExtension || Extensions == null) return; + var output = Task.Instance.Output ?? new JsonObject(); + foreach (var extension in Extensions.Where(ex => ex.Value.After != null).Reverse()) + { + var taskDefinition = new DoTaskDefinition() + { + Do = extension.Value.After! + }; + var task = await Task.Workflow.CreateTaskAsync(taskDefinition, JsonPointer.Create("after", extension.Key), output, Task, true, cancellationToken).ConfigureAwait(false); + var executor = await CreateTaskExecutorAsync(task, taskDefinition, Task.Workflow.Instance.ContextData, Task.Arguments, cancellationToken).ConfigureAwait(false); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + if (executor.Task.Instance.Next == FlowDirective.Exit) break; + output = executor.Task.Instance.Output ?? new JsonObject(); + Executors.Remove(executor); + await executor.DisposeAsync().ConfigureAwait(false); + } + } + + /// + public async Task SuspendAsync(CancellationToken cancellationToken = default) + { + foreach (var executor in Executors) + { + await executor.SuspendAsync(cancellationToken).ConfigureAwait(false); + Executors.Remove(executor); + } + Stopwatch.Stop(); + await SuspendCoreAsync(cancellationToken).ConfigureAwait(false); + await Task.SuspendAsync(cancellationToken).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Suspended)); + if (!TaskCompletionSource.Task.IsCompleted) TaskCompletionSource.SetResult(); + CancellationTokenSource?.Cancel(); + } + + /// + /// Suspends the + /// + /// A new awaitable + protected virtual Task SuspendCoreAsync(CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + public async Task RetryAsync(Error cause, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(cause); + Stopwatch.Stop(); + await Task.RetryAsync(cause, cancellationToken).ConfigureAwait(false); + await RetryCoreAsync(cause, cancellationToken).ConfigureAwait(false); + } + + /// + /// Retries to run the + /// + /// The that caused the retry attempt + /// A + /// A new awaitable + protected virtual Task RetryCoreAsync(Error cause, CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + public async Task SetErrorAsync(Error error, CancellationToken cancellationToken = default) + { + Stopwatch.Stop(); + await SetErrorCoreAsync(error, cancellationToken).ConfigureAwait(false); + await Task.SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Faulted)); + Subject.OnError(new RuntimeErrorException(error)); + if (!TaskCompletionSource.Task.IsCompleted) TaskCompletionSource.SetResult(); + if (CancellationTokenSource != null) await CancellationTokenSource.CancelAsync().ConfigureAwait(false); + } + + /// + /// Faults the handled + /// + /// to set + /// A + /// A new awaitable + protected virtual Task SetErrorCoreAsync(Error error, CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + public async Task SetResultAsync(JsonNode? result = null, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default) + { + if (Task.Instance.Status != TaskStatus.Running) return; + Stopwatch.Stop(); + if (string.IsNullOrWhiteSpace(then)) then = FlowDirective.Continue; + var output = result ?? new JsonObject(); + var arguments = GetExpressionEvaluationArguments() ?? []; + arguments[RuntimeExpressions.Arguments.Output] = output.DeepClone(); + if (Task.Definition.Output?.As is not null) output = (await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.Output.As, output, arguments, cancellationToken).ConfigureAwait(false))?.AsObject(); + if (Task.Definition.Export?.As is not null) + { + var context = await Task.Workflow.Expressions.EvaluateAsync(Task.Definition.Export.As, output ?? new JsonObject(), arguments, cancellationToken).ConfigureAwait(false); + if (context is JsonObject jsonObject) await Task.SetContextDataAsync(jsonObject, cancellationToken).ConfigureAwait(false); + } + await AfterExecuteAsync(cancellationToken).ConfigureAwait(false); + await SetResultCoreAsync(output, then, cancellationToken).ConfigureAwait(false); + await Task.SetResultAsync(output, then, cancellationToken).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Completed)); + Subject.OnCompleted(); + if (!TaskCompletionSource.Task.IsCompleted) TaskCompletionSource.SetResult(); + } + + /// + /// Sets the 's result and transitions to ''. + /// + /// The 's result, if any + /// The to perform next + /// A + /// A new awaitable + protected virtual Task SetResultCoreAsync(JsonNode? result, string then, CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + public async Task CancelAsync(CancellationToken cancellationToken = default) + { + foreach (var executor in Executors) + { + await executor.CancelAsync(cancellationToken).ConfigureAwait(false); + Executors.Remove(executor); + } + Stopwatch.Stop(); + await Task.CancelAsync(cancellationToken).ConfigureAwait(false); + await CancelCoreAsync(cancellationToken).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Cancelled)); + if (!TaskCompletionSource.Task.IsCompleted) TaskCompletionSource.SetCanceled(cancellationToken); + CancellationTokenSource?.Cancel(); + } + + /// + /// Cancels the + /// + /// A new awaitable + protected virtual Task CancelCoreAsync(CancellationToken cancellationToken) => System.Threading.Tasks.Task.CompletedTask; + + /// + public virtual async Task SkipAsync(JsonNode? result, string? then = FlowDirective.Continue, CancellationToken cancellationToken = default) + { + if (Task.Instance.Status != null) return; + Stopwatch.Stop(); + if (string.IsNullOrWhiteSpace(then)) then = FlowDirective.Continue; + var output = result; + await Task.SkipAsync(output, then, cancellationToken).ConfigureAwait(false); + Subject.OnNext(new TaskLifeCycleEvent(TaskLifeCycleEventType.Skipped)); + Subject.OnCompleted(); + if (!TaskCompletionSource.Task.IsCompleted) TaskCompletionSource.SetResult(); + } + + /// + public virtual IDisposable Subscribe(IObserver observer) => Subject.Subscribe(observer); + + /// + /// Gets a new , if any, containing the runtime expression evaluation arguments for the to run + /// + /// A new , if any, containing the runtime expression evaluation arguments for the to run + protected virtual JsonObject? GetExpressionEvaluationArguments() + { + var parameters = Task.Workflow.GetExpressionEvaluationArguments(); + if (Task.Arguments?.Count > 0) foreach (var (key, value) in Task.Arguments) parameters.TryAdd(key, value?.DeepClone()); + parameters[RuntimeExpressions.Arguments.Context] = Task.Workflow.Instance.ContextData.DeepClone(); + parameters[RuntimeExpressions.Arguments.Task] = descriptor.DeepClone(); + parameters[RuntimeExpressions.Arguments.Input] = Task.Instance.Input.DeepClone(); + return parameters; + } + + /// + /// Creates a new for the specified + /// + /// The to create a new for + /// The of the to execute + /// The current context data + /// A name/value mapping of the task's arguments, if any + /// A + /// A new + protected virtual async Task CreateTaskExecutorAsync(ITaskInstance instance, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(instance); + ArgumentNullException.ThrowIfNull(definition); + ArgumentNullException.ThrowIfNull(contextData); + var process = ExecutionContextFactory.Create(Task.Workflow, definition, instance, arguments); + var executor = ExecutorFactory.Create(process); + await executor.InitializeAsync(cancellationToken).ConfigureAwait(false); + Executors.Add(executor); + return executor; + } + + /// + /// Disposes of the + /// + /// A boolean indicating whether or not the is being disposed of + /// A new awaitable + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (disposed) return; + foreach (var executor in Executors) + { + try { await executor.DisposeAsync().ConfigureAwait(false); } + catch { } + } + Subject.Dispose(); + Executors.Clear(); + CancellationTokenSource?.Dispose(); + disposed = true; + } + + /// + public async ValueTask DisposeAsync() + { + await DisposeAsync(disposing: true).ConfigureAwait(false); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of the + /// + /// A boolean indicating whether or not the is being disposed of + protected virtual void Dispose(bool disposing) + { + if (disposed) return; + foreach (var executor in Executors) + { + try { executor.Dispose(); } + catch { } + } + Subject.Dispose(); + Executors.Clear(); + CancellationTokenSource?.Dispose(); + disposed = true; + } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutorFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutorFactory.cs new file mode 100644 index 0000000..37250da --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutorFactory.cs @@ -0,0 +1,64 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface. +/// +/// The current +/// The used to resolve executor types +/// The used to resolve call-type-specific executor types +/// The used to resolve process-type-specific executor types +public sealed class TaskExecutorFactory(IServiceProvider serviceProvider, TaskExecutorRegistry registry, CallTaskExecutorRegistry callRegistry, RunTaskExecutorRegistry runRegistry) + : ITaskExecutorFactory +{ + + /// + public ITaskExecutor Create(ITaskExecutionContext context) + { + ArgumentNullException.ThrowIfNull(context); + if (context.Definition is CallTaskDefinition callDefinition) + { + var callExecutorType = callRegistry.Resolve(callDefinition.Call) ?? typeof(CustomFunctionCallTaskExecutor); + return (ITaskExecutor)ActivatorUtilities.CreateInstance(serviceProvider, callExecutorType, context); + } + if (context.Definition is RunTaskDefinition runDefinition) + { + var runExecutorType = runRegistry.Resolve(runDefinition.Run.ProcessType) ?? throw new NotSupportedException($"The process type '{runDefinition.Run.ProcessType}' is not supported"); + return (ITaskExecutor)ActivatorUtilities.CreateInstance(serviceProvider, runExecutorType, context); + } + var executorType = registry.Resolve(context.Definition.Type) ?? throw new InvalidOperationException($"No task executor registered for task definition type '{context.Definition.GetType().Name}'"); + return (ITaskExecutor)ActivatorUtilities.CreateInstance(serviceProvider, executorType, context); + } + + /// + public ITaskExecutor Create(ITaskExecutionContext context) + where TDefinition : TaskDefinition + { + ArgumentNullException.ThrowIfNull(context); + if (context.Definition is CallTaskDefinition callDefinition) + { + var callExecutorType = callRegistry.Resolve(callDefinition.Call) ?? typeof(CustomFunctionCallTaskExecutor); + return (ITaskExecutor)ActivatorUtilities.CreateInstance(serviceProvider, callExecutorType, context); + } + if (context.Definition is RunTaskDefinition runDefinition) + { + var runExecutorType = runRegistry.Resolve(runDefinition.Run.ProcessType) ?? throw new NotSupportedException($"The process type '{runDefinition.Run.ProcessType}' is not supported"); + return (ITaskExecutor)ActivatorUtilities.CreateInstance(serviceProvider, runExecutorType, context); + } + var executorType = registry.Resolve(context.Definition.Type) ?? throw new InvalidOperationException($"No task executor registered for task type '{context.Definition.Type}'"); + return (ITaskExecutor)ActivatorUtilities.CreateInstance(serviceProvider, executorType, context); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutorRegistry.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutorRegistry.cs new file mode 100644 index 0000000..bfc7aac --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/TaskExecutorRegistry.cs @@ -0,0 +1,76 @@ +// 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.Runtime.Services; + +/// +/// Represents a registry used to map task type discriminators to their corresponding types +/// +public sealed class TaskExecutorRegistry +{ + + static readonly Dictionary taskDefinitionTypeMap = new() + { + [typeof(CallTaskDefinition)] = TaskType.Call, + [typeof(DoTaskDefinition)] = TaskType.Do, + [typeof(EmitTaskDefinition)] = TaskType.Emit, + [typeof(ExtensionTaskDefinition)] = TaskType.Extension, + [typeof(ForTaskDefinition)] = TaskType.For, + [typeof(ForkTaskDefinition)] = TaskType.Fork, + [typeof(ListenTaskDefinition)] = TaskType.Listen, + [typeof(RaiseTaskDefinition)] = TaskType.Raise, + [typeof(RunTaskDefinition)] = TaskType.Run, + [typeof(SetTaskDefinition)] = TaskType.Set, + [typeof(SwitchTaskDefinition)] = TaskType.Switch, + [typeof(TryTaskDefinition)] = TaskType.Try, + [typeof(WaitTaskDefinition)] = TaskType.Wait, + }; + readonly Dictionary registry = []; + + /// + /// Registers the specified type for the specified task type + /// + /// The task type discriminator to register the executor for + /// The type of to register + public void Register(string taskType) + where TExecutor : class, ITaskExecutor + { + ArgumentException.ThrowIfNullOrWhiteSpace(taskType); + registry[taskType] = typeof(TExecutor); + } + + /// + /// Registers the specified for the specified type + /// + /// The type of to register the executor for + /// The type of to register + public void Register() + where TDefinition : TaskDefinition + where TExecutor : class, ITaskExecutor + { + if (!taskDefinitionTypeMap.TryGetValue(typeof(TDefinition), out var taskType)) throw new InvalidOperationException($"Unknown task definition type '{typeof(TDefinition).Name}'. Use Register(string taskType) for custom task types."); + registry[taskType] = typeof(TExecutor); + } + + /// + /// Resolves the type registered for the specified task type + /// + /// The task type discriminator to resolve the executor for + /// The resolved executor type, if any + public Type? Resolve(string taskType) + { + ArgumentException.ThrowIfNullOrWhiteSpace(taskType); + return registry.TryGetValue(taskType, out var executorType) ? executorType : null; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowExecutionContext.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowExecutionContext.cs new file mode 100644 index 0000000..1d34083 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowExecutionContext.cs @@ -0,0 +1,259 @@ +// 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 ServerlessWorkflow.Sdk.Events.Tasks; +using ServerlessWorkflow.Sdk.Events.Workflows; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The service used to perform logging +/// The options used to configure workflow execution +/// The of the workflow being executed +/// The of the workflow being executed +/// The service used to evaluate runtime expressions +/// The in which the workflow is being executed +/// The service used to publish and subscribe to s +/// The service used to manage s +public sealed class WorkflowExecutionContext(ILogger logger, WorkflowExecutionsOptions options, WorkflowDefinition definition, + IWorkflowInstance state, IRuntimeExpressionEvaluator runtimeExpressionEvaluator, IWorkflowRuntime runtime, ICloudEventBus eventBus, ITaskStore tasks) + : IWorkflowExecutionContext +{ + + readonly AsyncLock asyncLock = new(); + JsonObject? expressionEvaluationArguments; + + /// + public WorkflowDefinition Definition => definition; + + /// + public IWorkflowInstance Instance => state; + + /// + public IRuntimeExpressionEvaluator Expressions => runtimeExpressionEvaluator; + + /// + public IWorkflowRuntime Runtime => runtime; + + /// + public WorkflowExecutionsOptions Options => options; + + /// + public JsonObject GetExpressionEvaluationArguments() + { + expressionEvaluationArguments ??= new() + { + [RuntimeExpressions.Arguments.Runtime] = JsonSerializer.SerializeToNode(Runtime.Descriptor, Sdk.Serialization.Json.JsonSerializationContext.Default.RuntimeDescriptor), + [RuntimeExpressions.Arguments.Workflow] = JsonSerializer.SerializeToNode(this.GetDescriptor(), Sdk.Serialization.Json.JsonSerializationContext.Default.WorkflowDescriptor) + }; + return expressionEvaluationArguments; + } + + /// + public Task ContinueWithAsync(TaskDefinition task, CancellationToken cancellationToken = default) => Task.CompletedTask; + + /// + public async Task CreateTaskAsync(TaskDefinition definition, JsonPointer path, JsonNode input, ITaskExecutionContext? parent = null, bool isExtension = false, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Task.Created.v1, + Subject = Instance.Id, + DataContentType = MediaTypeNames.Application.Json, + Data = new TaskCreatedEvent() + { + Workflow = Instance.GetQualifiedName(), + Task = path, + CreatedAt = state.CreatedAt + } + }, cancellationToken).ConfigureAwait(false); + return await tasks.AddAsync(new TaskInstance() + { + WorkflowId = state.Id, + Name = path.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries).Last(), + Reference = path, + ParentId = parent?.Instance.Id, + IsExtension = isExtension, + Input = input + }, cancellationToken).ConfigureAwait(false); + } + + /// + public IAsyncEnumerable GetTasksAsync(CancellationToken cancellationToken = default) => tasks.ListAsync(Instance.Id, cancellationToken); + + /// + public Task PublishAsync(ICloudEvent e, CancellationToken cancellationToken = default) => eventBus.PublishAsync(e, cancellationToken); + + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Starting workflow with id '{WorkflowId}'...", state.Id); + await state.StartAsync(cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Workflow.Started.v1, + Subject = state.GetQualifiedName(), + DataContentType = MediaTypeNames.Application.Json, + Data = new WorkflowStartedEvent() + { + Name = state.GetQualifiedName(), + Definition = definition.GetQualifiedName(), + StartedAt = state.StartedAt ?? DateTimeOffset.Now + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Workflow with id '{WorkflowId}' started", state.Id); + } + + /// + public async Task SuspendAsync(CancellationToken cancellationToken = default) + { + if (state.Status == WorkflowStatus.Suspended) return; + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Suspending the execution of the workflow with id '{WorkflowId}'...", state.Id); + await state.SuspendAsync(cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Workflow.Suspended.v1, + Subject = state.GetQualifiedName(), + DataContentType = MediaTypeNames.Application.Json, + Data = new WorkflowSuspendedEvent() + { + Name = state.GetQualifiedName(), + SuspendedAt = DateTimeOffset.Now + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("The execution of the workflow with id '{WorkflowId}' has been suspended", state.Id); + } + + /// + public async Task ResumeAsync(CancellationToken cancellationToken = default) + { + if (state.Status != WorkflowStatus.Suspended) return; + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Resuming the execution of the workflow with id '{WorkflowId}'...", state.Id); + await state.ResumeAsync(cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Workflow.Resumed.v1, + Subject = state.GetQualifiedName(), + DataContentType = MediaTypeNames.Application.Json, + Data = new WorkflowResumedEvent() + { + Name = state.GetQualifiedName(), + ResumedAt = DateTimeOffset.Now + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("The execution of the workflow with id '{WorkflowId}' has been resumed", state.Id); + + } + + /// + public async Task SetErrorAsync(Error error, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(error); + if (state.Status != WorkflowStatus.Running && state.Status != WorkflowStatus.Suspended) return; + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + await state.SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Workflow.Faulted.v1, + Subject = state.GetQualifiedName(), + DataContentType = MediaTypeNames.Application.Json, + Data = new WorkflowFaultedEvent() + { + Name = state.GetQualifiedName(), + Error = error, + FaultedAt = DateTimeOffset.Now + } + }, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task SetResultAsync(JsonNode? result, CancellationToken cancellationToken = default) + { + if (state.Status != WorkflowStatus.Running && state.Status != WorkflowStatus.Suspended) return; + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + await state.SetOutputAsync(result, cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Workflow.Completed.v1, + Subject = state.GetQualifiedName(), + DataContentType = MediaTypeNames.Application.Json, + Data = new WorkflowCompletedEvent() + { + Name = state.GetQualifiedName(), + CompletedAt = DateTimeOffset.Now, + Output = result + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("The workflow with id '{WorkflowId}' ran to completion", state.Id); + } + + /// + public Task SetContextDataAsync(JsonObject contextData, CancellationToken cancellationToken = default) => Instance.SetContextDataAsync(contextData, cancellationToken); + + /// + public async Task CancelAsync(CancellationToken cancellationToken = default) + { + if (state.Status == WorkflowStatus.Cancelled) return; + using var @lock = await asyncLock.LockAsync(cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("Cancelling the execution of the workflow with id '{WorkflowId}'...", state.Id); + await state.CancelAsync(cancellationToken).ConfigureAwait(false); + if (options.LifecycleEvents.Publish) await eventBus.PublishAsync(new CloudEvent() + { + SpecVersion = CloudEvent.DefaultVersion, + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.Now, + Source = options.LifecycleEvents.Source, + Type = ServerlessWorkflowSpecificationDefaults.CloudEvents.Workflow.Cancelled.v1, + Subject = state.GetQualifiedName(), + DataContentType = MediaTypeNames.Application.Json, + Data = new WorkflowCancelledEvent() + { + Name = state.GetQualifiedName(), + CancelledAt = DateTimeOffset.Now + } + }, cancellationToken).ConfigureAwait(false); + if (logger.IsEnabled(LogLevel.Information)) logger.LogInformation("The execution of the workflow with id '{WorkflowId}' has been cancelled", state.Id); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowExecutionContextFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowExecutionContextFactory.cs new file mode 100644 index 0000000..08f2a13 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowExecutionContextFactory.cs @@ -0,0 +1,36 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The current +/// The service used to resolve s based on the specified language +public sealed class WorkflowExecutionContextFactory(IServiceProvider serviceProvider, IRuntimeExpressionEvaluatorProvider expressionEvaluatorProvider) + : IWorkflowExecutionContextFactory +{ + + /// + public IWorkflowExecutionContext Create(WorkflowDefinition definition, IWorkflowInstance state, WorkflowExecutionsOptions executionsOptions) + { + ArgumentNullException.ThrowIfNull(definition); + ArgumentNullException.ThrowIfNull(state); + ArgumentNullException.ThrowIfNull(executionsOptions); + var language = definition.Evaluate?.Language ?? RuntimeExpressions.Languages.JQ; + var expressionEvaluator = expressionEvaluatorProvider.GetEvaluator(language) ?? throw new NullReferenceException($"Failed to resolve an expression evaluator for the specified language '{language}'"); + return ActivatorUtilities.CreateInstance(serviceProvider, definition, state, executionsOptions, expressionEvaluator); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowProcess.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowProcess.cs new file mode 100644 index 0000000..83a9626 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowProcess.cs @@ -0,0 +1,200 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The service used to perform logging +/// The in which to execute the workflow process +/// The service used to create s +/// The service used to create s +public sealed class WorkflowProcess(ILogger logger, IWorkflowExecutionContext workflow, ITaskExecutionContextFactory executionContextFactory, ITaskExecutorFactory executorFactory) + : IWorkflowProcess +{ + + readonly CancellationTokenSource cancellationTokenSource = new(); + readonly TaskCompletionSource taskCompletionSource = new(); + readonly ConcurrentDictionary executors = []; + readonly Subject lifeCycleEvents = new(); + readonly Stopwatch stopwatch = new(); + + /// + /// Runs the + /// + /// A new awaitable + public async Task RunAsync() + { + try + { + switch (workflow.Instance.Status) + { + case null or WorkflowStatus.Pending: + await StartAsync(cancellationTokenSource.Token).ConfigureAwait(false); + break; + case WorkflowStatus.Running: + case WorkflowStatus.Suspended: + await ResumeAsync(cancellationTokenSource.Token).ConfigureAwait(false); + break; + case WorkflowStatus.Completed: + taskCompletionSource.SetResult(); + return; + default: + if (logger.IsEnabled(LogLevel.Warning)) logger.LogWarning("The workflow instance '{instance}' is in an unexpected status phase '{status}'", workflow.Instance.GetQualifiedName(), workflow.Instance.Status); + return; + } + } + catch (Exception ex) + { + if (logger.IsEnabled(LogLevel.Error)) logger.LogError("A critical exception occurred while executing the workflow instance '{instance}': {ex}", workflow.Instance.GetQualifiedName(), ex); + await workflow.SetErrorAsync(Error.Runtime(new Uri("/", UriKind.Relative), $"A critical exception occurred while executing the workflow instance '{workflow.Instance.GetQualifiedName()}': {ex}"), cancellationTokenSource.Token).ConfigureAwait(false); + } + } + + /// + public IDisposable Subscribe(IObserver observer) => lifeCycleEvents.Subscribe(observer); + + async Task StartAsync(CancellationToken cancellationToken) + { + await workflow.StartAsync(cancellationToken).ConfigureAwait(false); + var taskDefinition = workflow.Definition.Do.First(); + var task = await workflow.CreateTaskAsync(taskDefinition.Value, JsonPointer.Create(taskDefinition.Key), workflow.Instance.Input ?? [], null, false, cancellationToken).ConfigureAwait(false); + var executor = await CreateTaskExecutorAsync(task, taskDefinition.Value, workflow.Instance.ContextData ?? [], null, cancellationToken).ConfigureAwait(false); + stopwatch.Start(); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public Task WaitAsync(CancellationToken cancellationToken = default) => taskCompletionSource.Task.WaitAsync(CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, cancellationToken).Token); + + /// + public async Task SuspendAsync(CancellationToken cancellationToken = default) + { + foreach (var executor in executors.Keys.ToList()) + { + await executor.SuspendAsync(cancellationToken).ConfigureAwait(false); + executors.TryRemove(executor, out _); + } + stopwatch.Stop(); + await workflow.SuspendAsync(cancellationToken).ConfigureAwait(false); + lifeCycleEvents.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Suspended)); + taskCompletionSource.TrySetResult(); + cancellationTokenSource?.Cancel(); + } + + /// + public async Task ResumeAsync(CancellationToken cancellationToken = default) + { + await workflow.ResumeAsync(cancellationToken).ConfigureAwait(false); + var task = await workflow.GetTasksAsync(cancellationToken).FirstOrDefaultAsync(t => string.IsNullOrWhiteSpace(t.ParentId) && (t.Status == null || t.IsOperative || t.Status == TaskStatus.Suspended), cancellationToken); + if (task == null) return; + var taskDefinition = workflow.Definition.GetComponent(task.Reference.ToString()); + var executor = await CreateTaskExecutorAsync(task, taskDefinition, [], [], cancellationToken).ConfigureAwait(false); + stopwatch.Start(); + await executor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + async Task SetErrorAsync(Error error, CancellationToken cancellationToken = default) + { + stopwatch.Stop(); + await workflow.SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + lifeCycleEvents.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Faulted, error)); + lifeCycleEvents.OnError(new RuntimeErrorException(error)); + taskCompletionSource.TrySetException(new RuntimeErrorException(error)); + } + + async Task SetResultAsync(JsonNode? result, CancellationToken cancellationToken = default) + { + if (workflow.Instance.Status != WorkflowStatus.Running) return; + stopwatch.Stop(); + var output = result; + await workflow.SetResultAsync(output, cancellationToken).ConfigureAwait(false); + lifeCycleEvents.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Completed, output)); + lifeCycleEvents.OnCompleted(); + taskCompletionSource.TrySetResult(); + } + + /// + public async Task CancelAsync(CancellationToken cancellationToken = default) + { + foreach (var executor in executors.Keys.ToList()) + { + await executor.CancelAsync(cancellationToken).ConfigureAwait(false); + executors.TryRemove(executor, out _); + } + stopwatch.Stop(); + await workflow.CancelAsync(cancellationToken).ConfigureAwait(false); + lifeCycleEvents.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Cancelled)); + taskCompletionSource.TrySetCanceled(cancellationToken); + cancellationTokenSource?.Cancel(); + } + + async Task CreateTaskExecutorAsync(ITaskInstance task, TaskDefinition definition, JsonObject contextData, JsonObject? arguments = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(task); + ArgumentNullException.ThrowIfNull(definition); + ArgumentNullException.ThrowIfNull(contextData); + var context = executionContextFactory.Create(workflow, definition, task, arguments); + var executor = executorFactory.Create(context); + executor.SubscribeAsync + ( + _ => Task.CompletedTask, + async ex => await OnTaskFaultedAsync(executor, ex, cancellationToken).ConfigureAwait(false), + async () => await OnTaskCompletedAsync(executor, cancellationToken).ConfigureAwait(false) + ); + await executor.InitializeAsync(cancellationToken).ConfigureAwait(false); + executors.TryAdd(executor, false); + return executor; + } + + async Task OnTaskFaultedAsync(ITaskExecutor executor, Exception ex, CancellationToken cancellationToken) + { + var error = executor.Task.Instance.Error; + if (error is null) + { + if (ex is RuntimeErrorException rex) error = rex.Error; + else error = Error.Runtime(new(executor.Task.Instance.Reference.ToString(), UriKind.Relative), $"An unhandled exception was thrown during the execution of task '{executor.Task.Instance.Reference}': {ex}"); + } + await SetErrorAsync(error, cancellationToken).ConfigureAwait(false); + executors.TryRemove(executor, out _); + } + + async Task OnTaskCompletedAsync(ITaskExecutor executor, CancellationToken cancellationToken) + { + var nextDefinition = (executor.Task.Instance.Status == TaskStatus.Skipped ? FlowDirective.Continue : executor.Task.Instance.Next) switch + { + FlowDirective.End or FlowDirective.Exit => null, + _ => workflow.Definition.GetTaskAfter(executor.Task.Instance) + }; + var completedTask = executor.Task; + executors.TryRemove(executor, out _); + if (nextDefinition == null) + { + await SetResultAsync(completedTask.Instance.Output, cancellationToken).ConfigureAwait(false); + return; + } + var nextTask = await workflow.CreateTaskAsync(nextDefinition.Value, JsonPointer.Create(nextDefinition.Key), completedTask.Instance.Output ?? new JsonObject(), cancellationToken: cancellationToken).ConfigureAwait(false); + var nextExecutor = await CreateTaskExecutorAsync(nextTask, nextDefinition.Value, executor.Task.Workflow.Instance.ContextData, [], cancellationToken).ConfigureAwait(false); + await nextExecutor.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public async ValueTask DisposeAsync() + { + foreach (var executor in executors.Keys.ToList()) if (executors.TryRemove(executor, out var _)) await executor.DisposeAsync().ConfigureAwait(false); + stopwatch.Stop(); + cancellationTokenSource.Dispose(); + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowProcessFactory.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowProcessFactory.cs new file mode 100644 index 0000000..804e170 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowProcessFactory.cs @@ -0,0 +1,36 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The current +/// The service used to create s +public sealed class WorkflowProcessFactory(IServiceProvider serviceProvider, IWorkflowExecutionContextFactory executionContextFactory) + : IWorkflowProcessFactory +{ + + /// + public async Task CreateAsync(WorkflowDefinition definition, IWorkflowInstance state, WorkflowExecutionsOptions executionsOptions, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + ArgumentNullException.ThrowIfNull(state); + var executionContext = executionContextFactory.Create(definition, state, executionsOptions); + var process = ActivatorUtilities.CreateInstance(serviceProvider, executionContext); + await process.RunAsync().ConfigureAwait(false); + return process; + } + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowRuntime.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowRuntime.cs new file mode 100644 index 0000000..b8e40e6 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowRuntime.cs @@ -0,0 +1,61 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The service used to manage s +/// The service used to manage s +/// The service used to create es +public sealed class WorkflowRuntime(IWorkflowDefinitionStore definitions, IWorkflowStore states, IWorkflowProcessFactory processFactory) + : IWorkflowRuntime +{ + + readonly ConcurrentDictionary processes = []; + + /// + public RuntimeDescriptor Descriptor { get; } = new() + { + Name = "Serverless Workflow Runtime", + Version = "1.0.0" + }; + + /// + public async Task RunAsync(string @namespace, string name, string? version = null, JsonObject? input = null, WorkflowExecutionsOptions? executionOptions = null, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(@namespace); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + var definition = await definitions.GetAsync(@namespace, name, version, cancellationToken).ConfigureAwait(false) ?? throw new NullReferenceException($"Failed to find the specified workflow definition '{@namespace}.{name}:{version ?? "latest"}'"); + return await RunAsync(definition, input, executionOptions, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task RunAsync(WorkflowDefinition definition, JsonObject? input = null, WorkflowExecutionsOptions? executionOptions = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(definition); + var state = await states.AddAsync(definition, input, cancellationToken).ConfigureAwait(false); + var process = await processFactory.CreateAsync(definition, state, executionOptions ?? new(), cancellationToken).ConfigureAwait(false); + processes[state.Id] = process; + return process; + } + + /// + public async ValueTask DisposeAsync() + { + foreach (var processId in processes.Keys.ToList()) if (processes.TryRemove(processId, out var process) && process is not null) await process.DisposeAsync().ConfigureAwait(false); + } + + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowRuntimeBuilder.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowRuntimeBuilder.cs new file mode 100644 index 0000000..4ae9043 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/WorkflowRuntimeBuilder.cs @@ -0,0 +1,232 @@ +// 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.Runtime.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The underlying +/// The application's +/// The to use when registering services +public sealed class WorkflowRuntimeBuilder(IServiceCollection services, IConfiguration configuration, ServiceLifetime serviceLifetime) + : IWorkflowRuntimeBuilder +{ + + TaskExecutorRegistry? registry; + CallTaskExecutorRegistry? callRegistry; + RunTaskExecutorRegistry? runRegistry; + + /// + public IServiceCollection Services { get; } = services; + + /// + public IConfiguration Configuration { get; } = configuration; + + /// + public ServiceLifetime ServiceLifetime { get; } = serviceLifetime; + + TaskExecutorRegistry GetOrCreateRegistry() + { + if (registry != null) return registry; + registry = Services.FirstOrDefault(d => d.ServiceType == typeof(TaskExecutorRegistry))?.ImplementationInstance as TaskExecutorRegistry; + if (registry != null) return registry; + registry = new TaskExecutorRegistry(); + Services.AddSingleton(registry); + return registry; + } + + CallTaskExecutorRegistry GetOrCreateCallRegistry() + { + if (callRegistry != null) return callRegistry; + callRegistry = Services.FirstOrDefault(d => d.ServiceType == typeof(CallTaskExecutorRegistry))?.ImplementationInstance as CallTaskExecutorRegistry; + if (callRegistry != null) return callRegistry; + callRegistry = new CallTaskExecutorRegistry(); + Services.AddSingleton(callRegistry); + return callRegistry; + } + + RunTaskExecutorRegistry GetOrCreateRunRegistry() + { + if (runRegistry != null) return runRegistry; + runRegistry = Services.FirstOrDefault(d => d.ServiceType == typeof(RunTaskExecutorRegistry))?.ImplementationInstance as RunTaskExecutorRegistry; + if (runRegistry != null) return runRegistry; + runRegistry = new RunTaskExecutorRegistry(); + Services.AddSingleton(runRegistry); + return runRegistry; + } + + WorkflowRuntimeBuilder ReplaceService(Type implementationType) where TService : class + { + Services.Replace(new ServiceDescriptor(typeof(TService), implementationType, ServiceLifetime)); + return this; + } + + WorkflowRuntimeBuilder ReplaceService(Func factory) where TService : class + { + Services.Replace(new ServiceDescriptor(typeof(TService), factory, ServiceLifetime)); + return this; + } + + WorkflowRuntimeBuilder AddService(Type implementationType) where TService : class + { + Services.Add(new ServiceDescriptor(typeof(TService), implementationType, ServiceLifetime)); + return this; + } + + WorkflowRuntimeBuilder AddService(Func factory) where TService : class + { + Services.Add(new ServiceDescriptor(typeof(TService), factory, ServiceLifetime)); + return this; + } + + /// + public IWorkflowRuntimeBuilder UseAuthenticationHandler() where THandler : class, IAuthenticationHandler => ReplaceService(typeof(THandler)); + + /// + public IWorkflowRuntimeBuilder UseAuthenticationHandler(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseCloudEventBus() where TBus : class, ICloudEventBus => ReplaceService(typeof(TBus)); + + /// + public IWorkflowRuntimeBuilder UseCloudEventBus(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseOAuth2TokenManager() where TManager : class, IOAuth2TokenManager => ReplaceService(typeof(TManager)); + + /// + public IWorkflowRuntimeBuilder UseOAuth2TokenManager(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseContainerRuntime() where TRuntime : class, IContainerRuntime => ReplaceService(typeof(TRuntime)); + + /// + public IWorkflowRuntimeBuilder UseContainerRuntime(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseExternalResourceReader() where TReader : class, IExternalResourceReader => ReplaceService(typeof(TReader)); + + /// + public IWorkflowRuntimeBuilder UseExternalResourceReader(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluator() where TEvaluator : class, IRuntimeExpressionEvaluator => AddService(typeof(TEvaluator)); + + /// + public IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluator(Func factory) => AddService(factory); + + /// + public IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluatorProvider() where TProvider : class, IRuntimeExpressionEvaluatorProvider => ReplaceService(typeof(TProvider)); + + /// + public IWorkflowRuntimeBuilder UseRuntimeExpressionEvaluatorProvider(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseSchemaHandler() where THandler : class, ISchemaHandler => AddService(typeof(THandler)); + + /// + public IWorkflowRuntimeBuilder UseSchemaHandler(Func factory) => AddService(factory); + + /// + public IWorkflowRuntimeBuilder UseSchemaHandlerProvider() where TProvider : class, ISchemaHandlerProvider => ReplaceService(typeof(TProvider)); + + /// + public IWorkflowRuntimeBuilder UseSchemaHandlerProvider(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseSecretsManager() where TManager : class, ISecretsManager => ReplaceService(typeof(TManager)); + + /// + public IWorkflowRuntimeBuilder UseSecretsManager(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseScriptExecutor() where TExecutor : class, IScriptExecutor => AddService(typeof(TExecutor)); + + /// + public IWorkflowRuntimeBuilder UseScriptExecutor(Func factory) => AddService(factory); + + /// + public IWorkflowRuntimeBuilder UseScriptExecutorProvider() where TProvider : class, IScriptExecutorProvider => ReplaceService(typeof(TProvider)); + + /// + public IWorkflowRuntimeBuilder UseScriptExecutorProvider(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseTaskExecutor() + where TDefinition : TaskDefinition + where TExecutor : class, ITaskExecutor + { + GetOrCreateRegistry().Register(); + return this; + } + + /// + public IWorkflowRuntimeBuilder UseCallTaskExecutor(string type) + where TExecutor : class, ITaskExecutor + { + GetOrCreateCallRegistry().Register(type); + return this; + } + + /// + public IWorkflowRuntimeBuilder UseRunTaskExecutor(string type) + where TExecutor : class, ITaskExecutor + { + GetOrCreateRunRegistry().Register(type); + return this; + } + + /// + public IWorkflowRuntimeBuilder UseTaskExecutorFactory() where TFactory : class, ITaskExecutorFactory => ReplaceService(typeof(TFactory)); + + /// + public IWorkflowRuntimeBuilder UseTaskExecutorFactory(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseWorkflowProcessFactory() where TFactory : class, IWorkflowProcessFactory => ReplaceService(typeof(TFactory)); + + /// + public IWorkflowRuntimeBuilder UseWorkflowProcessFactory(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseWorkflowExecutionContextFactory() where TFactory : class, IWorkflowExecutionContextFactory => ReplaceService(typeof(TFactory)); + + /// + public IWorkflowRuntimeBuilder UseWorkflowExecutionContextFactory(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseTaskExecutionContextFactory() where TFactory : class, ITaskExecutionContextFactory => ReplaceService(typeof(TFactory)); + + /// + public IWorkflowRuntimeBuilder UseTaskExecutionContextFactory(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseWorkflowDefinitionStore() where TStore : class, IWorkflowDefinitionStore => ReplaceService(typeof(TStore)); + + /// + public IWorkflowRuntimeBuilder UseWorkflowDefinitionStore(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseWorkflowStateStore() where TStore : class, IWorkflowStore => ReplaceService(typeof(TStore)); + + /// + public IWorkflowRuntimeBuilder UseWorkflowStateStore(Func factory) => ReplaceService(factory); + + /// + public IWorkflowRuntimeBuilder UseTaskStateStore() where TStore : class, ITaskStore => ReplaceService(typeof(TStore)); + + /// + public IWorkflowRuntimeBuilder UseTaskStateStore(Func factory) => ReplaceService(factory); + +} diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Services/XmlSchemaHandler.cs b/src/ServerlessWorkflow.Sdk.Runtime/Services/XmlSchemaHandler.cs new file mode 100644 index 0000000..a7ee7b3 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Services/XmlSchemaHandler.cs @@ -0,0 +1,77 @@ +// 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 Newtonsoft.Json; +using System.Xml; +using System.Xml.Schema; + +namespace ServerlessWorkflow.Sdk.Runtime.Services; + +/// +/// Represents the implementation used to handle XML schemas +/// +/// The service used to reader external resources +public sealed class XmlSchemaHandler(IExternalResourceReader externalResourceReader) + : ISchemaHandler +{ + + /// + public bool Supports(string format) => format.Equals(SchemaFormat.Xml, StringComparison.OrdinalIgnoreCase); + + /// + public async Task ValidateAsync(JsonNode graph, SchemaDefinition schema, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(graph); + ArgumentNullException.ThrowIfNull(schema); + if (!Supports(schema.Format)) throw new NotSupportedException($"The specified schema format '{schema.Format}' is not supported in this context"); + var xml = string.Empty; + XmlSchema xmlSchema; + if (schema.Resource is null) + { + xml = schema.Document?.Match + ( + jsonObject => throw new InvalidOperationException("Document-based schemas are not supported for XML schema validation"), + str => str + ); + if (string.IsNullOrWhiteSpace(xml)) throw new NullReferenceException("The specified schema does not contain a valid XML schema definition"); + using var reader = new StringReader(xml); + xmlSchema = XmlSchema.Read(reader, OnValidationError)!; + } + else + { + using var stream = await externalResourceReader.ReadAsync(schema.Resource, cancellationToken: cancellationToken).ConfigureAwait(false); + xmlSchema = XmlSchema.Read(stream, OnValidationError)!; + } + var settings = new XmlReaderSettings(); + settings.Schemas.Add(xmlSchema); + settings.ValidationType = ValidationType.Schema; + var json = System.Text.Json.JsonSerializer.Serialize(graph, Sdk.Serialization.Json.JsonSerializationContext.Default.JsonObject); + var xmlDocument = JsonConvert.DeserializeXmlNode(json)!; + xml = xmlDocument.OuterXml; + using var stringReader = new StringReader(xml); + using var xmlReader = XmlReader.Create(stringReader, settings); + var validationErrors = new List(); + settings.ValidationEventHandler += (sender, args) => validationErrors.Add(args.Message); + try { while (xmlReader.Read()) { } } + catch (XmlException ex) { validationErrors.Add(ex.Message); } + if (validationErrors.Count != 0) return SchemaValidationResult.Failed(validationErrors.GroupBy(e => e).Select(e => new KeyValuePair>(e.Key, [e.Key]))); + return SchemaValidationResult.Succeeded(); + } + + + void OnValidationError(object? sender, ValidationEventArgs e) + { + if (e.Severity == XmlSeverityType.Error) throw new XmlSchemaValidationException(e.Message, e.Exception, e.Exception.LineNumber, e.Exception.LinePosition); + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/StringFormatter.cs b/src/ServerlessWorkflow.Sdk.Runtime/StringFormatter.cs new file mode 100644 index 0000000..19cd712 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/StringFormatter.cs @@ -0,0 +1,36 @@ +// 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.Runtime; + +/// +/// Represents an helper class used to perform string formatting operations +/// +public static class StringFormatter +{ + + /// + /// Formats the specified string template by replacing the placeholders with the provided parameters + /// + /// The string template containing the placeholders to replace + /// A key-value collection containing the parameters to replace in the template, where the key represents the placeholder name and the value represents the value to replace it with + /// The formatted string + public static string Format(string template, IDictionary? parameters) + { + if (string.IsNullOrWhiteSpace(template) || parameters == null || !parameters.Any()) return template; + var text = template; + foreach (var parameter in parameters) text = text.Replace("{" + parameter.Key + "}", parameter.Value?.ToString(), StringComparison.OrdinalIgnoreCase); + return text; + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk.Runtime/Usings.cs b/src/ServerlessWorkflow.Sdk.Runtime/Usings.cs new file mode 100644 index 0000000..fff7021 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk.Runtime/Usings.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. + +global using Json.Pointer; +global using Json.Schema; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using ServerlessWorkflow.Sdk; +global using ServerlessWorkflow.Sdk.Models; +global using ServerlessWorkflow.Sdk.Models.Authentication; +global using ServerlessWorkflow.Sdk.Models.Tasks; +global using ServerlessWorkflow.Sdk.Runtime.Configuration; +global using ServerlessWorkflow.Sdk.Runtime.Models; +global using ServerlessWorkflow.Sdk.Runtime.Services; +global using ServerlessWorkflow.Sdk.Runtime.Services.Executors; +global using System.Collections.Concurrent; +global using System.ComponentModel; +global using System.ComponentModel.DataAnnotations; +global using System.Diagnostics; +global using System.Net; +global using System.Net.Http.Headers; +global using System.Net.Mime; +global using System.Reactive.Linq; +global using System.Reactive.Subjects; +global using System.Runtime.InteropServices; +global using System.Runtime.Serialization; +global using System.Security.Claims; +global using System.Text; +global using System.Text.Json; +global using System.Text.Json.Nodes; +global using System.Text.Json.Serialization; diff --git a/src/ServerlessWorkflow.Sdk/Attributes/SemanticVersionAttribute.cs b/src/ServerlessWorkflow.Sdk/Attributes/SemanticVersionAttribute.cs index 03458c9..9f00008 100644 --- a/src/ServerlessWorkflow.Sdk/Attributes/SemanticVersionAttribute.cs +++ b/src/ServerlessWorkflow.Sdk/Attributes/SemanticVersionAttribute.cs @@ -11,13 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace ServerlessWorkflow.Sdk; /// /// Represents a used to validate semantic versions /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] -public class SemanticVersionAttribute() +public sealed class SemanticVersionAttribute() : RegularExpressionAttribute(@"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$") { diff --git a/src/ServerlessWorkflow.Sdk/AuthenticationScheme.cs b/src/ServerlessWorkflow.Sdk/AuthenticationScheme.cs index 2d2e4b5..eaa4291 100644 --- a/src/ServerlessWorkflow.Sdk/AuthenticationScheme.cs +++ b/src/ServerlessWorkflow.Sdk/AuthenticationScheme.cs @@ -44,6 +44,11 @@ public static class AuthenticationScheme /// public const string OpenIDConnect = "OpenIDConnect"; + /// + /// Gets an containing the authentication schemes supported by default + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing the authentication schemes supported by default /// diff --git a/src/ServerlessWorkflow.Sdk/ContainerCleanupPolicy.cs b/src/ServerlessWorkflow.Sdk/ContainerCleanupPolicy.cs index 46a1283..dd4eda8 100644 --- a/src/ServerlessWorkflow.Sdk/ContainerCleanupPolicy.cs +++ b/src/ServerlessWorkflow.Sdk/ContainerCleanupPolicy.cs @@ -32,6 +32,11 @@ public static class ContainerCleanupPolicy /// public const string Never = "never"; + /// + /// Gets an containing all supported values + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing all supported values /// diff --git a/src/ServerlessWorkflow.Sdk/EquatableDictionary.cs b/src/ServerlessWorkflow.Sdk/EquatableDictionary.cs new file mode 100644 index 0000000..3a8dad8 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/EquatableDictionary.cs @@ -0,0 +1,116 @@ +// 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; + +/// +/// Represents a record implementation of the interface, equatable based on its record keys and values +/// +/// The type of keys contained by the dictionary +/// The type of values contained by the dictionary +[CollectionDataContract] +public sealed record EquatableDictionary + : IDictionary, IEnumerable> + where TKey : notnull +{ + + /// + /// Initializes a new + /// + public EquatableDictionary() : this(new Dictionary()) { } + + /// + /// Initializes a new + /// + /// An containing the items the is made out of + public EquatableDictionary(IEnumerable> items) => this.Items = items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + /// + /// Gets the underlying + /// + IDictionary Items { get; init; } + + /// + public TValue this[TKey key] { get => this.Items[key]; set => this.Items[key] = value; } + + /// + public ICollection Keys => this.Items.Keys; + + /// + public ICollection Values => this.Items.Values; + + /// + public int Count => this.Items.Count; + + /// + public bool IsReadOnly => this.Items.IsReadOnly; + + /// + public object? this[object key] { get => this[(TKey)key]; set => this[(TKey)key] = (TValue)value!; } + + /// + public void Add(TKey key, TValue value) => this.Items.Add(key, value); + + /// + public void Add(KeyValuePair item) => this.Items.Add(item); + + /// + public bool Contains(KeyValuePair item) => this.Items.Contains(item); + + /// + public bool ContainsKey(TKey key) => this.Items.ContainsKey(key); + + /// + public bool TryGetValue(TKey key, out TValue value) => this.Items.TryGetValue(key, out value); + + /// + public bool Remove(TKey key) => this.Items.Remove(key); + + /// + public bool Remove(KeyValuePair item) => this.Items.Remove(item); + + /// + public void Clear() => this.Items.Clear(); + + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) => this.Items.CopyTo(array, arrayIndex); + + /// + public IEnumerator> GetEnumerator() => this.Items.GetEnumerator(); + + /// + public override int GetHashCode() + { + var someHashValue = -234897289; + foreach (var item in this.Items) + { + someHashValue ^= item!.GetHashCode(); + } + return someHashValue; + } + + /// + public bool Equals(EquatableDictionary? other) + { + if (other == null || other.Count != this.Count) return false; + for (var i = 0; i < this.Count; i++) + { + var record = other.ElementAt(i); + if (record.Equals(default) || !record.Equals(this.ElementAt(i))) return false; + } + return true; + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + +} diff --git a/src/ServerlessWorkflow.Sdk/EquatableList.cs b/src/ServerlessWorkflow.Sdk/EquatableList.cs new file mode 100644 index 0000000..9ce9fb5 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/EquatableList.cs @@ -0,0 +1,44 @@ +// 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; + +/// +/// Represents a list that can be compared for equality by comparing its items +/// +/// The type of the items contained in the list +[CollectionDataContract] +public sealed class EquatableList + : List, IEquatable> +{ + + /// + public EquatableList() : base() { } + + /// + public EquatableList(IEnumerable collection) : base(collection) { } + + /// + public bool Equals(EquatableList? other) => other is not null && this.SequenceEqual(other); + + /// + public override bool Equals(object? obj) => Equals(obj as EquatableList); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + foreach (var item in this) hash.Add(item); + return hash.ToHashCode(); + } +} diff --git a/src/ServerlessWorkflow.Sdk/ErrorStatus.cs b/src/ServerlessWorkflow.Sdk/ErrorStatus.cs index 39f5a26..7f5ffec 100644 --- a/src/ServerlessWorkflow.Sdk/ErrorStatus.cs +++ b/src/ServerlessWorkflow.Sdk/ErrorStatus.cs @@ -52,4 +52,4 @@ public static class ErrorStatus /// public const int Runtime = 500; -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/EventReadMode.cs b/src/ServerlessWorkflow.Sdk/EventReadMode.cs index 6da631b..a831506 100644 --- a/src/ServerlessWorkflow.Sdk/EventReadMode.cs +++ b/src/ServerlessWorkflow.Sdk/EventReadMode.cs @@ -32,6 +32,11 @@ public static class EventReadMode /// public const string Raw = "raw"; + /// + /// Gets an containing all supported event read modes + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing all supported event read modes /// diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/RetryingTaskEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/RetryingTaskEvent.cs new file mode 100644 index 0000000..40e8ee8 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/RetryingTaskEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task is being retried +/// +[DataContract] +public sealed record RetryingTaskEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that is being retried belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that is being retried + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the task is being retried + /// + [DataMember(Name = "retryingAt", Order = 3), JsonPropertyName("retryingAt"), JsonPropertyOrder(3)] + public DateTimeOffset RetryingAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCancelledEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCancelledEvent.cs new file mode 100644 index 0000000..4b2cf9c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCancelledEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that the execution of a task has been cancelled +/// +[DataContract] +public sealed record TaskCancelledEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has been cancelled belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has been cancelled + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has been cancelled + /// + [DataMember(Name = "cancelledAt", Order = 2), JsonPropertyName("cancelledAt"), JsonPropertyOrder(2)] + public DateTimeOffset CancelledAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCompletedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCompletedEvent.cs new file mode 100644 index 0000000..b2676d1 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCompletedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task ran to completion +/// +[DataContract] +public sealed record TaskCompletedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has completed belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has completed + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the task ran to completion + /// + [DataMember(Name = "completedAt", Order = 3), JsonPropertyName("completedAt"), JsonPropertyOrder(3)] + public DateTimeOffset CompletedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCreatedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCreatedEvent.cs new file mode 100644 index 0000000..53a7d90 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskCreatedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task has been created +/// +[DataContract] +public sealed record TaskCreatedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has been created belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has been created + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the task has been created + /// + [DataMember(Name = "createdAt", Order = 3), JsonPropertyName("createdAt"), JsonPropertyOrder(3)] + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskEndedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskEndedEvent.cs new file mode 100644 index 0000000..38f7297 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskEndedEvent.cs @@ -0,0 +1,47 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that the execution of a task has ended +/// +[DataContract] +public sealed record TaskEndedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has ended belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has ended + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required Uri Task { get; set; } + + /// + /// Gets/sets the status with which the task ended + /// + [DataMember(Name = "status", Order = 3), JsonPropertyName("status"), JsonPropertyOrder(3)] + public required string Status { get; set; } + + /// + /// Gets/sets the date and time at which the task has ended + /// + [DataMember(Name = "endedAt", Order = 4), JsonPropertyName("endedAt"), JsonPropertyOrder(4)] + public DateTimeOffset EndedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskFaultedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskFaultedEvent.cs new file mode 100644 index 0000000..73c7022 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskFaultedEvent.cs @@ -0,0 +1,47 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task has faulted +/// +[DataContract] +public sealed record TaskFaultedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has faulted belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has faulted + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the error that has cause the task to fault + /// + [DataMember(Name = "error", Order = 3), JsonPropertyName("error"), JsonPropertyOrder(3)] + public required Error Error { get; set; } + + /// + /// Gets/sets the date and time at which the task has faulted + /// + [DataMember(Name = "faultedAt", Order = 4), JsonPropertyName("faultedAt"), JsonPropertyOrder(4)] + public DateTimeOffset FaultedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskResumedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskResumedEvent.cs new file mode 100644 index 0000000..00e3fdb --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskResumedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task has been resumed +/// +[DataContract] +public sealed record TaskResumedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has been resumed + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has been resumed + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required Uri Task { get; set; } + + /// + /// Gets/sets the date and time at which the task has been resumed + /// + [DataMember(Name = "resumedAt", Order = 2), JsonPropertyName("resumedAt"), JsonPropertyOrder(2)] + public DateTimeOffset ResumedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskSkippedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskSkippedEvent.cs new file mode 100644 index 0000000..d1c4e71 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskSkippedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task has been skipped +/// +[DataContract] +public sealed record TaskSkippedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has been skipped belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has been skipped + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the task has been skipped + /// + [DataMember(Name = "skippedAt", Order = 3), JsonPropertyName("skippedAt"), JsonPropertyOrder(3)] + public DateTimeOffset SkippedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskStartedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskStartedEvent.cs new file mode 100644 index 0000000..5f2c473 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskStartedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that a task has started +/// +[DataContract] +public sealed record TaskStartedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has started belongs to + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has started + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the task has started + /// + [DataMember(Name = "startedAt", Order = 3), JsonPropertyName("startedAt"), JsonPropertyOrder(3)] + public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskSuspendedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskSuspendedEvent.cs new file mode 100644 index 0000000..b7512b7 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Tasks/TaskSuspendedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Tasks; + +/// +/// Represents the data carried by the cloud event that notifies that the execution of a task has been suspended +/// +[DataContract] +public sealed record TaskSuspendedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance the task that has been suspended + /// + [DataMember(Name = "workflow", Order = 1), JsonPropertyName("workflow"), JsonPropertyOrder(1)] + public required string Workflow { get; set; } + + /// + /// Gets/sets the reference of the task that has been suspended + /// + [DataMember(Name = "task", Order = 2), JsonPropertyName("task"), JsonPropertyOrder(2)] + public required JsonPointer Task { get; set; } + + /// + /// Gets/sets the date and time at which the task has been suspended + /// + [DataMember(Name = "suspendedAt", Order = 2), JsonPropertyName("suspendedAt"), JsonPropertyOrder(2)] + public DateTimeOffset SuspendedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCancelledEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCancelledEvent.cs new file mode 100644 index 0000000..d0c4ce6 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCancelledEvent.cs @@ -0,0 +1,35 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that the execution of a workflow instance has been cancelled +/// +[DataContract] +public sealed record WorkflowCancelledEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has been cancelled + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has been cancelled + /// + [DataMember(Name = "cancelledAt", Order = 2), JsonPropertyName("cancelledAt"), JsonPropertyOrder(2)] + public DateTimeOffset CancelledAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCompletedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCompletedEvent.cs new file mode 100644 index 0000000..6d4090c --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCompletedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that a workflow instance ran to completion +/// +[DataContract] +public sealed record WorkflowCompletedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that ran to completion + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance ran to completion + /// + [DataMember(Name = "completedAt", Order = 2), JsonPropertyName("completedAt"), JsonPropertyOrder(2)] + public DateTimeOffset CompletedAt { get; set; } = DateTimeOffset.Now; + + /// + /// Gets/sets the workflow instance's output + /// + [DataMember(Name = "output", Order = 3), JsonPropertyName("output"), JsonPropertyOrder(3)] + public object? Output { get; set; } + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCorrelationCompletedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCorrelationCompletedEvent.cs new file mode 100644 index 0000000..93aabdd --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCorrelationCompletedEvent.cs @@ -0,0 +1,47 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that a workflow instance has completed correlating events +/// +[DataContract] +public sealed record WorkflowCorrelationCompletedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has completed correlating events + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the id of the correlation context + /// + [DataMember(Name = "correlationContext", Order = 2), JsonPropertyName("correlationContext"), JsonPropertyOrder(2)] + public required string CorrelationContext { get; set; } + + /// + /// Gets a key/value mapping of the context's correlation keys + /// + [DataMember(Name = "correlationKeys", Order = 3), JsonPropertyName("correlationKeys"), JsonPropertyOrder(3)] + public required EquatableDictionary? CorrelationKeys { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has completed correlating events + /// + [DataMember(Name = "completedAt", Order = 4), JsonPropertyName("completedAt"), JsonPropertyOrder(4)] + public DateTimeOffset CompletedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCorrelationStartedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCorrelationStartedEvent.cs new file mode 100644 index 0000000..62193be --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowCorrelationStartedEvent.cs @@ -0,0 +1,35 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that a workflow instance has started correlating events +/// +[DataContract] +public sealed record WorkflowCorrelationStartedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has started correlating events + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has started correlating events + /// + [DataMember(Name = "startedAt", Order = 2), JsonPropertyName("startedAt"), JsonPropertyOrder(2)] + public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowEndedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowEndedEvent.cs new file mode 100644 index 0000000..8b0ed19 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowEndedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that the execution of a workflow instance has ended +/// +[DataContract] +public sealed record WorkflowEndedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has ended + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the status with which the workflow ended + /// + [DataMember(Name = "status", Order = 2), JsonPropertyName("status"), JsonPropertyOrder(2)] + public required string Status { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has ended + /// + [DataMember(Name = "endedAt", Order = 3), JsonPropertyName("endedAt"), JsonPropertyOrder(3)] + public DateTimeOffset EndedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowFaultedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowFaultedEvent.cs new file mode 100644 index 0000000..99f198e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowFaultedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that a workflow instance has faulted +/// +[DataContract] +public sealed record WorkflowFaultedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has faulted + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the error that has cause the workflow to fault + /// + [DataMember(Name = "error", Order = 2), JsonPropertyName("error"), JsonPropertyOrder(2)] + public required Error Error { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has faulted + /// + [DataMember(Name = "faultedAt", Order = 3), JsonPropertyName("faultedAt"), JsonPropertyOrder(3)] + public DateTimeOffset FaultedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowResumedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowResumedEvent.cs new file mode 100644 index 0000000..b3aa93b --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowResumedEvent.cs @@ -0,0 +1,35 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that a workflow instance has been resumed +/// +[DataContract] +public sealed record WorkflowResumedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has been resumed + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has been resumed + /// + [DataMember(Name = "resumedAt", Order = 2), JsonPropertyName("resumedAt"), JsonPropertyOrder(2)] + public DateTimeOffset ResumedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowStartedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowStartedEvent.cs new file mode 100644 index 0000000..12aa070 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowStartedEvent.cs @@ -0,0 +1,41 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that a workflow instance has started +/// +[DataContract] +public sealed record WorkflowStartedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has started + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the an object that describes the definition of the workflow instance that has started + /// + [DataMember(Name = "definition", Order = 2), JsonPropertyName("definition"), JsonPropertyOrder(2)] + public required WorkflowDefinitionReference Definition { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has started + /// + [DataMember(Name = "startedAt", Order = 3), JsonPropertyName("startedAt"), JsonPropertyOrder(3)] + public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.Now; + +} diff --git a/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowSuspendedEvent.cs b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowSuspendedEvent.cs new file mode 100644 index 0000000..e9127fa --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Events/Workflows/WorkflowSuspendedEvent.cs @@ -0,0 +1,35 @@ +// 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.Events.Workflows; + +/// +/// Represents the data carried by the cloud event that notifies that the execution of a workflow instance has been suspended +/// +[DataContract] +public sealed record WorkflowSuspendedEvent +{ + + /// + /// Gets/sets the qualified name of the workflow instance that has been suspended + /// + [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1)] + public required string Name { get; set; } + + /// + /// Gets/sets the date and time at which the workflow instance has been suspended + /// + [DataMember(Name = "suspendedAt", Order = 2), JsonPropertyName("suspendedAt"), JsonPropertyOrder(2)] + public DateTimeOffset SuspendedAt { get; set; } = DateTimeOffset.Now; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Extendable.cs b/src/ServerlessWorkflow.Sdk/Extendable.cs similarity index 71% rename from src/ServerlessWorkflow.Sdk/Models/Extendable.cs rename to src/ServerlessWorkflow.Sdk/Extendable.cs index 38cc78e..84f5058 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Extendable.cs +++ b/src/ServerlessWorkflow.Sdk/Extendable.cs @@ -11,11 +11,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Models; +namespace ServerlessWorkflow.Sdk; /// /// Represents the base class of extendable objects /// +[Description("Represents the base class of extendable objects")] +[DataContract] public abstract record Extendable : IExtendable { @@ -23,7 +25,8 @@ public abstract record Extendable /// /// Gets/sets a key/value mapping of the object's extension data, if any /// - [DataMember(Name = "extensions", Order = 99), JsonPropertyName("extensions"), JsonPropertyOrder(99), YamlMember(Alias = "extensions", Order = 99)] - public virtual EquatableDictionary? Extensions { get; set; } + [Description("A key/value mapping of the object's extension data, if any")] + [DataMember(Order = 100, Name = "extensions"), JsonPropertyOrder(100), JsonPropertyName("extensions")] + public JsonObject? Extensions { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Extensions/DurationExtensions.cs b/src/ServerlessWorkflow.Sdk/Extensions/DateTimeOffsetExtensions.cs similarity index 50% rename from src/ServerlessWorkflow.Sdk/Extensions/DurationExtensions.cs rename to src/ServerlessWorkflow.Sdk/Extensions/DateTimeOffsetExtensions.cs index c90a145..a811484 100644 --- a/src/ServerlessWorkflow.Sdk/Extensions/DurationExtensions.cs +++ b/src/ServerlessWorkflow.Sdk/Extensions/DateTimeOffsetExtensions.cs @@ -11,21 +11,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models; - +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace ServerlessWorkflow.Sdk; /// -/// Defines extensions for s +/// Defines extensions for s /// -public static class DurationExtensions +public static class DateTimeOffsetExtensions { /// - /// Converts the into a new + /// Gets an object describing the specified /// - /// The to convert - /// A new - public static TimeSpan ToTimeSpan(this Duration duration) => new(duration.Days.HasValue ? (int)duration.Days : 0, duration.Hours.HasValue ? (int)duration.Hours : 0, duration.Minutes.HasValue ? (int)duration.Minutes : 0, duration.Seconds.HasValue ? (int)duration.Seconds : 0, duration.Milliseconds.HasValue ? (int)duration.Milliseconds : 0); + /// The to get the descriptor of + /// A new describing the specified + public static DateTimeDescriptor GetDescriptor(this DateTimeOffset dateTime) => new() + { + Iso8601 = dateTime.ToString("o"), + Epoch = new() + { + Milliseconds = (ulong)dateTime.ToUnixTimeSeconds() * 1000 + } + }; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Extensions/ExceptionExtensions.cs b/src/ServerlessWorkflow.Sdk/Extensions/ExceptionExtensions.cs deleted file mode 100644 index ce03035..0000000 --- a/src/ServerlessWorkflow.Sdk/Extensions/ExceptionExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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 System.Net; - -namespace ServerlessWorkflow.Sdk; - -/// -/// Defines extensions for s -/// -public static class ExceptionExtensions -{ - - /// - /// Converts the into a new - /// - /// The to convert - /// The , if any, that references the instance to which the to create applies - /// A new based on the specified - public static Error ToError(this Exception ex, Uri? instance = null) - { - return ex switch - { - HttpRequestException httpEx => new() - { - Status = (ushort)(httpEx.StatusCode ?? HttpStatusCode.InternalServerError), - Type = ErrorType.Communication, - Title = ErrorTitle.Communication, - Instance = instance, - Detail = httpEx.Message - }, - _ => new() - { - Status = ErrorStatus.Runtime, - Type = ErrorType.Runtime, - Title = ErrorTitle.Runtime, - Instance = instance, - Detail = ex.Message - } - }; - } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Extensions/IServiceCollectionExtensions.cs b/src/ServerlessWorkflow.Sdk/Extensions/IServiceCollectionExtensions.cs deleted file mode 100644 index 8ca31ed..0000000 --- a/src/ServerlessWorkflow.Sdk/Extensions/IServiceCollectionExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; -using Neuroglia.Serialization.Yaml; -using ServerlessWorkflow.Sdk.Models; -using ServerlessWorkflow.Sdk.Serialization.Yaml; -using ServerlessWorkflow.Sdk.Validation; - -namespace ServerlessWorkflow.Sdk; - -/// -/// Defines extensions for s -/// -public static class IServiceCollectionExtensions -{ - - /// - /// Adds and configures ServerlessWorkflow IO services - /// - /// The to configure - /// The configured - public static IServiceCollection AddServerlessWorkflowValidation(this IServiceCollection services) - { - services.AddHttpClient(); - services.AddJsonSerializer(); - services.AddYamlDotNetSerializer(options => - { - YamlSerializer.DefaultSerializerConfiguration(options.Serializer); - YamlSerializer.DefaultDeserializerConfiguration(options.Deserializer); - options.Deserializer.WithNodeDeserializer( - inner => new TaskDefinitionYamlDeserializer(inner), - syntax => syntax.InsteadOf()); - options.Deserializer.WithNodeDeserializer( - inner => new OneOfNodeDeserializer(inner), - syntax => syntax.InsteadOf()); - options.Deserializer.WithNodeDeserializer( - inner => new OneOfScalarDeserializer(inner), - syntax => syntax.InsteadOf()); - var mapEntryConverter = new MapEntryYamlConverter(() => options.Serializer.Build(), () => options.Deserializer.Build()); - options.Deserializer.WithTypeConverter(mapEntryConverter); - options.Serializer.WithTypeConverter(mapEntryConverter); - options.Serializer.WithTypeConverter(new OneOfConverter()); - }); - services.AddSingleton(); - services.AddValidatorsFromAssemblyContaining(); - var defaultPropertyNameResolver = ValidatorOptions.Global.PropertyNameResolver; - ValidatorOptions.Global.PropertyNameResolver = (type, member, lambda) => member == null ? defaultPropertyNameResolver(type, member, lambda) : member.Name.ToCamelCase(); - return services; - } - -} diff --git a/src/ServerlessWorkflow.Sdk/Extensions/StringExtensions.cs b/src/ServerlessWorkflow.Sdk/Extensions/StringExtensions.cs new file mode 100644 index 0000000..3d2845e --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Extensions/StringExtensions.cs @@ -0,0 +1,30 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk; + +/// +/// Defines extensions for s +/// +public static class StringExtensions +{ + + /// + /// Determines whether the specified string is formatted as a runtime expression. + /// + /// The string to evaluate for runtime expression formatting. + /// true if the string starts with "${" and ends with "}"; otherwise, false. + public static bool IsRuntimeExpression(this string value) => value.TrimStart().StartsWith("${") && value.TrimEnd().EndsWith("}"); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Extensions/TypeExtensions.cs b/src/ServerlessWorkflow.Sdk/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..eec6985 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Extensions/TypeExtensions.cs @@ -0,0 +1,52 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ServerlessWorkflow.Sdk; + +/// +/// Defines extensions for s +/// +public static class TypeExtensions +{ + + /// + /// Determines whether the extended is an + /// + /// The type to check + /// A boolean indicating whether the extended is an + public static bool IsEnumerable(this Type extended) => typeof(IEnumerable).IsAssignableFrom(extended); + + /// + /// Gets the type's generic type of the specified generic type definition + /// + /// The extended type + /// The generic type definition to get the generic type of + /// The type's generic type of the specified generic type definition + public static Type? GetGenericType(this Type extended, Type genericTypeDefinition) + { + if (genericTypeDefinition is null) throw new ArgumentNullException(nameof(genericTypeDefinition)); + Type? baseType, result; + if (!genericTypeDefinition.IsGenericTypeDefinition) throw new ArgumentException("The specified type is not a generic type definition", nameof(genericTypeDefinition)); + baseType = extended; + while (baseType != null) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericTypeDefinition) return baseType; + result = baseType.GetInterfaces().Select(i => i.GetGenericType(genericTypeDefinition)).Where(t => t != null).FirstOrDefault(); + if (result != null) return result; + baseType = baseType.BaseType; + } + return null; + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Extensions/WorkflowDefinitionExtensions.cs b/src/ServerlessWorkflow.Sdk/Extensions/WorkflowDefinitionExtensions.cs index a3ff830..f52cb47 100644 --- a/src/ServerlessWorkflow.Sdk/Extensions/WorkflowDefinitionExtensions.cs +++ b/src/ServerlessWorkflow.Sdk/Extensions/WorkflowDefinitionExtensions.cs @@ -11,14 +11,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models; -using System.Collections; +#pragma warning disable IDE0130 // Namespace does not match folder structure using System.Reflection; namespace ServerlessWorkflow.Sdk; /// -/// Defines extensions for s +/// Defines extensions for s. /// public static class WorkflowDefinitionExtensions { @@ -33,11 +32,11 @@ public static class WorkflowDefinitionExtensions /// A new used to reference the public static Uri BuildReferenceTo(this WorkflowDefinition workflow, TaskDefinition task, string? path, Uri? parentReference = null) { - ArgumentNullException.ThrowIfNull(workflow); - ArgumentNullException.ThrowIfNull(task); + if (workflow is null) throw new ArgumentNullException(nameof(workflow)); + if (task is null) throw new ArgumentNullException(nameof(task)); if (string.IsNullOrWhiteSpace(path)) return parentReference ?? throw new ArgumentNullException(nameof(parentReference), "The parent must be set when the path to the task to execute is null (in case the task is a function)"); return parentReference == null - ? new Uri($"/{nameof(WorkflowDefinition.Do).ToCamelCase()}/{workflow.Do.Keys.ToList().IndexOf(path)}/{path}", UriKind.Relative) + ? new Uri($"/{JsonNamingPolicy.CamelCase.ConvertName(nameof(WorkflowDefinition.Do))}/{workflow.Do.Keys.ToList().IndexOf(path!)}/{path}", UriKind.Relative) : new Uri($"{parentReference.OriginalString}/{path}", UriKind.Relative); } @@ -59,8 +58,8 @@ public static Uri BuildReferenceTo(this WorkflowDefinition workflow, TaskDefinit /// The component at the specified path public static TComponent GetComponent(this WorkflowDefinition workflow, string path) { - ArgumentException.ThrowIfNullOrWhiteSpace(path); - var pathSegments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); + var pathSegments = path.Split('/'); var currentObject = workflow as object; foreach (var pathSegment in pathSegments) { @@ -88,9 +87,28 @@ public static TComponent GetComponent(this WorkflowDefinition workfl /// The specified public static AuthenticationPolicyDefinition GetAuthenticationPolicy(this WorkflowDefinition workflow, string nameOrReference) { - ArgumentException.ThrowIfNullOrWhiteSpace(nameOrReference); - if (nameOrReference.StartsWith('/') && Uri.TryCreate(nameOrReference, UriKind.Relative, out var uri) && uri != null) return workflow.GetComponent(nameOrReference); + if (string.IsNullOrWhiteSpace(nameOrReference)) throw new ArgumentNullException(nameof(nameOrReference)); + if (nameOrReference.StartsWith("/") && Uri.TryCreate(nameOrReference, UriKind.Relative, out var uri) && uri != null) return workflow.GetComponent(nameOrReference); else return workflow.Use?.Authentications?.FirstOrDefault(a => string.Equals(a.Key, nameOrReference, StringComparison.OrdinalIgnoreCase)).Value ?? throw new NullReferenceException($"Failed to find an authentication policy definition with the specified name '{nameOrReference}'"); } + /// + /// Gets the qualified name of the workflow definition, which is a combination of its namespace, name, and version in the format "namespace.name:version". + /// + /// The workflow definition for which to get the qualified name. + /// The qualified name of the workflow definition. + public static string GetQualifiedName(this WorkflowDefinition definition) => $"{definition.Document.Namespace}.{definition.Document.Name}:{definition.Document.Version}"; + + /// + /// Creates a new used to reference the + /// + /// The to create a new reference for. + /// A new + public static WorkflowDefinitionReference GetReference(this WorkflowDefinition definition) => new() + { + Namespace = definition.Document.Namespace, + Name = definition.Document.Name, + Version = definition.Document.Version + }; + } diff --git a/src/ServerlessWorkflow.Sdk/FlowDirective.cs b/src/ServerlessWorkflow.Sdk/FlowDirective.cs index 3ea4eb7..1796d3c 100644 --- a/src/ServerlessWorkflow.Sdk/FlowDirective.cs +++ b/src/ServerlessWorkflow.Sdk/FlowDirective.cs @@ -32,4 +32,20 @@ public static class FlowDirective /// public const string Exit = "exit"; + /// + /// Gets an containing all supported flow directives + /// + public static readonly IEnumerable All = AsEnumerable(); + + /// + /// Gets a new containing all supported flow directives + /// + /// An containing all supported flow directives + public static IEnumerable AsEnumerable() + { + yield return Continue; + yield return End; + yield return Exit; + } + } diff --git a/src/ServerlessWorkflow.Sdk/HttpOutputFormat.cs b/src/ServerlessWorkflow.Sdk/HttpOutputFormat.cs index 4e357d9..dd36fad 100644 --- a/src/ServerlessWorkflow.Sdk/HttpOutputFormat.cs +++ b/src/ServerlessWorkflow.Sdk/HttpOutputFormat.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models; - namespace ServerlessWorkflow.Sdk; /// diff --git a/src/ServerlessWorkflow.Sdk/IExtendable.cs b/src/ServerlessWorkflow.Sdk/IExtendable.cs index 0fe4338..affb050 100644 --- a/src/ServerlessWorkflow.Sdk/IExtendable.cs +++ b/src/ServerlessWorkflow.Sdk/IExtendable.cs @@ -22,6 +22,6 @@ public interface IExtendable /// /// Gets/sets a name/definition mapping of the component's extensions, if any /// - EquatableDictionary? Extensions { get; set; } + JsonObject? Extensions { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Map.cs b/src/ServerlessWorkflow.Sdk/Map.cs index 9af90db..0b57974 100644 --- a/src/ServerlessWorkflow.Sdk/Map.cs +++ b/src/ServerlessWorkflow.Sdk/Map.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Collections; - namespace ServerlessWorkflow.Sdk; /// @@ -20,44 +18,45 @@ namespace ServerlessWorkflow.Sdk; /// /// The type of keys to use /// The type of values to use -public record Map +[CollectionDataContract] +public sealed record Map : ICollection> where TKey : notnull { - readonly Dictionary _entries = []; + readonly Dictionary entries = []; /// /// Gets an that contains all the map's keys /// - public IReadOnlyList Keys => [.. this._entries.Keys]; + public IReadOnlyList Keys => [.. entries.Keys]; /// /// Gets an that contains all the map's values /// - public IReadOnlyList Values => [.. this._entries.Values]; + public IReadOnlyList Values => [.. entries.Values]; /// - public int Count => this._entries.Count; + public int Count => entries.Count; /// - public bool IsReadOnly => ((IDictionary)this._entries).IsReadOnly; + public bool IsReadOnly => ((IDictionary)entries).IsReadOnly; /// /// Gets/sets the value with the specified key /// - /// Tje key of the value to set + /// The key of the value to set /// The value at the specified key public TValue this[TKey key] { get { - if (!_entries.TryGetValue(key, out TValue? value)) throw new KeyNotFoundException($"The key '{key}' was not found in the map."); + if (!entries.TryGetValue(key, out TValue? value)) throw new KeyNotFoundException($"The key '{key}' was not found in the map."); return value; } set { - if (!_entries.TryAdd(key, value)) this._entries[key] = value; + entries[key] = value; } } @@ -66,33 +65,33 @@ public TValue this[TKey key] /// /// The key of the to get /// The with the specified key - public virtual MapEntry? GetEntry(TKey key) + public MapEntry? GetEntry(TKey key) { - var kvp = this._entries.FirstOrDefault(e => e.Key.Equals(key)); + var kvp = entries.FirstOrDefault(e => e.Key.Equals(key)); if (kvp.Key.Equals(default(TKey))) return null; else return new(kvp.Key, kvp.Value); } /// - public virtual void Add(MapEntry item) => this._entries[item.Key] = item.Value; + public void Add(MapEntry item) => entries[item.Key] = item.Value; /// - public virtual void Clear() => this._entries.Clear(); + public void Clear() => entries.Clear(); /// - public virtual bool Contains(MapEntry item) => this._entries.ContainsKey(item.Key); + public bool Contains(MapEntry item) => entries.ContainsKey(item.Key); /// - public virtual void CopyTo(MapEntry[] array, int arrayIndex) + public void CopyTo(MapEntry[] array, int arrayIndex) { - ArgumentNullException.ThrowIfNull(array); - ArgumentOutOfRangeException.ThrowIfLessThan(arrayIndex, 0); - if (arrayIndex + this.Count > array.Length) throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); - foreach (var entry in this) array[arrayIndex++] = entry; + if (array is null) throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "arrayIndex must be a non-negative integer."); + if (arrayIndex + Count > array.Length) throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); + foreach (var entry in this) array[arrayIndex++] = entry; } /// - public virtual bool Remove(MapEntry item) => this._entries.Remove(item.Key); + public bool Remove(MapEntry item) => entries.Remove(item.Key); /// /// Attempts to get the value with the specified key @@ -100,14 +99,14 @@ public virtual void CopyTo(MapEntry[] array, int arrayIndex) /// The kye of the value to get /// The value at the specified key, if any /// A boolean indicating whether or not the map contains the specified key - public virtual bool TryGetValue(TKey key, out TValue? value) => this._entries.TryGetValue(key, out value); + public bool TryGetValue(TKey key, out TValue? value) => entries.TryGetValue(key, out value); /// - public virtual IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { - foreach (var kvp in this._entries) yield return new(kvp.Key, kvp.Value); + foreach (var kvp in entries) yield return new(kvp.Key, kvp.Value); } - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/MapEntry.cs b/src/ServerlessWorkflow.Sdk/MapEntry.cs index 31ba995..180fe3d 100644 --- a/src/ServerlessWorkflow.Sdk/MapEntry.cs +++ b/src/ServerlessWorkflow.Sdk/MapEntry.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Serialization.Json; - namespace ServerlessWorkflow.Sdk; /// @@ -20,8 +18,8 @@ namespace ServerlessWorkflow.Sdk; /// /// The type of the entry's key /// The type of the entry's value -[JsonConverter(typeof(MapEntryJsonConverter))] -public record MapEntry +[JsonConverter(typeof(MapEntryJsonConverterFactory))] +public sealed record MapEntry { /// @@ -43,11 +41,11 @@ public MapEntry(TKey key, TValue value) /// /// Gets/sets the entry key /// - public TKey Key { get; set; } = default!; + public TKey Key { get; init; } = default!; /// /// Gets/sets the entry value /// - public TValue Value { get; set; } = default!; + public TValue Value { get; init; } = default!; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/AsyncApiMessageDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/AsyncApiMessageDefinition.cs index 5a1a91e..eb7a2d5 100644 --- a/src/ServerlessWorkflow.Sdk/Models/AsyncApiMessageDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/AsyncApiMessageDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an AsyncAPI message /// +[Description("Represents the definition of an AsyncAPI message")] [DataContract] -public record AsyncApiMessageDefinition +public sealed record AsyncApiMessageDefinition { /// /// Gets/sets the message's payload, if any /// - [DataMember(Name = "payload", Order = 1), JsonPropertyName("payload"), JsonPropertyOrder(1), YamlMember(Alias = "payload", Order = 1)] - public virtual object? Payload { get; set; } + [Description("The message's payload, if any")] + [DataMember(Order = 1, Name = "payload"), JsonPropertyOrder(1), JsonPropertyName("payload")] + public JsonNode? Payload { get; init; } /// /// Gets/sets the message's headers, if any /// - [DataMember(Name = "headers", Order = 2), JsonPropertyName("headers"), JsonPropertyOrder(2), YamlMember(Alias = "headers", Order = 2)] - public virtual object? Headers { get; set; } + [Description("The message's headers, if any")] + [DataMember(Order = 2, Name = "headers"), JsonPropertyOrder(2), JsonPropertyName("headers")] + public JsonObject? Headers { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionDefinition.cs index 6866115..f2fb896 100644 --- a/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionDefinition.cs @@ -16,27 +16,31 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to configure an AsyncAPI subscription /// +[Description("Represents an object used to configure an AsyncAPI subscription")] [DataContract] -public record AsyncApiSubscriptionDefinition +public sealed record AsyncApiSubscriptionDefinition { /// /// Gets/sets a runtime expression, if any, used to filter consumed messages /// - [DataMember(Name = "filter", Order = 1), JsonPropertyName("filter"), JsonPropertyOrder(1), YamlMember(Alias = "filter", Order = 1)] - public virtual string? Filter { get; set; } + [Description("A runtime expression, if any, used to filter consumed messages")] + [DataMember(Order = 1, Name = "filter"), JsonPropertyOrder(1), JsonPropertyName("filter")] + public string? Filter { get; init; } /// /// Gets/sets an object used to configure the subscription's lifetime. /// + [Description("An object used to configure the subscription's lifetime")] [Required] - [DataMember(Name = "consume", Order = 2), JsonPropertyName("consume"), JsonPropertyOrder(2), YamlMember(Alias = "consume", Order = 2)] - public required virtual AsyncApiSubscriptionLifetimeDefinition Consume { get; set; } + [DataMember(Order = 2, Name = "consume"), JsonPropertyOrder(2), JsonPropertyName("consume")] + public required AsyncApiSubscriptionLifetimeDefinition Consume { get; init; } /// /// Gets/sets the configuration of the iterator, if any, used to process each consumed message /// - [DataMember(Name = "foreach", Order = 3), JsonPropertyName("foreach"), JsonPropertyOrder(3), YamlMember(Alias = "foreach", Order = 3)] - public virtual SubscriptionIteratorDefinition? Foreach { get; set; } + [Description("The configuration of the iterator, if any, used to process each consumed message")] + [DataMember(Order = 3, Name = "foreach"), JsonPropertyOrder(3), JsonPropertyName("foreach")] + public SubscriptionIteratorDefinition? Foreach { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionLifetimeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionLifetimeDefinition.cs index 62422db..39dabae 100644 --- a/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionLifetimeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/AsyncApiSubscriptionLifetimeDefinition.cs @@ -16,35 +16,40 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to configure the lifetime of an AsyncAPI subscription /// +[Description("Represents an object used to configure the lifetime of an AsyncAPI subscription")] [DataContract] -public record AsyncApiSubscriptionLifetimeDefinition +public sealed record AsyncApiSubscriptionLifetimeDefinition { /// /// Gets/sets the duration that defines for how long to consume messages - /// /// - [DataMember(Name = "for", Order = 1), JsonPropertyName("for"), JsonPropertyOrder(1), YamlMember(Alias = "for", Order = 1)] - public virtual Duration? For { get; set; } + /// + [Description("The duration that defines for how long to consume messages")] + [DataMember(Order = 1, Name = "for"), JsonPropertyOrder(1), JsonPropertyName("for")] + public Duration? For { get; init; } /// /// Gets/sets the amount of messages to consume. /// Required if and have not been set. - /// /// - [DataMember(Name = "amount", Order = 2), JsonPropertyName("amount"), JsonPropertyOrder(2), YamlMember(Alias = "amount", Order = 2)] - public virtual int? Amount { get; set; } + /// + [Description("The amount of messages to consume. Required if While and Until have not been set.")] + [DataMember(Order = 2, Name = "amount"), JsonPropertyOrder(2), JsonPropertyName("amount")] + public int? Amount { get; init; } /// /// Gets/sets a runtime expression, if any, used to determine whether or not to keep consuming messages. /// Required if and have not been set. - /// /// - [DataMember(Name = "while", Order = 3), JsonPropertyName("while"), JsonPropertyOrder(3), YamlMember(Alias = "while", Order = 3)] - public virtual string? While { get; set; } + /// + [Description("A runtime expression, if any, used to determine whether or not to keep consuming messages. Required if Amount and Until have not been set.")] + [DataMember(Order = 3, Name = "while"), JsonPropertyOrder(3), JsonPropertyName("while")] + public string? While { get; init; } /// /// Gets/sets a runtime expression, if any, used to determine until when to consume messages.. /// Required if and have not been set. - /// /// - [DataMember(Name = "until", Order = 4), JsonPropertyName("until"), JsonPropertyOrder(4), YamlMember(Alias = "until", Order = 4)] - public virtual string? Until { get; set; } + /// + [Description("A runtime expression, if any, used to determine until when to consume messages. Required if Amount and While have not been set.")] + [DataMember(Order = 4, Name = "until"), JsonPropertyOrder(4), JsonPropertyName("until")] + public string? Until { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/BasicAuthenticationSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/BasicAuthenticationSchemeDefinition.cs index 31b7cdc..1edaf6d 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/BasicAuthenticationSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/BasicAuthenticationSchemeDefinition.cs @@ -11,30 +11,35 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Xml.Serialization; + namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of a basic authentication scheme /// +[Description("Represents the definition of a basic authentication scheme")] [DataContract] -public record BasicAuthenticationSchemeDefinition +public sealed record BasicAuthenticationSchemeDefinition : AuthenticationSchemeDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Scheme => AuthenticationScheme.Basic; /// /// Gets/sets the username used for authentication /// - [DataMember(Name = "username", Order = 1), JsonPropertyName("username"), JsonPropertyOrder(1), YamlMember(Alias = "username", Order = 1)] - public virtual string? Username { get; set; } + [Description("The username used for authentication")] + [DataMember(Order = 1, Name = "username"), JsonPropertyOrder(1), JsonPropertyName("username")] + public string? Username { get; init; } /// /// Gets/sets the password used for authentication /// - [DataMember(Name = "password", Order = 2), JsonPropertyName("password"), JsonPropertyOrder(2), YamlMember(Alias = "password", Order = 2)] - public virtual string? Password { get; set; } + [Description("The password used for authentication")] + [DataMember(Order = 2, Name = "password"), JsonPropertyOrder(2), JsonPropertyName("password")] + public string? Password { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/BearerAuthenticationSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/BearerAuthenticationSchemeDefinition.cs index 19e4545..f24133a 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/BearerAuthenticationSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/BearerAuthenticationSchemeDefinition.cs @@ -16,19 +16,21 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of a bearer authentication scheme /// +[Description("Represents the definition of a bearer authentication scheme")] [DataContract] -public record BearerAuthenticationSchemeDefinition +public sealed record BearerAuthenticationSchemeDefinition : AuthenticationSchemeDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Scheme => AuthenticationScheme.Bearer; /// /// Gets/sets the bearer token used for authentication /// - [DataMember(Name = "token", Order = 1), JsonPropertyName("token"), JsonPropertyOrder(1), YamlMember(Alias = "token", Order = 1)] - public virtual string? Token { get; set; } + [Description("The bearer token used for authentication")] + [DataMember(Order = 1, Name = "token"), JsonPropertyOrder(1), JsonPropertyName("token")] + public string? Token { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/CertificateAuthenticationSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/CertificateAuthenticationSchemeDefinition.cs index e122903..7df82a4 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/CertificateAuthenticationSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/CertificateAuthenticationSchemeDefinition.cs @@ -16,13 +16,14 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of a certificate authentication scheme /// +[Description("Represents the definition of a certificate authentication scheme")] [DataContract] -public record CertificateAuthenticationSchemeDefinition +public sealed record CertificateAuthenticationSchemeDefinition : AuthenticationSchemeDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Scheme => AuthenticationScheme.Certificate; } diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/DigestAuthenticationSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/DigestAuthenticationSchemeDefinition.cs index 8ab2e59..a88b9f3 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/DigestAuthenticationSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/DigestAuthenticationSchemeDefinition.cs @@ -16,25 +16,28 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of a digest authentication scheme /// +[Description("Represents the definition of a digest authentication scheme")] [DataContract] -public record DigestAuthenticationSchemeDefinition +public sealed record DigestAuthenticationSchemeDefinition : AuthenticationSchemeDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Scheme => AuthenticationScheme.Digest; /// /// Gets/sets the username used for authentication /// - [DataMember(Name = "username", Order = 1), JsonPropertyName("username"), JsonPropertyOrder(1), YamlMember(Alias = "username", Order = 1)] - public virtual string? Username { get; set; } + [Description("The username used for authentication")] + [DataMember(Order = 1, Name = "username"), JsonPropertyOrder(1), JsonPropertyName("username")] + public string? Username { get; init; } /// /// Gets/sets the password used for authentication /// - [DataMember(Name = "password", Order = 2), JsonPropertyName("password"), JsonPropertyOrder(2), YamlMember(Alias = "password", Order = 2)] - public virtual string? Password { get; set; } + [Description("The password used for authentication")] + [DataMember(Order = 2, Name = "password"), JsonPropertyOrder(2), JsonPropertyName("password")] + public string? Password { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationClientDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationClientDefinition.cs index f823b0e..e787788 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationClientDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationClientDefinition.cs @@ -16,32 +16,37 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of an OAUTH2 client /// +[Description("Represents the definition of an OAUTH2 client")] [DataContract] -public record OAuth2AuthenticationClientDefinition +public sealed record OAuth2AuthenticationClientDefinition { /// /// Gets/sets the OAUTH2 `client_id` to use. Required if 'Authentication' has NOT been set to 'none'. /// - [DataMember(Name = "id", Order = 1), JsonPropertyName("id"), JsonPropertyOrder(1), YamlMember(Alias = "id", Order = 1)] - public virtual string? Id { get; set; } + [Description("The OAUTH2 `client_id` to use. Required if 'Authentication' has NOT been set to 'none'.")] + [DataMember(Order = 1, Name = "id"), JsonPropertyOrder(1), JsonPropertyName("id")] + public string? Id { get; init; } /// /// Gets/sets the OAUTH2 `client_secret` to use, if any /// - [DataMember(Name = "secret", Order = 2), JsonPropertyName("secret"), JsonPropertyOrder(2), YamlMember(Alias = "secret", Order = 2)] - public virtual string? Secret { get; set; } + [Description("The OAUTH2 `client_secret` to use, if any")] + [DataMember(Order = 2, Name = "secret"), JsonPropertyOrder(2), JsonPropertyName("secret")] + public string? Secret { get; init; } /// /// Gets/sets a JWT containing a signed assertion with the application credentials /// - [DataMember(Name = "assertion", Order = 3), JsonPropertyName("assertion"), JsonPropertyOrder(3), YamlMember(Alias = "assertion", Order = 3)] - public virtual string? Assertion { get; set; } + [Description("A JWT containing a signed assertion with the application credentials")] + [DataMember(Order = 3, Name = "assertion"), JsonPropertyOrder(3), JsonPropertyName("assertion")] + public string? Assertion { get; init; } /// /// Gets/sets the authentication method to use to authenticate the client. Defaults to 'client_secret_post'. See /// - [DataMember(Name = "authentication", Order = 4), JsonPropertyName("authentication"), JsonPropertyOrder(4), YamlMember(Alias = "authentication", Order = 4)] - public virtual string? Authentication { get; set; } + [Description("The authentication method to use to authenticate the client. Defaults to 'client_secret_post'. See OAuth2ClientAuthenticationMethod")] + [DataMember(Order = 4, Name = "authentication"), JsonPropertyOrder(4), JsonPropertyName("authentication")] + public string? Authentication { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationEndpointsDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationEndpointsDefinition.cs index a96f835..9c76f99 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationEndpointsDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationEndpointsDefinition.cs @@ -16,29 +16,46 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the configuration of OAUTH2 endpoints /// +[Description("Represents the configuration of OAUTH2 endpoints")] [DataContract] -public record OAuth2AuthenticationEndpointsDefinition +public sealed record OAuth2AuthenticationEndpointsDefinition { + /// + /// Gets the relative path to the token endpoint + /// + public static readonly Uri TokenEndpoint = new Uri("/oauth2/token", UriKind.RelativeOrAbsolute); + /// + /// Gets the relative path to the revocation endpoint + /// + public static readonly Uri RevocationEndpoint = new Uri("/oauth2/revoke", UriKind.RelativeOrAbsolute); + /// + /// Gets the relative path to the introspection endpoint + /// + public static readonly Uri IntrospectionEndpoint = new Uri("/oauth2/introspect", UriKind.RelativeOrAbsolute); + /// /// Gets/sets the relative path to the token endpoint. Defaults to `/oauth2/token` /// + [Description("The relative path to the token endpoint. Defaults to `/oauth2/token`")] [Required] - [DataMember(Name = "token", Order = 1), JsonPropertyName("token"), JsonPropertyOrder(1), YamlMember(Alias = "token", Order = 1)] - public virtual Uri Token { get; set; } = new("/oauth2/token", UriKind.RelativeOrAbsolute); + [DataMember(Order = 1, Name = "token"), JsonPropertyOrder(1), JsonPropertyName("token")] + public Uri Token { get; init; } = TokenEndpoint; /// /// Gets/sets the relative path to the revocation endpoint. Defaults to `/oauth2/revoke` /// + [Description("The relative path to the revocation endpoint. Defaults to `/oauth2/revoke`")] [Required] - [DataMember(Name = "revocation", Order = 2), JsonPropertyName("revocation"), JsonPropertyOrder(2), YamlMember(Alias = "revocation", Order = 2)] - public virtual Uri Revocation { get; set; } = new("/oauth2/revoke", UriKind.RelativeOrAbsolute); + [DataMember(Order = 2, Name = "revocation"), JsonPropertyOrder(2), JsonPropertyName("revocation")] + public Uri Revocation { get; init; } = RevocationEndpoint; /// /// Gets/sets the relative path to the introspection endpoint. Defaults to `/oauth2/introspect` /// + [Description("The relative path to the introspection endpoint. Defaults to `/oauth2/introspect`")] [Required] - [DataMember(Name = "introspection", Order = 3), JsonPropertyName("introspection"), JsonPropertyOrder(3), YamlMember(Alias = "introspection", Order = 3)] - public virtual Uri Introspection { get; set; } = new("/oauth2/introspect", UriKind.RelativeOrAbsolute); + [DataMember(Order = 3, Name = "introspection"), JsonPropertyOrder(3), JsonPropertyName("introspection")] + public Uri Introspection { get; init; } = IntrospectionEndpoint; } diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationRequestDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationRequestDefinition.cs index bc9d091..8a2e54f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationRequestDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationRequestDefinition.cs @@ -16,14 +16,17 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the configuration of an OAUTH2 authentication request /// +[Description("Represents the configuration of an OAUTH2 authentication request")] [DataContract] -public record OAuth2AuthenticationRequestDefinition +public sealed record OAuth2AuthenticationRequestDefinition { /// /// Gets/sets the encoding of the authentication request. Defaults to 'application/x-www-form-urlencoded'. See /// - [DataMember(Name = "encoding", Order = 1), JsonPropertyName("encoding"), JsonPropertyOrder(1), YamlMember(Alias = "encoding", Order = 1)] - public virtual string Encoding { get; set; } = OAuth2RequestEncoding.FormUrl; + [Description("The encoding of the authentication request. Defaults to 'application/x-www-form-urlencoded'. See OAuth2RequestEncoding")] + [Required, StringLength(int.MaxValue, MinimumLength = 1)] + [DataMember(Order = 1, Name = "encoding"), JsonPropertyOrder(1), JsonPropertyName("encoding")] + public string Encoding { get; init; } = OAuth2RequestEncoding.FormUrl; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinition.cs index 40a8fb0..cd33c3b 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinition.cs @@ -16,19 +16,21 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of an OAUTH2 authentication scheme /// +[Description("Represents the definition of an OAUTH2 authentication scheme")] [DataContract] -public record OAuth2AuthenticationSchemeDefinition +public sealed record OAuth2AuthenticationSchemeDefinition : OAuth2AuthenticationSchemeDefinitionBase { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Scheme => AuthenticationScheme.OAuth2; /// /// Gets/sets the configuration of the OAUTH2 endpoints to use /// - [DataMember(Name = "endpoints", Order = 2), JsonPropertyName("endpoints"), JsonPropertyOrder(2), YamlMember(Alias = "endpoints", Order = 2)] - public virtual OAuth2AuthenticationEndpointsDefinition Endpoints { get; set; } = new(); + [Description("The configuration of the OAUTH2 endpoints to use")] + [DataMember(Order = 1, Name = "endpoints"), JsonPropertyOrder(1), JsonPropertyName("endpoints")] + public OAuth2AuthenticationEndpointsDefinition? Endpoints { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinitionBase.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinitionBase.cs index 5345bdd..a90066b 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinitionBase.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2AuthenticationSchemeDefinitionBase.cs @@ -16,6 +16,7 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the base class for all authentication schemes based on OAUTH2 /// +[Description("Represents the base class for all authentication schemes based on OAUTH2")] [DataContract] public abstract record OAuth2AuthenticationSchemeDefinitionBase : AuthenticationSchemeDefinition @@ -24,68 +25,79 @@ public abstract record OAuth2AuthenticationSchemeDefinitionBase /// /// Gets/sets the uri that references the OAUTH2 authority to use /// - [DataMember(Name = "authority", Order = 1), JsonPropertyName("authority"), JsonPropertyOrder(1), YamlMember(Alias = "authority", Order = 1)] - public virtual Uri? Authority { get; set; } + [Description("The uri that references the OAUTH2 authority to use")] + [DataMember(Order = 1, Name = "authority"), JsonPropertyOrder(1), JsonPropertyName("authority")] + public Uri? Authority { get; init; } /// /// Gets/sets the grant type to use. See /// - [DataMember(Name = "grant", Order = 2), JsonPropertyName("grant"), JsonPropertyOrder(2), YamlMember(Alias = "grant", Order = 2)] - public virtual string? Grant { get; set; } + [Description("The grant type to use. See OAuth2GrantType")] + [DataMember(Order = 2, Name = "grant"), JsonPropertyOrder(2), JsonPropertyName("grant")] + public string? Grant { get; init; } /// /// Gets/sets the definition of the client to use /// - [DataMember(Name = "client", Order = 3), JsonPropertyName("client"), JsonPropertyOrder(3), YamlMember(Alias = "client", Order = 3)] - public virtual OAuth2AuthenticationClientDefinition? Client { get; set; } + [Description("The definition of the client to use")] + [DataMember(Order = 3, Name = "client"), JsonPropertyOrder(3), JsonPropertyName("client")] + public OAuth2AuthenticationClientDefinition? Client { get; init; } /// /// Gets/sets the configuration of the authentication request to perform /// - [DataMember(Name = "request", Order = 4), JsonPropertyName("request"), JsonPropertyOrder(4), YamlMember(Alias = "request", Order = 4)] - public virtual OAuth2AuthenticationRequestDefinition? Request { get; set; } + [Description("The configuration of the authentication request to perform")] + [DataMember(Order = 4, Name = "request"), JsonPropertyOrder(4), JsonPropertyName("request")] + public OAuth2AuthenticationRequestDefinition? Request { get; init; } /// /// Gets/sets a list, if any, that contains valid issuers that will be used to check against the issuer of generated tokens /// - [DataMember(Name = "issuers", Order = 5), JsonPropertyName("issuers"), JsonPropertyOrder(5), YamlMember(Alias = "issuers", Order = 5)] - public virtual EquatableList? Issuers { get; set; } + [Description("A list, if any, that contains valid issuers that will be used to check against the issuer of generated tokens")] + [DataMember(Order = 5, Name = "issuers"), JsonPropertyOrder(5), JsonPropertyName("issuers")] + public EquatableList? Issuers { get; init; } /// /// Gets/sets the scopes, if any, to request the token for /// - [DataMember(Name = "scopes", Order = 6), JsonPropertyName("scopes"), JsonPropertyOrder(6), YamlMember(Alias = "scopes", Order = 6)] - public virtual EquatableList? Scopes { get; set; } + [Description("The scopes, if any, to request the token for")] + [DataMember(Order = 6, Name = "scopes"), JsonPropertyOrder(6), JsonPropertyName("scopes")] + public EquatableList? Scopes { get; init; } /// /// Gets/sets the audiences, if any, to request the token for /// - [DataMember(Name = "audiences", Order = 7), JsonPropertyName("audiences"), JsonPropertyOrder(7), YamlMember(Alias = "audiences", Order = 7)] - public virtual EquatableList? Audiences { get; set; } + [Description("The audiences, if any, to request the token for")] + [DataMember(Order = 7, Name = "audiences"), JsonPropertyOrder(7), JsonPropertyName("audiences")] + public EquatableList? Audiences { get; init; } /// /// Gets/sets the username to use. Used only if is /// - [DataMember(Name = "username", Order = 8), JsonPropertyName("username"), JsonPropertyOrder(8), YamlMember(Alias = "username", Order = 8)] - public virtual string? Username { get; set; } + [Description("The username to use. Used only if Grant is 'Password'")] + [DataMember(Order = 8, Name = "username"), JsonPropertyOrder(8), JsonPropertyName("username")] + public string? Username { get; init; } /// /// Gets/sets the password to use. Used only if is /// - [DataMember(Name = "password", Order = 9), JsonPropertyName("password"), JsonPropertyOrder(9), YamlMember(Alias = "password", Order = 9)] - public virtual string? Password { get; set; } + [Description("The password to use. Used only if Grant is 'Password'")] + [DataMember(Order = 9, Name = "password"), JsonPropertyOrder(9), JsonPropertyName("password")] + public string? Password { get; init; } /// /// Gets/sets the security token that represents the identity of the party on behalf of whom the request is being made. Used only if is , in which case it is required /// - [DataMember(Name = "subject", Order = 10), JsonPropertyName("subject"), JsonPropertyOrder(10), YamlMember(Alias = "subject", Order = 10)] - public virtual OAuth2TokenDefinition? Subject { get; set; } + [Description("The security token that represents the identity of the party on behalf of whom the request is being made. Used only if Grant is 'TokenExchange', in which case it is required")] + [DataMember(Order = 10, Name = "subject"), JsonPropertyOrder(10), JsonPropertyName("subject")] + public OAuth2TokenDefinition? Subject { get; init; } /// /// Gets/sets the security token that represents the identity of the acting party. Typically, this will be the party that is authorized to use the requested security token and act on behalf of the subject. /// Used only if is , in which case it is required /// - [DataMember(Name = "actor", Order = 11), JsonPropertyName("actor"), JsonPropertyOrder(11), YamlMember(Alias = "actor", Order = 11)] - public virtual OAuth2TokenDefinition? Actor { get; set; } + [Description("The security token that represents the identity of the acting party. Typically, this will be the party that is authorized to use the requested security token and act on behalf of the subject. Used only if Grant is 'TokenExchange', in which case it is required")] + [DataMember(Order = 11, Name = "actor"), JsonPropertyOrder(11), JsonPropertyName("actor")] + public OAuth2TokenDefinition? Actor { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2TokenDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2TokenDefinition.cs index 1c66841..dcdd0a0 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2TokenDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OAuth2TokenDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of an OAUTH2 token /// +[Description("Represents the definition of an OAUTH2 token")] [DataContract] -public record OAuth2TokenDefinition +public sealed record OAuth2TokenDefinition { /// /// Gets/sets the security token to use /// - [DataMember(Name = "token", Order = 1), JsonPropertyName("token"), JsonPropertyOrder(1), YamlMember(Alias = "token", Order = 1)] - public required virtual string Token { get; set; } + [Description("The security token to use")] + [DataMember(Order = 1, Name = "token"), JsonPropertyOrder(1), JsonPropertyName("token")] + public required string Token { get; init; } /// /// Gets/sets the type of security token to use /// - [DataMember(Name = "type", Order = 2), JsonPropertyName("type"), JsonPropertyOrder(2), YamlMember(Alias = "type", Order = 2)] - public required virtual string Type { get; set; } + [Description("The type of security token to use")] + [DataMember(Order = 2, Name = "type"), JsonPropertyOrder(2), JsonPropertyName("type")] + public required string Type { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Authentication/OpenIDConnectSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Authentication/OpenIDConnectSchemeDefinition.cs index 2373653..fb38988 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Authentication/OpenIDConnectSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Authentication/OpenIDConnectSchemeDefinition.cs @@ -16,13 +16,14 @@ namespace ServerlessWorkflow.Sdk.Models.Authentication; /// /// Represents the definition of an OpenIDConnect authentication scheme /// +[Description("Represents the definition of an OpenIDConnect authentication scheme")] [DataContract] -public record OpenIDConnectSchemeDefinition +public sealed record OpenIDConnectSchemeDefinition : OAuth2AuthenticationSchemeDefinitionBase { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Scheme => AuthenticationScheme.OpenIDConnect; } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/AuthenticationPolicyDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/AuthenticationPolicyDefinition.cs index 72bde98..2ff61de 100644 --- a/src/ServerlessWorkflow.Sdk/Models/AuthenticationPolicyDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/AuthenticationPolicyDefinition.cs @@ -11,64 +11,70 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Authentication; - namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an authentication policy /// +[Description("Represents the definition of an authentication policy")] [DataContract] -public record AuthenticationPolicyDefinition +public sealed record AuthenticationPolicyDefinition : ReferenceableComponentDefinition { /// /// Gets the configured authentication scheme /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string Scheme => this.Basic?.Scheme ?? this.Bearer?.Scheme ?? this.Certificate?.Scheme ?? this.Digest?.Scheme ?? this.OAuth2?.Scheme ?? this.Oidc?.Scheme ?? throw new NullReferenceException(); + [IgnoreDataMember, JsonIgnore] + public string Scheme => Basic?.Scheme ?? Bearer?.Scheme ?? Certificate?.Scheme ?? Digest?.Scheme ?? OAuth2?.Scheme ?? Oidc?.Scheme ?? throw new NullReferenceException(); /// /// Gets/sets the name of the top level authentication policy to use, if any /// - [DataMember(Name = "use", Order = 1), JsonPropertyName("use"), JsonPropertyOrder(1), YamlMember(Alias = "use", Order = 1)] - public virtual string? Use { get; set; } + [Description("The name of the top level authentication policy to use, if any")] + [DataMember(Order = 1, Name = "use"), JsonPropertyOrder(1), JsonPropertyName("use")] + public string? Use { get; init; } /// /// Gets/sets the `basic` authentication scheme to use, if any /// - [DataMember(Name = "basic", Order = 1), JsonPropertyName("basic"), JsonPropertyOrder(1), YamlMember(Alias = "basic", Order = 1)] - public virtual BasicAuthenticationSchemeDefinition? Basic { get; set; } + [Description("The `basic` authentication scheme to use, if any)")] + [DataMember(Order = 2, Name = "basic"), JsonPropertyOrder(2), JsonPropertyName("basic")] + public BasicAuthenticationSchemeDefinition? Basic { get; init; } /// /// Gets/sets the `Bearer` authentication scheme to use, if any /// - [DataMember(Name = "bearer", Order = 2), JsonPropertyName("bearer"), JsonPropertyOrder(2), YamlMember(Alias = "bearer", Order = 2)] - public virtual BearerAuthenticationSchemeDefinition? Bearer { get; set; } + [Description("The `Bearer` authentication scheme to use, if any)")] + [DataMember(Order = 3, Name = "bearer"), JsonPropertyOrder(3), JsonPropertyName("bearer")] + public BearerAuthenticationSchemeDefinition? Bearer { get; init; } /// /// Gets/sets the `Certificate` authentication scheme to use, if any /// - [DataMember(Name = "certificate", Order = 3), JsonPropertyName("certificate"), JsonPropertyOrder(3), YamlMember(Alias = "certificate", Order = 3)] - public virtual CertificateAuthenticationSchemeDefinition? Certificate { get; set; } + [Description("The `Certificate` authentication scheme to use, if any)")] + [DataMember(Order = 4, Name = "certificate"), JsonPropertyOrder(4), JsonPropertyName("certificate")] + public CertificateAuthenticationSchemeDefinition? Certificate { get; init; } /// /// Gets/sets the `Digest` authentication scheme to use, if any /// - [DataMember(Name = "digest", Order = 4), JsonPropertyName("digest"), JsonPropertyOrder(4), YamlMember(Alias = "digest", Order = 4)] - public virtual DigestAuthenticationSchemeDefinition? Digest { get; set; } + [Description("The `Digest` authentication scheme to use, if any'")] + [DataMember(Order = 5, Name = "digest"), JsonPropertyOrder(5), JsonPropertyName("digest")] + public DigestAuthenticationSchemeDefinition? Digest { get; init; } /// /// Gets/sets the `OAUTH2` authentication scheme to use, if any /// - [DataMember(Name = "oauth2", Order = 5), JsonPropertyName("oauth2"), JsonPropertyOrder(5), YamlMember(Alias = "oauth2", Order = 5)] - public virtual OAuth2AuthenticationSchemeDefinition? OAuth2 { get; set; } + [Description("The `OAUTH2` authentication scheme to use, if any)")] + [DataMember(Order = 6, Name = "oauth2"), JsonPropertyOrder(6), JsonPropertyName("oauth2")] + public OAuth2AuthenticationSchemeDefinition? OAuth2 { get; init; } /// /// Gets/sets the `OIDC` authentication scheme to use, if any /// - [DataMember(Name = "oidc", Order = 6), JsonPropertyName("oidc"), JsonPropertyOrder(6), YamlMember(Alias = "oidc", Order = 6)] - public virtual OpenIDConnectSchemeDefinition? Oidc { get; set; } + [Description("The `OIDC` authentication scheme to use, if any)")] + [DataMember(Order = 7, Name = "oidc"), JsonPropertyOrder(7), JsonPropertyName("oidc")] + public OpenIDConnectSchemeDefinition? Oidc { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/AuthenticationSchemeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/AuthenticationSchemeDefinition.cs index f0fcd0c..22c7d2b 100644 --- a/src/ServerlessWorkflow.Sdk/Models/AuthenticationSchemeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/AuthenticationSchemeDefinition.cs @@ -16,6 +16,7 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the base class for all authentication scheme definitions /// +[Description("Represents the base class for all authentication scheme definitions")] [DataContract] public abstract record AuthenticationSchemeDefinition : Extendable @@ -24,13 +25,14 @@ public abstract record AuthenticationSchemeDefinition /// /// Gets the name of the authentication scheme /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public abstract string Scheme { get; } /// /// Gets/sets the name of the secret, if any, used to configure the authentication scheme /// - [DataMember(Name = "use", Order = 1), JsonPropertyName("use"), JsonPropertyOrder(1), YamlMember(Alias = "use", Order = 1)] - public virtual string? Use { get; set; } + [Description("The name of the secret, if any, used to configure the authentication scheme")] + [DataMember(Order = 1, Name = "use"), JsonPropertyOrder(1), JsonPropertyName("use")] + public string? Use { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/BackoffDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/BackoffDefinition.cs index b06a00f..b46ba8f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/BackoffDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/BackoffDefinition.cs @@ -16,6 +16,7 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the base class of all backoff definitions /// +[Description("Represents the base class of all backoff definitions")] [DataContract] public abstract record BackoffDefinition { diff --git a/src/ServerlessWorkflow.Sdk/Models/BackoffStrategyDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/BackoffStrategyDefinition.cs index af64167..2c091b1 100644 --- a/src/ServerlessWorkflow.Sdk/Models/BackoffStrategyDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/BackoffStrategyDefinition.cs @@ -16,26 +16,30 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a retry backoff strategy /// +[Description("Represents the definition of a retry backoff strategy")] [DataContract] -public record BackoffStrategyDefinition +public sealed record BackoffStrategyDefinition { /// /// Gets/sets the definition of the constant backoff to use, if any /// - [DataMember(Name = "constant", Order = 1), JsonPropertyName("constant"), JsonPropertyOrder(1), YamlMember(Alias = "constant", Order = 1)] - public virtual ConstantBackoffDefinition? Constant { get; set; } + [Description("The definition of the constant backoff to use, if any")] + [DataMember(Order = 1, Name = "constant"), JsonPropertyOrder(1), JsonPropertyName("constant")] + public ConstantBackoffDefinition? Constant { get; init; } /// /// Gets/sets the definition of the exponential backoff to use, if any /// - [DataMember(Name = "exponential", Order = 2), JsonPropertyName("exponential"), JsonPropertyOrder(2), YamlMember(Alias = "exponential", Order = 2)] - public virtual ExponentialBackoffDefinition? Exponential { get; set; } + [Description("The definition of the exponential backoff to use, if any")] + [DataMember(Order = 2, Name = "exponential"), JsonPropertyOrder(2), JsonPropertyName("exponential")] + public ExponentialBackoffDefinition? Exponential { get; init; } /// /// Gets/sets the definition of the linear backoff to use, if any /// - [DataMember(Name = "linear", Order = 3), JsonPropertyName("linear"), JsonPropertyOrder(3), YamlMember(Alias = "linear", Order = 3)] - public virtual LinearBackoffDefinition? Linear { get; set; } + [Description("The definition of the linear backoff to use, if any")] + [DataMember(Order = 3, Name = "linear"), JsonPropertyOrder(3), JsonPropertyName("linear")] + public LinearBackoffDefinition? Linear { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/BranchingDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/BranchingDefinition.cs index 09bd3d0..1d4a882 100644 --- a/src/ServerlessWorkflow.Sdk/Models/BranchingDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/BranchingDefinition.cs @@ -16,21 +16,24 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to configure branches to perform concurrently /// +[Description("Represents an object used to configure branches to perform concurrently")] [DataContract] -public record BranchingDefinition +public sealed record BranchingDefinition { /// /// Gets/sets a name/definition mapping of the subtasks to perform concurrently /// + [Description("A name/definition mapping of the subtasks to perform concurrently")] [Required, MinLength(1)] - [DataMember(Name = "branches", Order = 1), JsonPropertyName("branches"), JsonPropertyOrder(1), YamlMember(Alias = "branches", Order = 1)] - public required virtual Map Branches { get; set; } + [DataMember(Order = 1, Name = "branches"), JsonPropertyOrder(1), JsonPropertyName("branches")] + public required Map Branches { get; init; } /// /// Gets/sets a boolean indicating whether or not the branches should compete each other. If `true` and if a branch completes, it will cancel all other branches then it will return its output as the task's output /// - [DataMember(Name = "compete", Order = 1), JsonPropertyName("compete"), JsonPropertyOrder(1), YamlMember(Alias = "compete", Order = 1)] - public virtual bool Compete { get; set; } + [Description("A boolean indicating whether or not the branches should compete each other. If `true` and if a branch completes, it will cancel all other branches then it will return its output as the task's output")] + [DataMember(Order = 2, Name = "compete"), JsonPropertyOrder(2), JsonPropertyName("compete")] + public bool Compete { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/CallDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/CallDefinition.cs index 0e7cda4..e416b94 100644 --- a/src/ServerlessWorkflow.Sdk/Models/CallDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/CallDefinition.cs @@ -16,6 +16,7 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the base class for all call definitions /// +[Description("Represents the base class for all call definitions")] [DataContract] public abstract record CallDefinition { diff --git a/src/ServerlessWorkflow.Sdk/Models/Calls/AsyncApiCallDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Calls/AsyncApiCallDefinition.cs index 3ca6c18..f71df95 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Calls/AsyncApiCallDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Calls/AsyncApiCallDefinition.cs @@ -16,64 +16,73 @@ namespace ServerlessWorkflow.Sdk.Models.Calls; /// /// Represents the definition of an AsyncAPI call /// +[Description("Represents the definition of an AsyncAPI call")] [DataContract] -public record AsyncApiCallDefinition +public sealed record AsyncApiCallDefinition : CallDefinition { /// /// Gets/sets the document that defines the AsyncAPI operation to call /// + [Description("The document that defines the AsyncAPI operation to call")] [Required] - [DataMember(Name = "document", Order = 1), JsonPropertyName("document"), JsonPropertyOrder(1), YamlMember(Alias = "document", Order = 1)] - public required virtual ExternalResourceDefinition Document { get; set; } + [DataMember(Order = 1, Name = "document"), JsonPropertyOrder(1), JsonPropertyName("document")] + public required ExternalResourceDefinition Document { get; init; } /// /// Gets/sets the name of the channel on which to perform the operation. The operation to perform is defined by declaring either message, in which case the channel's publish operation will be executed, or subscription, in which case the channel's subscribe operation will be executed. /// Used only in case the referenced document uses AsyncAPI v2.6.0 /// - [DataMember(Name = "channel", Order = 2), JsonPropertyName("channel"), JsonPropertyOrder(2), JsonInclude, YamlMember(Alias = "channel", Order = 2)] - public virtual string? Channel { get; set; } + [Description("The name of the channel on which to perform the operation. The operation to perform is defined by declaring either message, in which case the channel's publish operation will be executed, or subscription, in which case the channel's subscribe operation will be executed. Used only in case the referenced document uses AsyncAPI v2.6.0")] + [DataMember(Order = 2, Name = "channel"), JsonPropertyOrder(2), JsonPropertyName("channel")] + public string? Channel { get; init; } /// /// Gets/sets a reference to the AsyncAPI operation to call. /// Used only in case the referenced document uses AsyncAPI v3.0.0. /// - [DataMember(Name = "operation", Order = 3), JsonPropertyName("operation"), JsonPropertyOrder(3), JsonInclude, YamlMember(Alias = "operation", Order = 3)] - public virtual string? Operation { get; set; } + [Description("A reference to the AsyncAPI operation to call. Used only in case the referenced document uses AsyncAPI v3.0.0.")] + [DataMember(Order = 3, Name = "operation"), JsonPropertyOrder(3), JsonPropertyName("operation")] + public string? Operation { get; init; } /// /// Gets/sets a object used to configure to the server to call the specified AsyncAPI operation on. /// If not set, default to the first server matching the operation's channel. /// - [DataMember(Name = "server", Order = 4), JsonPropertyName("server"), JsonPropertyOrder(4), JsonInclude, YamlMember(Alias = "server", Order = 4)] - public virtual string? Server { get; set; } + [Description("A object used to configure to the server to call the specified AsyncAPI operation on. If not set, default to the first server matching the operation's channel.")] + [DataMember(Order = 4, Name = "server"), JsonPropertyOrder(4), JsonPropertyName("server")] + public string? Server { get; init; } /// /// Gets/sets the protocol to use to select the target server. /// Ignored if has been set. /// - [DataMember(Name = "protocol", Order = 5), JsonPropertyName("protocol"), JsonPropertyOrder(5), JsonInclude, YamlMember(Alias = "protocol", Order = 5)] - public virtual string? Protocol { get; set; } + [Description("The protocol to use to select the target server. Ignored if Server has been set.")] + [DataMember(Order = 5, Name = "protocol"), JsonPropertyOrder(5), JsonPropertyName("protocol")] + public string? Protocol { get; init; } /// /// Gets/sets an object used to configure the message to publish using the target operation. /// Required if has not been set. /// - [DataMember(Name = "message", Order = 6), JsonPropertyName("message"), JsonPropertyOrder(6), JsonInclude, YamlMember(Alias = "message", Order = 6)] - public virtual AsyncApiMessageDefinition? Message { get; set; } + [Description("An object used to configure the message to publish using the target operation. Required if Subscription has not been set.")] + [DataMember(Order = 6, Name = "message"), JsonPropertyOrder(6), JsonPropertyName("message")] + public AsyncApiMessageDefinition? Message { get; init; } /// /// Gets/sets an object used to configure the subscription to messages consumed using the target operation. /// Required if has not been set. /// - [DataMember(Name = "subscription", Order = 7), JsonPropertyName("subscription"), JsonPropertyOrder(7), JsonInclude, YamlMember(Alias = "subscription", Order = 7)] - public virtual AsyncApiSubscriptionDefinition? Subscription { get; set; } + [Description("An object used to configure the subscription to messages consumed using the target operation. Required if Message has not been set.")] + [DataMember(Order = 7, Name = "subscription"), JsonPropertyOrder(7), JsonPropertyName("subscription")] + public AsyncApiSubscriptionDefinition? Subscription { get; init; } /// /// Gets/sets the authentication policy, if any, to use when calling the AsyncAPI operation /// - [DataMember(Name = "authentication", Order = 8), JsonPropertyName("authentication"), JsonPropertyOrder(8), JsonInclude, YamlMember(Alias = "authentication", Order = 8)] - public virtual AuthenticationPolicyDefinition? Authentication { get; set; } + [Description("The authentication policy, if any, to use when calling the AsyncAPI operation")] + [DataMember(Order = 8, Name = "authentication"), JsonPropertyOrder(8), JsonPropertyName("authentication")] + public AuthenticationPolicyDefinition? Authentication { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Calls/GrpcCallDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Calls/GrpcCallDefinition.cs index ee32f9d..15832e5 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Calls/GrpcCallDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Calls/GrpcCallDefinition.cs @@ -16,36 +16,41 @@ namespace ServerlessWorkflow.Sdk.Models.Calls; /// /// Represents the definition of a GRPC call /// +[Description("Represents the definition of a GRPC call")] [DataContract] -public record GrpcCallDefinition +public sealed record GrpcCallDefinition : CallDefinition { /// /// Gets the proto resource that describes the GRPC service to call /// + [Description("The proto resource that describes the GRPC service to call")] [Required] - [DataMember(Name = "proto", Order = 1), JsonPropertyName("proto"), JsonPropertyOrder(1), YamlMember(Alias = "proto", Order = 1)] - public required virtual ExternalResourceDefinition Proto { get; set; } + [DataMember(Order = 1, Name = "proto"), JsonPropertyOrder(1), JsonPropertyName("proto")] + public required ExternalResourceDefinition Proto { get; init; } /// /// Gets/sets the definition of the GRPC service to call /// + [Description("The definition of the GRPC service to call")] [Required] - [DataMember(Name = "service", Order = 2), JsonPropertyName("service"), JsonPropertyOrder(2), YamlMember(Alias = "service", Order = 2)] - public required virtual GrpcServiceDefinition Service { get; set; } + [DataMember(Order = 2, Name = "service"), JsonPropertyOrder(2), JsonPropertyName("service")] + public required GrpcServiceDefinition Service { get; init; } /// /// Gets/sets the name of the GRPC service method to call /// + [Description("The name of the GRPC service method to call")] [Required, MinLength(1)] - [DataMember(Name = "method", Order = 3), JsonPropertyName("method"), JsonPropertyOrder(3), YamlMember(Alias = "method", Order = 3)] - public required virtual string Method { get; set; } + [DataMember(Order = 3, Name = "method"), JsonPropertyOrder(3), JsonPropertyName("method")] + public required string Method { get; init; } /// /// Gets/sets the method call's arguments, if any /// - [DataMember(Name = "arguments", Order = 4), JsonPropertyName("arguments"), JsonPropertyOrder(4), YamlMember(Alias = "arguments", Order = 4)] - public virtual IDictionary? Arguments { get; set; } + [Description("The method call's arguments, if any")] + [DataMember(Order = 4, Name = "arguments"), JsonPropertyOrder(4), JsonPropertyName("arguments")] + public JsonObject? Arguments { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Calls/HttpCallDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Calls/HttpCallDefinition.cs index 5fbed16..e439941 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Calls/HttpCallDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Calls/HttpCallDefinition.cs @@ -16,69 +16,56 @@ namespace ServerlessWorkflow.Sdk.Models.Calls; /// /// Represents the definition of an HTTP call /// +[Description("Represents the definition of an HTTP call")] [DataContract] -public record HttpCallDefinition +public sealed record HttpCallDefinition : CallDefinition { /// /// Gets/sets the HTTP method of the request to perform /// + [Description("The HTTP method of the request to perform")] [Required, MinLength(1)] - [DataMember(Name = "method", Order = 1), JsonPropertyName("method"), JsonPropertyOrder(1), YamlMember(Alias = "method", Order = 1)] - public required virtual string Method { get; set; } - - /// - /// Gets/sets the endpoint at which to get the defined resource - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual EndpointDefinition Endpoint - { - get => this.EndpointValue.T1Value ?? new() { Uri = this.EndpointUri }; - set => this.EndpointValue = value; - } - - /// - /// Gets/sets the endpoint at which to get the defined resource - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual Uri EndpointUri - { - get => this.EndpointValue.T1Value?.Uri ?? this.EndpointValue.T2Value!; - set => this.EndpointValue = value; - } + [DataMember(Order = 1, Name = "method"), JsonPropertyOrder(1), JsonPropertyName("method")] + public required string Method { get; init; } /// /// Gets/sets the endpoint at which to get the defined resource /// + [Description("The endpoint at which to get the defined resource")] [Required] - [DataMember(Name = "endpoint", Order = 2), JsonInclude, JsonPropertyName("endpoint"), JsonPropertyOrder(2), YamlMember(Alias = "endpoint", Order = 2)] - protected virtual OneOf EndpointValue { get; set; } = null!; + [DataMember(Order = 2, Name = "endpoint"), JsonPropertyOrder(2), JsonPropertyName("endpoint"), JsonConverter(typeof(OneOfJsonConverter))] + public required OneOf Endpoint { get; init; } /// /// Gets/sets a name/value mapping of the headers, if any, of the HTTP request to perform /// - [DataMember(Name = "headers", Order = 3), JsonPropertyName("headers"), JsonPropertyOrder(3), YamlMember(Alias = "headers", Order = 3)] - public virtual EquatableDictionary? Headers { get; set; } + [Description("A name/value mapping of the headers, if any, of the HTTP request to perform")] + [DataMember(Order = 3, Name = "headers"), JsonPropertyOrder(3), JsonPropertyName("headers")] + public EquatableDictionary? Headers { get; init; } /// /// Gets/sets the body, if any, of the HTTP request to perform /// - [DataMember(Name = "body", Order = 4), JsonPropertyName("body"), JsonPropertyOrder(4), YamlMember(Alias = "body", Order = 4)] - public virtual object? Body { get; set; } + [Description("The body, if any, of the HTTP request to perform")] + [DataMember(Order = 4, Name = "body"), JsonPropertyOrder(4), JsonPropertyName("body")] + public JsonNode? Body { get; init; } /// /// Gets/sets the http call output format. Defaults to . /// - [DataMember(Name = "output", Order = 5), JsonPropertyName("output"), JsonPropertyOrder(5), YamlMember(Alias = "output", Order = 5)] - public virtual string? Output { get; set; } + [Description("The http call output format. Defaults to HttpOutputFormat.Content.")] + [DataMember(Order = 5, Name = "output"), JsonPropertyOrder(5), JsonPropertyName("output")] + public string? Output { get; init; } /// /// Gets/sets a boolean indicating whether redirection status codes (300–399) should be treated as errors. /// If set to 'false', runtimes must raise an error for response status codes outside the 200–299 range. /// If set to 'true', they must raise an error for status codes outside the 200–399 range. /// - [DataMember(Name = "redirect", Order = 6), JsonPropertyName("redirect"), JsonPropertyOrder(6), YamlMember(Alias = "redirect", Order = 6)] - public virtual bool Redirect { get; set; } + [Description("A boolean indicating whether redirection status codes (300–399) should be treated as errors. If set to 'false', runtimes must raise an error for response status codes outside the 200–299 range. If set to 'true', they must raise an error for status codes outside the 200–399 range.")] + [DataMember(Order = 6, Name = "redirect"), JsonPropertyOrder(6), JsonPropertyName("redirect")] + public bool Redirect { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Calls/OpenApiCallDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Calls/OpenApiCallDefinition.cs index c9699dd..12c6fd7 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Calls/OpenApiCallDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Calls/OpenApiCallDefinition.cs @@ -16,49 +16,56 @@ namespace ServerlessWorkflow.Sdk.Models.Calls; /// /// Represents the definition of an OpenAPI call /// +[Description("Represents the definition of an OpenAPI call")] [DataContract] -public record OpenApiCallDefinition +public sealed record OpenApiCallDefinition : CallDefinition { /// /// Gets/sets the document that defines the OpenAPI operation to call /// + [Description("The document that defines the OpenAPI operation to call")] [Required] - [DataMember(Name = "document", Order = 1), JsonPropertyName("document"), JsonPropertyOrder(1), YamlMember(Alias = "document", Order = 1)] - public required virtual ExternalResourceDefinition Document { get; set; } + [DataMember(Order = 1, Name = "document"), JsonPropertyOrder(1), JsonPropertyName("document")] + public required ExternalResourceDefinition Document { get; set; } /// /// Gets/sets the id of the OpenAPI operation to call /// + [Description("The id of the OpenAPI operation to call")] [Required] - [DataMember(Name = "operationId", Order = 2), JsonPropertyName("operationId"), JsonPropertyOrder(2), JsonInclude, YamlMember(Alias = "operationId", Order = 2)] - public required virtual string OperationId { get; set; } + [DataMember(Order = 2, Name = "operationId"), JsonPropertyOrder(2), JsonPropertyName("operationId")] + public required string OperationId { get; set; } /// /// Gets/sets a name/value mapping of the parameters of the OpenAPI operation to call /// - [DataMember(Name = "parameters", Order = 3), JsonPropertyName("parameters"), JsonPropertyOrder(3), YamlMember(Alias = "parameters", Order = 3)] - public virtual EquatableDictionary? Parameters { get; set; } + [Description("A name/value mapping of the parameters of the OpenAPI operation to call")] + [DataMember(Order = 3, Name = "parameters"), JsonPropertyOrder(3), JsonPropertyName("parameters")] + public JsonObject? Parameters { get; set; } /// /// Gets/sets the authentication policy, if any, to use when calling the OpenAPI operation /// - [DataMember(Name = "authentication", Order = 4), JsonPropertyName("authentication"), JsonPropertyOrder(4), YamlMember(Alias = "authentication", Order = 4)] - public virtual AuthenticationPolicyDefinition? Authentication { get; set; } + [Description("The authentication policy, if any, to use when calling the OpenAPI operation")] + [DataMember(Order = 4, Name = "authentication"), JsonPropertyOrder(4), JsonPropertyName("authentication")] + public AuthenticationPolicyDefinition? Authentication { get; set; } /// /// Gets/sets the http output format. Defaults to . /// - [DataMember(Name = "output", Order = 6), JsonPropertyName("output"), JsonPropertyOrder(6), YamlMember(Alias = "output", Order = 6)] - public virtual string? Output { get; set; } + [Description("The http output format. Defaults to HttpOutputFormat.Content.")] + [DataMember(Order = 5, Name = "output"), JsonPropertyOrder(5), JsonPropertyName("output")] + public string? Output { get; set; } /// /// Gets/sets a boolean indicating whether redirection status codes (300–399) should be treated as errors. /// If set to 'false', runtimes must raise an error for response status codes outside the 200–299 range. /// If set to 'true', they must raise an error for status codes outside the 200–399 range. /// - [DataMember(Name = "redirect", Order = 7), JsonPropertyName("redirect"), JsonPropertyOrder(7), YamlMember(Alias = "redirect", Order = 7)] - public virtual bool Redirect { get; set; } + [Description("A boolean indicating whether redirection status codes (300–399) should be treated as errors. If set to 'false', runtimes must raise an error for response status codes outside the 200–299 range. If set to 'true', they must raise an error for status codes outside the 200–399 range.")] + [DataMember(Order = 6, Name = "redirect"), JsonPropertyOrder(6), JsonPropertyName("redirect")] + public bool Redirect { get; set; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs index b9d22b5..9496136 100644 --- a/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/CatalogDefinition.cs @@ -16,8 +16,9 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a workflow component catalog /// +[Description("Represents the definition of a workflow component catalog.")] [DataContract] -public record CatalogDefinition +public sealed record CatalogDefinition { /// @@ -28,28 +29,9 @@ public record CatalogDefinition /// /// 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 - /// + [Description("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!; + [DataMember(Order = 1, Name = "endpoint"), JsonPropertyOrder(1), JsonPropertyName("endpoint"), JsonConverter(typeof(OneOfJsonConverter))] + public required OneOf Endpoint { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/ComponentDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ComponentDefinition.cs index b90f33d..2dcbb43 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ComponentDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ComponentDefinition.cs @@ -14,12 +14,13 @@ namespace ServerlessWorkflow.Sdk.Models; /// -/// Represents the base class for all ServerlessWorkflow workflow components +/// Represents the base class for all ServerlessWorkflow workflow components. /// [DataContract] +[Description("Represents the base class for all ServerlessWorkflow workflow components.")] public abstract record ComponentDefinition { -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs b/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs index a170be0..2212714 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ComponentDefinitionCollection.cs @@ -16,56 +16,65 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents a collection of workflow components /// +[Description("Represents a collection of workflow components.")] [DataContract] -public record ComponentDefinitionCollection +public sealed record ComponentDefinitionCollection { /// /// Gets/sets a name/value mapping of the workflow's reusable authentication policies /// - [DataMember(Name = "authentications", Order = 1), JsonPropertyName("authentications"), JsonPropertyOrder(1), YamlMember(Alias = "authentications", Order = 1)] - public virtual EquatableDictionary? Authentications { get; set; } + [Description("A name/value mapping of the workflow's reusable authentication policies.")] + [DataMember(Order = 1, Name = "authentications"), JsonPropertyOrder(1), JsonPropertyName("authentications")] + public EquatableDictionary? Authentications { get; init; } /// /// 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; } + [Description("A name/value mapping of the catalogs, if any, from which to import reusable components used within the workflow.")] + [DataMember(Order = 2, Name = "catalogs"), JsonPropertyOrder(2), JsonPropertyName("catalogs")] + public EquatableDictionary? Catalogs { get; init; } /// /// Gets/sets a name/value mapping of the workflow's errors, if any /// - [DataMember(Name = "errors", Order = 3), JsonPropertyName("errors"), JsonPropertyOrder(3), YamlMember(Alias = "errors", Order = 3)] - public virtual EquatableDictionary? Errors { get; set; } + [Description("A name/value mapping of the workflow's errors, if any.")] + [DataMember(Order = 3, Name = "errors"), JsonPropertyOrder(3), JsonPropertyName("errors")] + public EquatableDictionary? Errors { get; init; } /// /// Gets/sets a name/value mapping of the workflow's extensions, if any /// - [DataMember(Name = "extensions", Order = 4), JsonPropertyName("extensions"), JsonPropertyOrder(4), YamlMember(Alias = "extensions", Order = 4)] - public virtual EquatableDictionary? Extensions { get; set; } + [Description("A name/value mapping of the workflow's extensions, if any.")] + [DataMember(Order = 4, Name = "extensions"), JsonPropertyOrder(4), JsonPropertyName("extensions")] + public EquatableDictionary? Extensions { get; init; } /// /// Gets/sets a name/value mapping of the workflow's reusable functions /// - [DataMember(Name = "functions", Order = 5), JsonPropertyName("functions"), JsonPropertyOrder(5), YamlMember(Alias = "functions", Order = 5)] - public virtual EquatableDictionary? Functions { get; set; } + [Description("A name/value mapping of the workflow's reusable functions.")] + [DataMember(Order = 5, Name = "functions"), JsonPropertyOrder(5), JsonPropertyName("functions")] + public EquatableDictionary? Functions { get; init; } /// /// Gets/sets a name/value mapping of the workflow's reusable retry policies /// - [DataMember(Name = "retries", Order = 6), JsonPropertyName("retries"), JsonPropertyOrder(6), YamlMember(Alias = "retries", Order = 6)] - public virtual EquatableDictionary? Retries { get; set; } + [Description("A name/value mapping of the workflow's reusable retry policies.")] + [DataMember(Order = 6, Name = "retries"), JsonPropertyOrder(6), JsonPropertyName("retries")] + public EquatableDictionary? Retries { get; init; } /// /// Gets/sets a list containing the workflow's secrets /// - [DataMember(Name = "secrets", Order = 7), JsonPropertyName("secrets"), JsonPropertyOrder(7), YamlMember(Alias = "secrets", Order = 7)] - public virtual EquatableList? Secrets { get; set; } + [Description("A list containing the workflow's secrets.")] + [DataMember(Order = 7, Name = "secrets"), JsonPropertyOrder(7), JsonPropertyName("secrets")] + public EquatableList? Secrets { get; init; } /// /// Gets/sets a name/value mapping of the workflow's reusable timeouts /// - [DataMember(Name = "timeouts", Order = 7), JsonPropertyName("timeouts"), JsonPropertyOrder(8), YamlMember(Alias = "timeouts", Order = 8)] - public virtual EquatableDictionary? Timeouts { get; set; } + [Description("A name/value mapping of the workflow's reusable timeouts.")] + [DataMember(Order = 8, Name = "timeouts"), JsonPropertyOrder(8), JsonPropertyName("timeouts")] + public EquatableDictionary? Timeouts { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/ConstantBackoffDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ConstantBackoffDefinition.cs index 799accb..1020320 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ConstantBackoffDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ConstantBackoffDefinition.cs @@ -16,8 +16,9 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a constant backoff /// +[Description("Represents the definition of a constant backoff")] [DataContract] -public record ConstantBackoffDefinition +public sealed record ConstantBackoffDefinition : BackoffDefinition { diff --git a/src/ServerlessWorkflow.Sdk/Models/Processes/ContainerLifetimeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ContainerLifetimeDefinition.cs similarity index 62% rename from src/ServerlessWorkflow.Sdk/Models/Processes/ContainerLifetimeDefinition.cs rename to src/ServerlessWorkflow.Sdk/Models/ContainerLifetimeDefinition.cs index e399891..d64ffd6 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Processes/ContainerLifetimeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ContainerLifetimeDefinition.cs @@ -11,13 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Models.Processes; +namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to configure the lifetime of a container /// +[Description("Represents an object used to configure the lifetime of a container")] [DataContract] -public record ContainerLifetimeDefinition +public sealed record ContainerLifetimeDefinition { /// @@ -25,14 +26,16 @@ public record ContainerLifetimeDefinition /// See /// Defaults to /// + [Description("The cleanup policy to use. See ContainerCleanupPolicy. Defaults to ContainerCleanupPolicy.Never")] [Required, MinLength(1)] - [DataMember(Name = "cleanup", Order = 1), JsonPropertyName("cleanup"), JsonPropertyOrder(1), YamlMember(Alias = "cleanup", Order = 1)] - public required virtual string Cleanup { get; set; } + [DataMember(Order = 1, Name = "cleanup"), JsonPropertyOrder(1), JsonPropertyName("cleanup")] + public required string Cleanup { get; init; } /// /// Gets/sets the duration, if any, after which to delete the container once executed. /// Required if has been set to , otherwise ignored. /// - [DataMember(Name = "duration", Order = 2), JsonPropertyName("duration"), JsonPropertyOrder(2), YamlMember(Alias = "duration", Order = 2)] - public virtual Duration? Duration { get; set; } + [Description("The duration, if any, after which to delete the container once executed. Required if Cleanup has been set to ContainerCleanupPolicy.Eventually, otherwise ignored.")] + [DataMember(Order = 2, Name = "duration"), JsonPropertyOrder(2), JsonPropertyName("duration")] + public Duration? Duration { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/CorrelationKeyDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/CorrelationKeyDefinition.cs index 4254b51..302b8e3 100644 --- a/src/ServerlessWorkflow.Sdk/Models/CorrelationKeyDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/CorrelationKeyDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an event correlation key /// +[Description("Represents the definition of an event correlation key")] [DataContract] -public record CorrelationKeyDefinition +public sealed record CorrelationKeyDefinition { /// /// Gets/sets a runtime expression used to extract the correlation key value from events. /// - [DataMember(Name = "from", Order = 1), JsonPropertyName("from"), JsonPropertyOrder(1), YamlMember(Alias = "from", Order = 1)] - public required virtual string From { get; set; } + [Description("A runtime expression used to extract the correlation key value from events.")] + [DataMember(Order = 1, Name = "from"), JsonPropertyOrder(1), JsonPropertyName("from")] + public required string From { get; init; } /// /// Gets/sets a constant or a runtime expression, if any, used to determine whether or not the extracted correlation key value matches expectations and should be correlated. If not set, the first extracted value will be used as the correlation key's expectation. /// - [DataMember(Name = "expect", Order = 2), JsonPropertyName("expect"), JsonPropertyOrder(2), YamlMember(Alias = "expect", Order = 2)] - public virtual string? Expect { get; set; } + [Description("A constant or a runtime expression, if any, used to determine whether or not the extracted correlation key value matches expectations and should be correlated. If not set, the first extracted value will be used as the correlation key's expectation.")] + [DataMember(Order = 2, Name = "expect"), JsonPropertyOrder(2), JsonPropertyName("expect")] + public string? Expect { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/DateTimeDescriptor.cs b/src/ServerlessWorkflow.Sdk/Models/DateTimeDescriptor.cs new file mode 100644 index 0000000..aac6ab5 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/DateTimeDescriptor.cs @@ -0,0 +1,40 @@ +// 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 an object used to describe a datetime +/// +[Description("Represents an object used to describe a datetime.")] +[DataContract] +public sealed record DateTimeDescriptor +{ + + /// + /// Gets/sets the ISO 8601 representation of the described datetime + /// + [Description("The ISO 8601 representation of the described datetime.")] + [Required, MinLength(1)] + [DataMember(Order = 1, Name = "iso8601"), JsonPropertyOrder(1), JsonPropertyName("iso8601")] + public required string Iso8601 { get; init; } + + /// + /// Gets/sets the duration elapsed between the described datetime and midnight of 1970-01-01 UTC + /// + [Description("The duration elapsed between the described datetime and midnight of 1970-01-01 UTC.")] + [Required] + [DataMember(Order = 2, Name = "epoch"), JsonPropertyOrder(2), JsonPropertyName("epoch")] + public required Epoch Epoch { get; init; } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Duration.cs b/src/ServerlessWorkflow.Sdk/Models/Duration.cs index ca97f79..515573a 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Duration.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Duration.cs @@ -16,77 +16,83 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents a duration /// +[Description("Represents a duration")] [DataContract] -public record Duration +public sealed record Duration { /// - /// Gets/sets the numbers of days, if any + /// Gets/sets the number of days, if any /// - [DataMember(Name = "days", Order = 1), JsonPropertyName("days"), JsonPropertyOrder(1), YamlMember(Alias = "days", Order = 1)] - public virtual uint? Days { get; set; } + [Description("The number of days, if any")] + [DataMember(Order = 1, Name = "days"), JsonPropertyOrder(1), JsonPropertyName("days")] + public uint? Days { get; init; } /// - /// Gets/sets the numbers of hours, if any + /// Gets/sets the number of hours, if any /// - [DataMember(Name = "hours", Order = 2), JsonPropertyName("hours"), JsonPropertyOrder(2), YamlMember(Alias = "hours", Order = 2)] - public virtual uint? Hours { get; set; } + [Description("The number of hours, if any")] + [DataMember(Order = 2, Name = "hours"), JsonPropertyOrder(2), JsonPropertyName("hours")] + public uint? Hours { get; init; } /// - /// Gets/sets the numbers of minutes, if any + /// Gets/sets the number of minutes, if any /// - [DataMember(Name = "minutes", Order = 3), JsonPropertyName("minutes"), JsonPropertyOrder(3), YamlMember(Alias = "minutes", Order = 3)] - public virtual uint? Minutes { get; set; } + [Description("The number of minutes, if any")] + [DataMember(Order = 3, Name = "minutes"), JsonPropertyOrder(3), JsonPropertyName("minutes")] + public uint? Minutes { get; init; } /// - /// Gets/sets the numbers of seconds, if any + /// Gets/sets the number of seconds, if any /// - [DataMember(Name = "seconds", Order = 4), JsonPropertyName("seconds"), JsonPropertyOrder(4), YamlMember(Alias = "seconds", Order = 4)] - public virtual uint? Seconds { get; set; } + [Description("The number of seconds, if any")] + [DataMember(Order = 4, Name = "seconds"), JsonPropertyOrder(4), JsonPropertyName("seconds")] + public uint? Seconds { get; init; } /// - /// Gets/sets the numbers of milliseconds, if any + /// Gets/sets the number of milliseconds, if any /// - [DataMember(Name = "milliseconds", Order = 5), JsonPropertyName("milliseconds"), JsonPropertyOrder(5), YamlMember(Alias = "milliseconds", Order = 5)] - public virtual uint? Milliseconds { get; set; } + [Description("The number of milliseconds, if any")] + [DataMember(Order = 5, Name = "milliseconds"), JsonPropertyOrder(5), JsonPropertyName("milliseconds")] + public uint? Milliseconds { get; init; } /// /// Gets the the duration's total amount of days /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual double TotalDays => this.TotalHours / 24; + [IgnoreDataMember, JsonIgnore] + public double TotalDays => TotalHours / 24; /// /// Gets the the duration's total amount of hours /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual double TotalHours => this.TotalMinutes / 60; + [IgnoreDataMember, JsonIgnore] + public double TotalHours => TotalMinutes / 60; /// /// Gets the the duration's total amount of minutes /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual double TotalMinutes => this.TotalSeconds / 60; + [IgnoreDataMember, JsonIgnore] + public double TotalMinutes => TotalSeconds / 60; /// /// Gets the the duration's total amount of seconds /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual double TotalSeconds => this.TotalMilliseconds / 1000; + [IgnoreDataMember, JsonIgnore] + public double TotalSeconds => TotalMilliseconds / 1000; /// /// Gets the the duration's total amount of milliseconds /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual uint TotalMilliseconds + [IgnoreDataMember, JsonIgnore] + public uint TotalMilliseconds { get { - var milliseconds = this.Days.HasValue ? this.Days * 24 * 60 * 60 * 1000 : 0; - milliseconds += this.Hours.HasValue ? this.Hours * 60 * 60 * 1000 : 0; - milliseconds += this.Minutes.HasValue ? this.Minutes * 60 * 1000 : 0; - milliseconds += this.Seconds.HasValue ? this.Seconds * 1000 : 0; - milliseconds += this.Milliseconds.HasValue ? this.Milliseconds : 0; + var milliseconds = Days.HasValue ? Days * 24 * 60 * 60 * 1000 : 0; + milliseconds += Hours.HasValue ? Hours * 60 * 60 * 1000 : 0; + milliseconds += Minutes.HasValue ? Minutes * 60 * 1000 : 0; + milliseconds += Seconds.HasValue ? Seconds * 1000 : 0; + milliseconds += Milliseconds.HasValue ? Milliseconds : 0; return milliseconds ?? 0; } } @@ -95,7 +101,7 @@ public virtual uint TotalMilliseconds /// Converts the to a new /// /// A new - public virtual TimeSpan ToTimeSpan() => new((int)(this.Days ?? 0), (int)(this.Hours ?? 0), (int)(this.Minutes ?? 0), (int)(this.Seconds ?? 0), (int)(this.Milliseconds ?? 0)); + public TimeSpan ToTimeSpan() => new((int)(Days ?? 0), (int)(Hours ?? 0), (int)(Minutes ?? 0), (int)(Seconds ?? 0), (int)(Milliseconds ?? 0)); /// /// Gets a zero value @@ -163,4 +169,4 @@ public virtual uint TotalMilliseconds /// The to convert public static implicit operator Duration?(TimeSpan? timeSpan) => timeSpan == null ? null : FromTimeSpan(timeSpan.Value); -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Models/EndpointDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/EndpointDefinition.cs index b6afcf8..4c8a612 100644 --- a/src/ServerlessWorkflow.Sdk/Models/EndpointDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/EndpointDefinition.cs @@ -16,21 +16,24 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an endpoint /// +[Description("Represents the definition of an endpoint")] [DataContract] -public record EndpointDefinition +public sealed record EndpointDefinition { /// /// Gets/sets the endpoint's uri /// + [Description("The endpoint's uri")] [Required] - [DataMember(Name = "uri", Order = 1), JsonPropertyName("uri"), JsonPropertyOrder(1), YamlMember(Alias = "uri", Order = 1)] - public required virtual Uri Uri { get; set; } + [DataMember(Order = 1, Name = "uri"), JsonPropertyOrder(1), JsonPropertyName("uri")] + public required Uri Uri { get; init; } /// /// Gets/sets the endpoint's authentication policy, if any /// - [DataMember(Name = "authentication", Order = 2), JsonPropertyName("authentication"), JsonPropertyOrder(2), YamlMember(Alias = "authentication", Order = 2)] - public virtual AuthenticationPolicyDefinition? Authentication { get; set; } + [Description("The endpoint's authentication policy, if any")] + [DataMember(Order = 2, Name = "authentication"), JsonPropertyOrder(2), JsonPropertyName("authentication")] + public AuthenticationPolicyDefinition? Authentication { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Epoch.cs b/src/ServerlessWorkflow.Sdk/Models/Epoch.cs new file mode 100644 index 0000000..9c19c78 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/Epoch.cs @@ -0,0 +1,31 @@ +// 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 an epoch, which is the duration elapsed between a datetime and midnight of 1970-01-01 UTC +/// +[Description("Represents an epoch, which is the duration elapsed between a datetime and midnight of 1970-01-01 UTC.")] +[DataContract] +public sealed record Epoch +{ + + /// + /// Gets/sets the epoch's total milliseconds + /// + [Description("The epoch's total milliseconds.")] + [DataMember(Order = 1, Name = "ms"), JsonPropertyOrder(1), JsonPropertyName("ms")] + public required ulong Milliseconds { get; init; } + +} diff --git a/src/ServerlessWorkflow.Sdk/Models/Error.cs b/src/ServerlessWorkflow.Sdk/Models/Error.cs new file mode 100644 index 0000000..5c0efe8 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/Error.cs @@ -0,0 +1,127 @@ +// 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 an object used to describe an error or problem, as defined by RFC 7807 +/// +[Description("Represents an object used to describe an error or problem, as defined by RFC 7807")] +[DataContract] +public sealed record Error +{ + + /// + /// Gets/sets an uri that reference the type of the described problem. + /// + [Description("An uri that reference the type of the described problem.")] + [DataMember(Order = 1, Name = "type"), JsonPropertyOrder(1), JsonPropertyName("type")] + public required Uri Type { get; init; } + + /// + /// Gets/sets a short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. + /// + [Description("A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.")] + [DataMember(Order = 2, Name = "title"), JsonPropertyOrder(2), JsonPropertyName("title")] + public required string Title { get; init; } + + /// + /// Gets/sets the status code produced by the described problem + /// + [Description("The status code produced by the described problem")] + [DataMember(Order = 3, Name = "status"), JsonPropertyOrder(3), JsonPropertyName("status")] + public required ushort Status { get; init; } + + /// + /// Gets/sets a human-readable explanation specific to this occurrence of the problem. + /// + [Description("A human-readable explanation specific to this occurrence of the problem.")] + [DataMember(Order = 4, Name = "detail"), JsonPropertyOrder(4), JsonPropertyName("detail")] + public string? Detail { get; init; } + + /// + /// Gets/sets a reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced. + /// + [Description("A Uri reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.")] + [DataMember(Order = 5, Name = "instance"), JsonPropertyOrder(5), JsonPropertyName("instance")] + public Uri? Instance { get; init; } + + /// + /// Gets/sets a mapping containing problem details extension data, if any + /// + [Description("A mapping containing problem details extension data, if any")] + [DataMember(Name = "extensionData", Order = 6), JsonExtensionData] + public IDictionary? ExtensionData { get; set; } + + /// + /// Creates a new communication + /// + /// The source + /// The 's status + /// The detail, if any + /// A new communication + public static Error Communication(Uri instance, ushort status = ErrorStatus.Communication, string? detail = null) => new() + { + Status = status, + Type = ErrorType.Communication, + Title = ErrorTitle.Communication, + Detail = detail, + Instance = instance + }; + + /// + /// Creates a new communication + /// + /// The source + /// The detail, if any + /// A new communication + public static Error Configuration(Uri instance, string? detail = null) => new() + { + Status = ErrorStatus.Configuration, + Type = ErrorType.Configuration, + Title = ErrorTitle.Configuration, + Detail = detail, + Instance = instance + }; + + /// + /// Creates a new runtime + /// + /// The source + /// The detail, if any + /// A new communication + public static Error Runtime(Uri instance, string? detail = null) => new() + { + Status = ErrorStatus.Runtime, + Type = ErrorType.Runtime, + Title = ErrorTitle.Runtime, + Detail = detail, + Instance = instance + }; + + /// + /// Creates a new validation + /// + /// The source + /// The detail, if any + /// A new communication + public static Error Validation(Uri instance, string? detail = null) => new() + { + Status = ErrorStatus.Validation, + Type = ErrorType.Validation, + Title = ErrorTitle.Validation, + Detail = detail, + Instance = instance + }; + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ErrorCatcherDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ErrorCatcherDefinition.cs index 91c253a..ef530d5 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ErrorCatcherDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ErrorCatcherDefinition.cs @@ -16,65 +16,52 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of a concept used to catch errors /// +[Description("Represents the configuration of a concept used to catch errors")] [DataContract] -public record ErrorCatcherDefinition +public sealed record ErrorCatcherDefinition { /// /// Gets/sets the definition of the errors to catch /// - [DataMember(Name = "errors", Order = 1), JsonPropertyName("errors"), JsonPropertyOrder(1), YamlMember(Alias = "errors", Order = 1)] - public virtual ErrorFilterDefinition? Errors { get; set; } + [Description("The definition of the errors to catch")] + [DataMember(Order = 1, Name = "errors"), JsonPropertyOrder(1), JsonPropertyName("errors")] + public ErrorFilterDefinition? Errors { get; init; } /// /// Gets/sets the name of the runtime expression variable to save the error as. Defaults to 'error'. /// - [DataMember(Name = "as", Order = 2), JsonPropertyName("as"), JsonPropertyOrder(2), YamlMember(Alias = "as", Order = 2)] - public virtual string? As { get; set; } + [Description("The name of the runtime expression variable to save the error as. Defaults to 'error'.")] + [DataMember(Order = 2, Name = "as"), JsonPropertyOrder(2), JsonPropertyName("as")] + public string? As { get; init; } /// /// Gets/sets a runtime expression used to determine whether or not to catch the filtered error /// - [DataMember(Name = "when", Order = 3), JsonPropertyName("when"), JsonPropertyOrder(3), YamlMember(Alias = "when", Order = 3)] - public virtual string? When { get; set; } + [Description("A runtime expression used to determine whether or not to catch the filtered error")] + [DataMember(Order = 3, Name = "when"), JsonPropertyOrder(3), JsonPropertyName("when")] + public string? When { get; init; } /// /// Gets/sets a runtime expression used to determine whether or not to catch the filtered error /// - [DataMember(Name = "exceptWhen", Order = 4), JsonPropertyName("exceptWhen"), JsonPropertyOrder(4), YamlMember(Alias = "exceptWhen", Order = 4)] - public virtual string? ExceptWhen { get; set; } - - /// - /// Gets/sets the retry policy to use, if any - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual RetryPolicyDefinition? Retry - { - get => this.RetryValue?.T1Value; - set => this.RetryValue = value!; - } - - /// - /// Gets/sets the reference of the retry policy to use, if any - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string? RetryReference - { - get => this.RetryValue?.T2Value; - set => this.RetryValue = value!; - } + [Description("A runtime expression used to determine whether or not to catch the filtered error")] + [DataMember(Order = 4, Name = "exceptWhen"), JsonPropertyOrder(4), JsonPropertyName("exceptWhen")] + public string? ExceptWhen { get; init; } /// /// Gets/sets the retry policy to use, if any /// + [Description("The retry policy to use, if any")] [Required] - [DataMember(Name = "retry", Order = 5), JsonInclude, JsonPropertyName("retry"), JsonPropertyOrder(5), YamlMember(Alias = "retry", Order = 5)] - protected virtual OneOf? RetryValue { get; set; } = null!; + [DataMember(Order = 5, Name = "retry"), JsonPropertyOrder(5), JsonPropertyName("retry"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? Retry { get; init; } = null!; /// /// Gets/sets a name/definition map of the tasks to run when catching an error /// - [DataMember(Name = "do", Order = 6), JsonPropertyName("do"), JsonPropertyOrder(6), YamlMember(Alias = "do", Order = 6)] - public virtual Map? Do { get; set; } + [Description("A name/definition map of the tasks to run when catching an error")] + [DataMember(Order = 6, Name = "do"), JsonPropertyOrder(6), JsonPropertyName("do")] + public Map? Do { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/ErrorDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ErrorDefinition.cs index 0d09973..7a51587 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ErrorDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ErrorDefinition.cs @@ -16,45 +16,52 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition an error to raise /// +[Description("Represents the definition an error to raise")] [DataContract] -public record ErrorDefinition +public sealed record ErrorDefinition : ReferenceableComponentDefinition { /// /// Gets/sets an uri that reference the type of the described error. /// - [DataMember(Order = 1, Name = "type"), JsonPropertyName("type"), JsonPropertyOrder(1), YamlMember(Alias = "type", Order = 1)] - public required virtual string Type { get; set; } + [Description("An uri that reference the type of the described error.")] + [DataMember(Order = 1, Name = "type"), JsonPropertyOrder(1), JsonPropertyName("type")] + public required string Type { get; init; } /// /// Gets/sets a short, human-readable summary of the error type.It SHOULD NOT change from occurrence to occurrence of the error, except for purposes of localization. /// - [DataMember(Order = 2, Name = "title"), JsonPropertyName("title"), JsonPropertyOrder(2), YamlMember(Alias = "title", Order = 2)] - public required virtual string Title { get; set; } + [Description("A short, human-readable summary of the error type.It SHOULD NOT change from occurrence to occurrence of the error, except for purposes of localization.")] + [DataMember(Order = 2, Name = "title"), JsonPropertyOrder(2), JsonPropertyName("title")] + public required string Title { get; init; } /// /// Gets/sets the status code produced by the described error /// - [DataMember(Order = 3, Name = "status"), JsonPropertyName("status"), JsonPropertyOrder(3), YamlMember(Alias = "status", Order = 3)] - public required virtual object Status { get; set; } + [Description("The status code produced by the described error")] + [DataMember(Order = 3, Name = "status"), JsonPropertyOrder(3), JsonPropertyName("status")] + public required string Status { get; init; } /// /// Gets/sets a human-readable explanation specific to this occurrence of the error. /// - [DataMember(Order = 4, Name = "detail"), JsonPropertyName("detail"), JsonPropertyOrder(4), YamlMember(Alias = "detail", Order = 4)] - public virtual string? Detail { get; set; } + [Description("A human-readable explanation specific to this occurrence of the error.")] + [DataMember(Order = 4, Name = "detail"), JsonPropertyOrder(4), JsonPropertyName("detail")] + public string? Detail { get; init; } /// /// Gets/sets a reference that identifies the specific occurrence of the error.It may or may not yield further information if dereferenced. /// - [DataMember(Order = 5, Name = "instance"), JsonPropertyName("instance"), JsonPropertyOrder(5), YamlMember(Alias = "instance", Order = 5)] - public virtual string? Instance { get; set; } + [Description("A reference that identifies the specific occurrence of the error.It may or may not yield further information if dereferenced.")] + [DataMember(Order = 5, Name = "instance"), JsonPropertyOrder(5), JsonPropertyName("instance")] + public string? Instance { get; init; } /// /// Gets/sets a mapping containing error details extension data, if any /// + [Description("A mapping containing error details extension data, if any")] [DataMember(Order = 6, Name = "extensionData"), JsonExtensionData] - public virtual IDictionary? ExtensionData { get; set; } + public IDictionary? ExtensionData { get; set; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ErrorFilterDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ErrorFilterDefinition.cs index e6bd81a..e4b619b 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ErrorFilterDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ErrorFilterDefinition.cs @@ -16,14 +16,16 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition an an error filter /// +[Description("Represents the definition an an error filter")] [DataContract] -public record ErrorFilterDefinition +public sealed record ErrorFilterDefinition { /// /// Gets/sets a key/value mapping of the properties errors to filter must define /// - [DataMember(Name = "with", Order = 1), JsonPropertyName("with"), JsonPropertyOrder(1), YamlMember(Alias = "with", Order = 1)] - public virtual EquatableDictionary? With { get; set; } + [Description("A key/value mapping of the properties errors to filter must define")] + [DataMember(Order = 1, Name = "with"), JsonPropertyOrder(1), JsonPropertyName("with")] + public JsonObject? With { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/EventConsumptionStrategyDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/EventConsumptionStrategyDefinition.cs index 5815e91..502011e 100644 --- a/src/ServerlessWorkflow.Sdk/Models/EventConsumptionStrategyDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/EventConsumptionStrategyDefinition.cs @@ -16,53 +16,38 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of an event consumption strategy /// +[Description("Represents the configuration of an event consumption strategy")] [DataContract] -public record EventConsumptionStrategyDefinition +public sealed record EventConsumptionStrategyDefinition { /// /// Gets/sets a list containing all the events that must be consumed, if any /// - [DataMember(Name = "all", Order = 1), JsonPropertyName("all"), JsonPropertyOrder(1), YamlMember(Alias = "all", Order = 1)] - public virtual EquatableList? All { get; set; } + [Description("A list containing all the events that must be consumed, if any")] + [DataMember(Order = 1, Name = "all"), JsonPropertyOrder(1), JsonPropertyName("all")] + public EquatableList? All { get; init; } /// /// Gets/sets a list containing any of the events to consume, if any. /// If empty, listens to all incoming events, and requires to be set. /// - [DataMember(Name = "any", Order = 2), JsonPropertyName("any"), JsonPropertyOrder(2), YamlMember(Alias = "any", Order = 2)] - public virtual EquatableList? Any { get; set; } + [Description("A list containing any of the events to consume, if any. If empty, listens to all incoming events, and requires Until to be set.")] + [DataMember(Order = 2, Name = "any"), JsonPropertyOrder(2), JsonPropertyName("any")] + public EquatableList? Any { get; init; } /// /// Gets/sets the single event to consume /// - [DataMember(Name = "one", Order = 3), JsonPropertyName("one"), JsonPropertyOrder(3), YamlMember(Alias = "one", Order = 3)] - public virtual EventFilterDefinition? One { get; set; } + [Description("The single event to consume")] + [DataMember(Order = 3, Name = "one"), JsonPropertyOrder(3), JsonPropertyName("one")] + public EventFilterDefinition? One { get; init; } /// /// Gets/sets the condition or the consumption strategy that defines the events that must be consumed to stop listening /// - [DataMember(Name = "until", Order = 4), JsonInclude, JsonPropertyName("until"), JsonPropertyOrder(4), YamlMember(Alias = "until", Order = 4)] - protected virtual OneOf? UntilValue { get; set; } - - /// - /// Gets/sets the consumption strategy, if any, that defines the events that must be consumed to stop listening - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual EventConsumptionStrategyDefinition? Until - { - get => this.UntilValue?.T1Value; - set => this.UntilValue = value!; - } - - /// - /// Gets/sets a runtime expression, if any, that represents the condition that must be met to stop listening - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string? UntilExpression - { - get => this.UntilValue?.T2Value; - set => this.UntilValue = value!; - } + [Description("The condition or the consumption strategy that defines the events that must be consumed to stop listening")] + [DataMember(Order = 4, Name = "until"), JsonPropertyOrder(4), JsonPropertyName("until"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? Until { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/EventDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/EventDefinition.cs index 3803add..d90abbd 100644 --- a/src/ServerlessWorkflow.Sdk/Models/EventDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/EventDefinition.cs @@ -16,15 +16,17 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an event /// +[Description("Represents the definition of an event")] [DataContract] -public record EventDefinition +public sealed record EventDefinition { /// /// Gets/sets a key/value mapping of the attributes of the configured event /// + [Description("A key/value mapping of the attributes of the configured event")] [Required] - [DataMember(Name = "with", Order = 2), JsonPropertyName("with"), JsonPropertyOrder(2), YamlMember(Alias = "with", Order = 2)] - public required virtual EquatableDictionary With { get; set; } + [DataMember(Order = 1, Name = "with"), JsonPropertyOrder(1), JsonPropertyName("with")] + public required JsonObject With { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/EventEmissionDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/EventEmissionDefinition.cs index 955d17d..c4e0126 100644 --- a/src/ServerlessWorkflow.Sdk/Models/EventEmissionDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/EventEmissionDefinition.cs @@ -16,15 +16,17 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of an event's emission /// +[Description("Represents the configuration of an event's emission")] [DataContract] -public record EventEmissionDefinition +public sealed record EventEmissionDefinition { /// /// Gets/sets the definition of the event to emit /// + [Description("The definition of the event to emit")] [Required] - [DataMember(Name = "event", Order = 1), JsonPropertyName("event"), JsonPropertyOrder(1), YamlMember(Alias = "event", Order = 1)] - public required virtual EventDefinition Event { get; set; } + [DataMember(Order = 1, Name = "event"), JsonPropertyOrder(1), JsonPropertyName("event")] + public required EventDefinition Event { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/EventFilterDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/EventFilterDefinition.cs index 52a5e50..24fffa8 100644 --- a/src/ServerlessWorkflow.Sdk/Models/EventFilterDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/EventFilterDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of an event filter /// +[Description("Represents the configuration of an event filter")] [DataContract] -public record EventFilterDefinition +public sealed record EventFilterDefinition { /// /// Gets/sets a name/value mapping of the attributes filtered events must define. Supports both regular expressions and runtime expressions. /// - [DataMember(Name = "with", Order = 1), JsonPropertyName("with"), JsonPropertyOrder(1), YamlMember(Alias = "with", Order = 1)] - public virtual EquatableDictionary? With { get; set; } + [Description("A name/value mapping of the attributes filtered events must define. Supports both regular expressions and runtime expressions.")] + [DataMember(Order = 1, Name = "with"), JsonPropertyOrder(1), JsonPropertyName("with")] + public JsonObject? With { get; init; } /// /// Gets/sets a name/definition mapping of the correlation to attempt when filtering events. /// - [DataMember(Name = "correlate", Order = 2), JsonPropertyName("correlate"), JsonPropertyOrder(2), YamlMember(Alias = "correlate", Order = 2)] - public virtual EquatableDictionary? Correlate { get; set; } + [Description("A name/definition mapping of the correlation to attempt when filtering events.")] + [DataMember(Order = 2, Name = "correlate"), JsonPropertyOrder(2), JsonPropertyName("correlate")] + public EquatableDictionary? Correlate { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/ExponentialBackoffDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ExponentialBackoffDefinition.cs index 4d5e6eb..b50cd89 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ExponentialBackoffDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ExponentialBackoffDefinition.cs @@ -16,8 +16,9 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an exponential backoff /// +[Description("Represents the definition of an exponential backoff")] [DataContract] -public record ExponentialBackoffDefinition +public sealed record ExponentialBackoffDefinition : BackoffDefinition { diff --git a/src/ServerlessWorkflow.Sdk/Models/ExtensionDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ExtensionDefinition.cs index 1d0147e..560a4b2 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ExtensionDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ExtensionDefinition.cs @@ -16,34 +16,39 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a an extension /// +[Description("Represents the definition of a an extension.")] [DataContract] -public record ExtensionDefinition +public sealed record ExtensionDefinition : Extendable { /// /// Gets/sets the type of task to extend /// + [Description("The type of task to extend.")] [Required] - [DataMember(Name = "extend", Order = 1), JsonPropertyName("extend"), JsonPropertyOrder(1), YamlMember(Alias = "extend", Order = 1)] - public required virtual string Extend { get; set; } + [DataMember(Order = 1, Name = "extend"), JsonPropertyOrder(1), JsonPropertyName("extend")] + public required string Extend { get; init; } /// /// Gets/sets a runtime expression, if any, used to determine whether or not the extension should apply in the specified context /// - [DataMember(Name = "when", Order = 2), JsonPropertyName("when"), JsonPropertyOrder(2), YamlMember(Alias = "when", Order = 2)] - public virtual string? When { get; set; } + [Description("A runtime expression, if any, used to determine whether or not the extension should apply in the specified context.")] + [DataMember(Order = 2, Name = "when"), JsonPropertyOrder(2), JsonPropertyName("when")] + public string? When { get; init; } /// /// Gets/sets a name/definition map of the tasks to execute before the extended task, if any /// - [DataMember(Name = "before", Order = 3), JsonPropertyName("before"), JsonPropertyOrder(3), YamlMember(Alias = "before", Order = 3)] - public virtual Map? Before { get; set; } + [Description("A name/definition map of the tasks to execute before the extended task, if any.")] + [DataMember(Order = 3, Name = "before"), JsonPropertyOrder(3), JsonPropertyName("before")] + public Map? Before { get; init; } /// /// Gets/sets a name/definition map of the tasks to execute after the extended task, if any /// - [DataMember(Name = "after", Order = 4), JsonPropertyName("after"), JsonPropertyOrder(4), YamlMember(Alias = "after", Order = 4)] - public virtual Map? After { get; set; } + [Description("A name/definition map of the tasks to execute after the extended task, if any.")] + [DataMember(Order = 4, Name = "after"), JsonPropertyOrder(4), JsonPropertyName("after")] + public Map? After { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ExternalResourceDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ExternalResourceDefinition.cs index 2b82108..65eb5d5 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ExternalResourceDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ExternalResourceDefinition.cs @@ -16,41 +16,24 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an external resource /// +[Description("Represents the definition of an external resource")] [DataContract] -public record ExternalResourceDefinition +public sealed record ExternalResourceDefinition { /// /// Gets/sets the external resource's name, if any /// - [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1), YamlMember(Alias = "name", Order = 1)] - public virtual string? Name { get; set; } - - /// - /// Gets/sets the endpoint at which to get the defined resource - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual EndpointDefinition Endpoint - { - get => this.EndpointValue.T1Value ?? new() { Uri = this.EndpointUri }; - set => this.EndpointValue = value; - } - - /// - /// Gets/sets the endpoint at which to get the defined resource - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual Uri EndpointUri - { - get => this.EndpointValue.T1Value?.Uri ?? this.EndpointValue.T2Value!; - set => this.EndpointValue = value; - } + [Description("The external resource's name, if any")] + [DataMember(Order = 1, Name = "name"), JsonPropertyOrder(1), JsonPropertyName("name")] + public string? Name { get; init; } /// /// Gets/sets the endpoint at which to get the defined resource /// + [Description("The endpoint at which to get the defined resource")] [Required] - [DataMember(Name = "endpoint", Order = 2), JsonInclude, JsonPropertyName("endpoint"), JsonPropertyOrder(2), YamlMember(Alias = "endpoint", Order = 2)] - protected virtual OneOf EndpointValue { get; set; } = null!; + [DataMember(Order = 2, Name = "endpoint"), JsonPropertyOrder(2), JsonPropertyName("endpoint"), JsonConverter(typeof(OneOfJsonConverter))] + public required OneOf Endpoint { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ForLoopDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ForLoopDefinition.cs index f548d8b..c258799 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ForLoopDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ForLoopDefinition.cs @@ -16,33 +16,38 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a loop that iterates over a range of values /// +[Description("Represents the definition of a loop that iterates over a range of values")] [DataContract] -public record ForLoopDefinition +public sealed record ForLoopDefinition { /// /// Gets/sets the name of the variable that represents each element in the collection during iteration /// + [Description("The name of the variable that represents each element in the collection during iteration")] [Required] - [DataMember(Name = "each", Order = 1), JsonPropertyName("each"), JsonPropertyOrder(1), YamlMember(Alias = "each", Order = 1)] - public required virtual string Each { get; set; } + [DataMember(Order = 1, Name = "each"), JsonPropertyOrder(1), JsonPropertyName("each")] + public required string Each { get; init; } /// /// Gets/sets the runtime expression used to get the collection to iterate over /// - [DataMember(Name = "in", Order = 2), JsonPropertyName("in"), JsonPropertyOrder(2), YamlMember(Alias = "in", Order = 2)] - public required virtual string In { get; set; } + [Description("The runtime expression used to get the collection to iterate over")] + [DataMember(Order = 2, Name = "in"), JsonPropertyOrder(2), JsonPropertyName("in")] + public required string In { get; init; } /// /// Gets/sets the name of the variable used to hold the index of each element in the collection during iteration /// - [DataMember(Name = "at", Order = 3), JsonPropertyName("at"), JsonPropertyOrder(3), YamlMember(Alias = "at", Order = 3)] - public virtual string? At { get; set; } + [Description("The name of the variable used to hold the index of each element in the collection during iteration")] + [DataMember(Order = 3, Name = "index"), JsonPropertyOrder(3), JsonPropertyName("index")] + public string? At { get; init; } /// /// Gets/sets the definition of the data, if any, to pass to iterations to run /// - [DataMember(Name = "input", Order = 4), JsonPropertyName("input"), JsonPropertyOrder(4), YamlMember(Alias = "input", Order = 4)] - public virtual InputDataModelDefinition? Input { get; set; } + [Description("The definition of the data, if any, to pass to iterations to run")] + [DataMember(Order = 4, Name = "input"), JsonPropertyOrder(4), JsonPropertyName("input")] + public InputDataModelDefinition? Input { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/GrpcServiceDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/GrpcServiceDefinition.cs index 6d32078..781e394 100644 --- a/src/ServerlessWorkflow.Sdk/Models/GrpcServiceDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/GrpcServiceDefinition.cs @@ -16,34 +16,39 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a GRPC service /// +[Description("Represents the definition of a GRPC service")] [DataContract] -public record GrpcServiceDefinition +public sealed record GrpcServiceDefinition { /// /// Gets/sets the GRPC service name /// + [Description("The GRPC service name")] [Required, MinLength(1)] - [DataMember(Name = "name", Order = 1), JsonPropertyName("name"), JsonPropertyOrder(1), YamlMember(Alias = "name", Order = 1)] - public required virtual string Name { get; set; } + [DataMember(Order = 1, Name = "name"), JsonPropertyOrder(1), JsonPropertyName("name")] + public required string Name { get; init; } /// /// Gets/sets the hostname of the GRPC service to call /// + [Description("The hostname of the GRPC service to call")] [Required, MinLength(1)] - [DataMember(Name = "host", Order = 2), JsonPropertyName("host"), JsonPropertyOrder(2), YamlMember(Alias = "host", Order = 2)] - public required virtual string Host { get; set; } + [DataMember(Order = 2, Name = "host"), JsonPropertyOrder(2), JsonPropertyName("host")] + public required string Host { get; init; } /// /// Gets/sets the port number of the GRPC service to call /// - [DataMember(Name = "port", Order = 3), JsonPropertyName("port"), JsonPropertyOrder(3), YamlMember(Alias = "port", Order = 3)] - public virtual int? Port { get; set; } + [Description("The port number of the GRPC service to call")] + [DataMember(Order = 3, Name = "port"), JsonPropertyOrder(3), JsonPropertyName("port")] + public int? Port { get; init; } /// /// Gets/sets the endpoint's authentication policy, if any /// - [DataMember(Name = "authentication", Order = 4), JsonPropertyName("authentication"), JsonPropertyOrder(4), JsonInclude, YamlMember(Alias = "authentication", Order = 4)] - public virtual AuthenticationPolicyDefinition? Authentication { get; set; } + [Description("The endpoint's authentication policy, if any")] + [DataMember(Order = 4, Name = "authentication"), JsonPropertyOrder(4), JsonPropertyName("authentication")] + public AuthenticationPolicyDefinition? Authentication { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/HttpRequest.cs b/src/ServerlessWorkflow.Sdk/Models/HttpRequest.cs index 2b4224b..4844bf8 100644 --- a/src/ServerlessWorkflow.Sdk/Models/HttpRequest.cs +++ b/src/ServerlessWorkflow.Sdk/Models/HttpRequest.cs @@ -16,34 +16,39 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to describe an HTTP request /// +[Description("Represents an object used to describe an HTTP request.")] [DataContract] -public record HttpRequest +public sealed record HttpRequest { /// /// Gets/sets the HTTP method of the described request /// + [Description("The HTTP method of the described request.")] [Required, MinLength(1)] - [DataMember(Name = "method", Order = 1), JsonPropertyName("method"), JsonPropertyOrder(1), YamlMember(Alias = "method", Order = 1)] - public required virtual string Method { get; set; } + [DataMember(Order = 1, Name = "method"), JsonPropertyOrder(1), JsonPropertyName("method")] + public required string Method { get; init; } /// /// Gets/sets the request URI /// + [Description("The request URI.")] [Required] - [DataMember(Name = "uri", Order = 2), JsonPropertyName("uri"), JsonPropertyOrder(2), YamlMember(Alias = "uri", Order = 2)] - public required virtual Uri Uri { get; set; } + [DataMember(Order = 2, Name = "uri"), JsonPropertyOrder(2), JsonPropertyName("uri")] + public required Uri Uri { get; init; } /// /// Gets/sets the request headers, if any /// - [DataMember(Name = "headers", Order = 3), JsonPropertyName("headers"), JsonPropertyOrder(3), YamlMember(Alias = "headers", Order = 3)] - public virtual EquatableDictionary? Headers { get; set; } + [Description("The request headers, if any.")] + [DataMember(Order = 3, Name = "headers"), JsonPropertyOrder(3), JsonPropertyName("headers")] + public EquatableDictionary? Headers { get; init; } /// /// Gets/sets the request body, if any /// - [DataMember(Name = "body", Order = 4), JsonPropertyName("body"), JsonPropertyOrder(4), YamlMember(Alias = "body", Order = 4)] - public virtual object? Body { get; set; } + [Description("The request body, if any.")] + [DataMember(Order = 4, Name = "body"), JsonPropertyOrder(4), JsonPropertyName("body")] + public JsonNode? Body { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/HttpResponse.cs b/src/ServerlessWorkflow.Sdk/Models/HttpResponse.cs index 773ff4c..7a6e6ac 100644 --- a/src/ServerlessWorkflow.Sdk/Models/HttpResponse.cs +++ b/src/ServerlessWorkflow.Sdk/Models/HttpResponse.cs @@ -16,34 +16,39 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to describe an HTTP response /// +[Description("Represents an object used to describe an HTTP response.")] [DataContract] -public record HttpResponse +public sealed record HttpResponse { /// /// Gets/sets the HTTP request associated with the HTTP response /// + [Description("The HTTP request associated with the HTTP response.")] [Required] - [DataMember(Name = "request", Order = 1), JsonPropertyName("request"), JsonPropertyOrder(1), YamlMember(Alias = "request", Order = 1)] - public required virtual HttpRequest Request { get; set; } + [DataMember(Order = 1, Name = "request"), JsonPropertyOrder(1), JsonPropertyName("request")] + public required HttpRequest Request { get; init; } /// /// Gets/sets the HTTP response's status code /// + [Description("The HTTP response's status code.")] [Required] - [DataMember(Name = "statusCode", Order = 2), JsonPropertyName("statusCode"), JsonPropertyOrder(2), YamlMember(Alias = "statusCode", Order = 2)] - public required virtual int StatusCode { get; set; } + [DataMember(Order = 2, Name = "statusCode"), JsonPropertyOrder(2), JsonPropertyName("statusCode")] + public required int StatusCode { get; init; } /// /// Gets/sets the response headers, if any /// - [DataMember(Name = "headers", Order = 3), JsonPropertyName("headers"), JsonPropertyOrder(3), YamlMember(Alias = "headers", Order = 3)] - public virtual EquatableDictionary? Headers { get; set; } + [Description("The response headers, if any.")] + [DataMember(Order = 3, Name = "headers"), JsonPropertyOrder(3), JsonPropertyName("headers")] + public EquatableDictionary? Headers { get; init; } /// /// Gets/sets the HTTP response's content, if any /// - [DataMember(Name = "content", Order = 4), JsonPropertyName("content"), JsonPropertyOrder(4), YamlMember(Alias = "content", Order = 4)] - public virtual object? Content { get; set; } + [Description("The HTTP response's content, if any.")] + [DataMember(Order = 4, Name = "content"), JsonPropertyOrder(4), JsonPropertyName("content")] + public JsonNode? Content { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/InputDataModelDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/InputDataModelDefinition.cs index 270bdde..be0c595 100644 --- a/src/ServerlessWorkflow.Sdk/Models/InputDataModelDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/InputDataModelDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an input data model /// +[Description("Represents the definition of an input data model")] [DataContract] -public record InputDataModelDefinition +public sealed record InputDataModelDefinition { /// /// Gets/sets the schema, if any, that defines and describes the input data of a workflow or task /// - [DataMember(Name = "schema", Order = 1), JsonPropertyName("schema"), JsonPropertyOrder(1), YamlMember(Alias = "schema", Order = 1)] - public virtual SchemaDefinition? Schema { get; set; } + [Description("The schema, if any, that defines and describes the input data of a workflow or task")] + [DataMember(Order = 1, Name = "schema"), JsonPropertyOrder(1), JsonPropertyName("schema")] + public SchemaDefinition? Schema { get; init; } /// /// Gets/sets a runtime expression, if any, used to build the workflow or task input data based on both input and scope data /// - [DataMember(Name = "from", Order = 2), JsonPropertyName("from"), JsonPropertyOrder(2), JsonInclude, YamlMember(Alias = "from", Order = 2)] - public virtual object? From { get; set; } + [Description("A runtime expression, if any, used to build the workflow or task input data based on both input and scope data")] + [DataMember(Order = 2, Name = "from"), JsonPropertyOrder(2), JsonPropertyName("from"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? From { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/JitterDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/JitterDefinition.cs index 861e193..3867e18 100644 --- a/src/ServerlessWorkflow.Sdk/Models/JitterDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/JitterDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of the parameters that control the randomness or variability of a delay, typically between retry attempts /// +[Description("Represents the definition of the parameters that control the randomness or variability of a delay, typically between retry attempts")] [DataContract] -public record JitterDefinition +public sealed record JitterDefinition { /// /// Gets/sets the minimum duration of the jitter range /// - [DataMember(Name = "from", Order = 1), JsonPropertyName("from"), JsonPropertyOrder(1), YamlMember(Alias = "from", Order = 1)] - public required virtual Duration From { get; set; } + [Description("The minimum duration of the jitter range")] + [DataMember(Order = 1, Name = "from"), JsonPropertyOrder(1), JsonPropertyName("from")] + public required Duration From { get; init; } /// /// Gets/sets the maximum duration of the jitter range /// - [DataMember(Name = "to", Order = 2), JsonPropertyName("to"), JsonPropertyOrder(2), YamlMember(Alias = "to", Order = 2)] - public required virtual Duration To { get; set; } + [Description("The maximum duration of the jitter range")] + [DataMember(Order = 2, Name = "to"), JsonPropertyOrder(2), JsonPropertyName("to")] + public required Duration To { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/LinearBackoffDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/LinearBackoffDefinition.cs index 502403d..bbcd30c 100644 --- a/src/ServerlessWorkflow.Sdk/Models/LinearBackoffDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/LinearBackoffDefinition.cs @@ -16,15 +16,17 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a linear backoff /// +[Description("Represents the definition of a linear backoff")] [DataContract] -public record LinearBackoffDefinition +public sealed record LinearBackoffDefinition : BackoffDefinition { /// /// Gets/sets the linear incrementation to the delay between retry attempts /// - [DataMember(Name = "increment", Order = 1), JsonPropertyName("increment"), JsonPropertyOrder(1), YamlMember(Alias = "increment", Order = 1)] - public virtual Duration? Increment { get; set; } + [Description("The linear incrementation to the delay between retry attempts")] + [DataMember(Order = 1, Name = "increment"), JsonPropertyOrder(1), JsonPropertyName("increment")] + public Duration? Increment { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ListenerDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ListenerDefinition.cs index f1178ad..f4faf21 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ListenerDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ListenerDefinition.cs @@ -16,22 +16,25 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of an event listener /// +[Description("Represents the configuration of an event listener")] [DataContract] -public record ListenerDefinition +public sealed record ListenerDefinition { /// /// Gets/sets the listener's target /// + [Description("The listener's target")] [Required] - [DataMember(Name = "to", Order = 1), JsonPropertyName("to"), JsonPropertyOrder(1), YamlMember(Alias = "to", Order = 1)] - public required virtual EventConsumptionStrategyDefinition To { get; set; } + [DataMember(Order = 1, Name = "to"), JsonPropertyOrder(1), JsonPropertyName("to")] + public required EventConsumptionStrategyDefinition To { get; init; } /// /// Gets/sets a string that specifies how events are read during the listen operation /// See . Defaults to /// - [DataMember(Name = "read", Order = 1), JsonPropertyName("read"), JsonPropertyOrder(1), YamlMember(Alias = "read", Order = 1)] - public virtual string? Read { get; set; } + [Description("A string that specifies how events are read during the listen operation. See EventReadMode. Defaults to EventReadMode.Data")] + [DataMember(Order = 2, Name = "read"), JsonPropertyOrder(2), JsonPropertyName("read")] + public string? Read { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/OneOf.cs b/src/ServerlessWorkflow.Sdk/Models/OneOf.cs index c57b8d8..5a9334f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/OneOf.cs +++ b/src/ServerlessWorkflow.Sdk/Models/OneOf.cs @@ -11,86 +11,126 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Serialization.Json; - namespace ServerlessWorkflow.Sdk.Models; /// -/// Gets an object that is one of the specified types +/// Represents a value that can be one of two possible types. /// -/// A first type alternative -/// A second type alternative -[DataContract, JsonConverter(typeof(OneOfConverter))] -public class OneOf - : IOneOf +/// The first possible type. +/// The second possible type. +public sealed record OneOf { + readonly byte tag; + readonly T1? t1; + readonly T2? t2; + /// - /// Initializes a new + /// Initializes a new . /// - /// The value of the + /// The value. public OneOf(T1 value) { - this.TypeIndex = 1; - this.T1Value = value!; + tag = 1; + t1 = value; + t2 = default; } /// - /// Initializes a new + /// Initializes a new . /// - /// The value of the + /// The value. public OneOf(T2 value) { - this.TypeIndex = 2; - this.T2Value = value!; + tag = 2; + t1 = default; + t2 = value; } /// - /// Gets the index of the discriminated type + /// Attempts to get the value as . /// - public int TypeIndex { get; } + /// The output value. + /// A boolean indicating whether the value was of type . + public bool TryGetAsT1(out T1? value) + { + value = t1; + if (tag is not 1) return false; + return true; + } /// - /// Gets the first possible value + /// Attempts to get the value as . /// - [DataMember(Order = 1), JsonIgnore, YamlIgnore] - public T1? T1Value { get; } + /// The output value. + /// A boolean indicating whether the value was of type . + public bool TryGetAsT2(out T2? value) + { + value = t2; + if (tag is not 2) return false; + + return true; + } /// - /// Gets the second possible value + /// Matches the value and invokes the corresponding function. /// - [DataMember(Order = 2), JsonIgnore, YamlIgnore] - public T2? T2Value { get; } - - object? IOneOf.GetValue() => this.TypeIndex switch + /// The return type of the functions. + /// The function to invoke if the value is of type . + /// The function to invoke if the value is of type . + /// TA value of type . + public T Match(Func f1, Func f2) => tag switch { - 1 => this.T1Value, - 2 => this.T2Value, - _ => null + 1 => f1(t1!), + 2 => f2(t2!), + _ => throw new InvalidOperationException("Invalid OneOf state."), }; /// - /// Implicitly convert the specified value into a new + /// Matches the value and invokes the corresponding function. /// - /// The value to convert - public static implicit operator OneOf(T1 value) => new(value); + /// The return type of the functions. + /// The function to invoke if the value is of type . + /// The function to invoke if the value is of type . + /// A . + /// TA value of type . + public async Task MatchAsync(Func> f1, Func> f2, CancellationToken cancellationToken = default) => tag switch + { + 1 => await f1(t1!, cancellationToken), + 2 => await f2(t2!, cancellationToken), + _ => throw new InvalidOperationException("Invalid OneOf state."), + }; /// - /// Implicitly convert the specified value into a new + /// Switches the value and invokes the corresponding action. /// - /// The value to convert - public static implicit operator OneOf(T2 value) => new(value); + /// The action to invoke if the value is of type . + /// The action to invoke if the value is of type . + public void Switch(Action a1, Action a2) + { + switch (tag) + { + case 1: + a1(t1!); + break; + case 2: + a2(t2!); + break; + default: + throw new InvalidOperationException("Invalid OneOf state."); + } + } /// - /// Implicitly convert the specified into a new value + /// Implicitly converts a to a . /// - /// The to convert - public static implicit operator T1?(OneOf value) => value.T1Value; + /// The value to convert. + public static implicit operator OneOf(T1 value) => new(value); /// - /// Implicitly convert the specified into a new value + /// Implicitly converts a to a . /// - /// The to convert - public static implicit operator T2?(OneOf value) => value.T2Value; + /// The value to convert. + public static implicit operator OneOf(T2 value) => new(value); -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Models/OutputDataModelDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/OutputDataModelDefinition.cs index 63f16ce..85ece7f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/OutputDataModelDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/OutputDataModelDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of an output data model /// +[Description("Represents the definition of an output data model")] [DataContract] -public record OutputDataModelDefinition +public sealed record OutputDataModelDefinition { /// /// Gets/sets the schema, if any, that defines and describes the output data of a workflow or task /// - [DataMember(Name = "schema", Order = 1), JsonPropertyName("schema"), JsonPropertyOrder(1), YamlMember(Alias = "schema", Order = 1)] - public virtual SchemaDefinition? Schema { get; set; } + [Description("The schema, if any, that defines and describes the output data of a workflow or task")] + [DataMember(Order = 1, Name = "schema"), JsonPropertyOrder(1), JsonPropertyName("schema")] + public SchemaDefinition? Schema { get; init; } /// /// Gets/sets a runtime expression, if any, used to output specific data to the scope data /// - [DataMember(Name = "as", Order = 3), JsonPropertyName("as"), JsonPropertyOrder(3), YamlMember(Alias = "as", Order = 3)] - public virtual object? As { get; set; } + [Description("A runtime expression, if any, used to output specific data to the scope data")] + [DataMember(Order = 2, Name = "as"), JsonPropertyOrder(2), JsonPropertyName("as"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? As { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ProcessDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ProcessDefinition.cs index 9a7c7f2..068ae14 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ProcessDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ProcessDefinition.cs @@ -16,6 +16,7 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the base class for all process definitions /// +[Description("Represents the base class for all process definitions")] [DataContract] public abstract record ProcessDefinition : Extendable diff --git a/src/ServerlessWorkflow.Sdk/Models/ProcessTypeDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ProcessTypeDefinition.cs index 22d6350..e04239d 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ProcessTypeDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ProcessTypeDefinition.cs @@ -11,68 +11,73 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models.Processes; - namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of a process execution /// +[Description("Represents the configuration of a process execution")] [DataContract] -public record ProcessTypeDefinition +public sealed record ProcessTypeDefinition { /// /// Gets/sets the configuration of the container to run /// - [DataMember(Name = "container", Order = 1), JsonPropertyName("container"), JsonPropertyOrder(1), YamlMember(Alias = "container", Order = 1)] - public virtual ContainerProcessDefinition? Container { get; set; } + [Description("The configuration of the container to run")] + [DataMember(Order = 1, Name = "container"), JsonPropertyOrder(1), JsonPropertyName("container")] + public ContainerProcessDefinition? Container { get; init; } /// /// Gets/sets the configuration of the shell command to run /// - [DataMember(Name = "shell", Order = 2), JsonPropertyName("shell"), JsonPropertyOrder(2), YamlMember(Alias = "shell", Order = 2)] - public virtual ShellProcessDefinition? Shell { get; set; } + [Description("The configuration of the shell command to run")] + [DataMember(Order = 2, Name = "shell"), JsonPropertyOrder(2), JsonPropertyName("shell")] + public ShellProcessDefinition? Shell { get; init; } /// /// Gets/sets the configuration of the script to run /// - [DataMember(Name = "script", Order = 3), JsonPropertyName("script"), JsonPropertyOrder(3), YamlMember(Alias = "script", Order = 3)] - public virtual ScriptProcessDefinition? Script { get; set; } + [Description("The configuration of the script to run")] + [DataMember(Order = 3, Name = "script"), JsonPropertyOrder(3), JsonPropertyName("script")] + public ScriptProcessDefinition? Script { get; init; } /// /// Gets/sets the configuration of the workflow to run /// - [DataMember(Name = "workflow", Order = 4), JsonPropertyName("workflow"), JsonPropertyOrder(4), YamlMember(Alias = "workflow", Order = 4)] - public virtual WorkflowProcessDefinition? Workflow { get; set; } + [Description("The configuration of the workflow to run")] + [DataMember(Order = 4, Name = "workflow"), JsonPropertyOrder(4), JsonPropertyName("workflow")] + public WorkflowProcessDefinition? Workflow { get; init; } /// /// Gets/sets a boolean indicating whether or not to await the process completion before continuing. Defaults to 'true'. /// - [DataMember(Name = "await", Order = 5), JsonPropertyName("await"), JsonPropertyOrder(5), YamlMember(Alias = "await", Order = 5)] - public virtual bool? Await { get; set; } + [Description("A boolean indicating whether or not to await the process completion before continuing. Defaults to 'true'.")] + [DataMember(Order = 5, Name = "await"), JsonPropertyOrder(5), JsonPropertyName("await")] + public bool? Await { get; init; } /// /// Gets/sets the output of the process. /// See /// Defaults to /// - [DataMember(Name = "return", Order = 6), JsonPropertyName("return"), JsonPropertyOrder(6), YamlMember(Alias = "return", Order = 6)] - public virtual string? Return { get; set; } + [Description("The output of the process. See ProcessReturnType. Defaults to ProcessReturnType.Stdout")] + [DataMember(Order = 6, Name = "return"), JsonPropertyOrder(6), JsonPropertyName("return")] + public string? Return { get; init; } /// /// Gets the type of the defined process tasks /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string ProcessType + [IgnoreDataMember, JsonIgnore] + public string ProcessType { get { - if (this.Container != null) return Models.ProcessType.Container; - if (this.Shell != null) return Models.ProcessType.Shell; - if (this.Script != null) return Models.ProcessType.Script; - if (this.Workflow != null) return Models.ProcessType.Workflow; - return Models.ProcessType.Extension; + if (Container != null) return Sdk.ProcessType.Container; + if (Shell != null) return Sdk.ProcessType.Shell; + if (Script != null) return Sdk.ProcessType.Script; + if (Workflow != null) return Sdk.ProcessType.Workflow; + return Sdk.ProcessType.Extension; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Processes/ContainerProcessDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Processes/ContainerProcessDefinition.cs index d235097..a8fe743 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Processes/ContainerProcessDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Processes/ContainerProcessDefinition.cs @@ -16,52 +16,60 @@ namespace ServerlessWorkflow.Sdk.Models.Processes; /// /// Represents the configuration of a container process /// +[Description("Represents the configuration of a container process")] [DataContract] -public record ContainerProcessDefinition +public sealed record ContainerProcessDefinition : ProcessDefinition { /// /// Gets/sets the name of the container image to run /// + [Description("The name of the container image to run")] [Required, MinLength(1)] - [DataMember(Name = "image", Order = 1), JsonPropertyName("image"), JsonPropertyOrder(1), YamlMember(Alias = "image", Order = 1)] - public required virtual string Image { get; set; } + [DataMember(Order = 1, Name = "image"), JsonPropertyOrder(1), JsonPropertyName("image")] + public required string Image { get; init; } /// /// Gets/sets a runtime expression, if any, used to give specific name to the container /// - [DataMember(Name = "name", Order = 2), JsonPropertyName("name"), JsonPropertyOrder(2), YamlMember(Alias = "name", Order = 2)] - public virtual string? Name { get; set; } + [Description("A runtime expression, if any, used to give specific name to the container")] + [DataMember(Order = 2, Name = "name"), JsonPropertyOrder(2), JsonPropertyName("name")] + public string? Name { get; init; } /// /// Gets/sets the command, if any, to execute on the container /// - [DataMember(Name = "command", Order = 3), JsonPropertyName("command"), JsonPropertyOrder(3), YamlMember(Alias = "command", Order = 3)] - public virtual string? Command { get; set; } + [Description("The command, if any, to execute on the container")] + [DataMember(Order = 3, Name = "command"), JsonPropertyOrder(3), JsonPropertyName("command")] + public string? Command { get; init; } /// /// Gets/sets a list containing the container's port mappings, if any /// - [DataMember(Name = "ports", Order = 4), JsonPropertyName("ports"), JsonPropertyOrder(4), YamlMember(Alias = "ports", Order = 4)] - public virtual EquatableDictionary? Ports { get; set; } + [Description("A list containing the container's port mappings, if any")] + [DataMember(Order = 4, Name = "ports"), JsonPropertyOrder(4), JsonPropertyName("ports")] + public EquatableDictionary? Ports { get; init; } /// /// Gets/sets the volume mapping for the container, if any /// - [DataMember(Name = "volumes", Order = 5), JsonPropertyName("volumes"), JsonPropertyOrder(5), YamlMember(Alias = "volumes", Order = 5)] - public virtual EquatableDictionary? Volumes { get; set; } + [Description("The volume mapping for the container, if any")] + [DataMember(Order = 5, Name = "volumes"), JsonPropertyOrder(5), JsonPropertyName("volumes")] + public EquatableDictionary? Volumes { get; init; } /// /// Gets/sets a key/value mapping of the environment variables, if any, to use when running the configured process /// - [DataMember(Name = "environment", Order = 6), JsonPropertyName("environment"), JsonPropertyOrder(6), YamlMember(Alias = "environment", Order = 6)] - public virtual EquatableDictionary? Environment { get; set; } + [Description("A key/value mapping of the environment variables, if any, to use when running the configured process")] + [DataMember(Order = 6, Name = "environment"), JsonPropertyOrder(6), JsonPropertyName("environment")] + public EquatableDictionary? Environment { get; init; } /// /// Gets/sets an object object used to configure the container's lifetime /// - [DataMember(Name = "lifetime", Order = 7), JsonPropertyName("lifetime"), JsonPropertyOrder(7), YamlMember(Alias = "lifetime", Order = 7)] - public virtual ContainerLifetimeDefinition? Lifetime { get; set; } + [Description("An object object used to configure the container's lifetime")] + [DataMember(Order = 7, Name = "lifetime"), JsonPropertyOrder(7), JsonPropertyName("lifetime")] + public ContainerLifetimeDefinition? Lifetime { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Processes/ScriptProcessDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Processes/ScriptProcessDefinition.cs index f44ddde..673b93c 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Processes/ScriptProcessDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Processes/ScriptProcessDefinition.cs @@ -16,39 +16,45 @@ namespace ServerlessWorkflow.Sdk.Models.Processes; /// /// Represents the definition of a script evaluation process /// +[Description("Represents the definition of a script evaluation process")] [DataContract] -public record ScriptProcessDefinition +public sealed record ScriptProcessDefinition : ProcessDefinition { /// /// Gets/sets the language of the script to run /// - [DataMember(Name = "language", Order = 1), JsonPropertyName("language"), JsonPropertyOrder(1), YamlMember(Alias = "language", Order = 1)] - public required virtual string Language { get; set; } + [Description("The language of the script to run")] + [DataMember(Order = 1, Name = "language"), JsonPropertyOrder(1), JsonPropertyName("language")] + public required string Language { get; init; } /// /// Gets/sets the script's code. Required if has not been set. /// - [DataMember(Name = "code", Order = 2), JsonPropertyName("code"), JsonPropertyOrder(2), YamlMember(Alias = "code", Order = 2)] - public virtual string? Code { get; set; } + [Description("The script's code. Required if Source has not been set.")] + [DataMember(Order = 2, Name = "code"), JsonPropertyOrder(2), JsonPropertyName("code")] + public string? Code { get; init; } /// /// Gets the the script's source. Required if has not been set. /// - [DataMember(Name = "source", Order = 3), JsonPropertyName("source"), JsonPropertyOrder(3), YamlMember(Alias = "source", Order = 3)] - public virtual ExternalResourceDefinition? Source { get; set; } + [Description("The script's source. Required if Code has not been set.")] + [DataMember(Order = 3, Name = "source"), JsonPropertyOrder(3), JsonPropertyName("source")] + public ExternalResourceDefinition? Source { get; init; } /// /// Gets/sets a key/value mapping of the arguments, if any, to pass to the script to run /// - [DataMember(Name = "arguments", Order = 4), JsonPropertyName("arguments"), JsonPropertyOrder(4), YamlMember(Alias = "arguments", Order = 4)] - public virtual EquatableDictionary? Arguments { get; set; } + [Description("A key/value mapping of the arguments, if any, to pass to the script to run")] + [DataMember(Order = 4, Name = "arguments"), JsonPropertyOrder(4), JsonPropertyName("arguments")] + public EquatableDictionary? Arguments { get; init; } /// /// Gets/sets a key/value mapping of the environment variables, if any, to use when running the configured process /// - [DataMember(Name = "environment", Order = 5), JsonPropertyName("environment"), JsonPropertyOrder(5), YamlMember(Alias = "environment", Order = 5)] - public virtual EquatableDictionary? Environment { get; set; } + [Description("A key/value mapping of the environment variables, if any, to use when running the configured process")] + [DataMember(Order = 5, Name = "environment"), JsonPropertyOrder(5), JsonPropertyName("environment")] + public EquatableDictionary? Environment { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Processes/ShellProcessDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Processes/ShellProcessDefinition.cs index 5661485..b7190f3 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Processes/ShellProcessDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Processes/ShellProcessDefinition.cs @@ -16,28 +16,32 @@ namespace ServerlessWorkflow.Sdk.Models.Processes; /// /// Represents the definition of a shell process /// +[Description("Represents the definition of a shell process")] [DataContract] -public record ShellProcessDefinition +public sealed record ShellProcessDefinition : ProcessDefinition { /// /// Gets/sets the shell command to run /// + [Description("The shell command to run")] [Required, MinLength(1)] - [DataMember(Name = "command", Order = 1), JsonPropertyName("command"), JsonPropertyOrder(1), YamlMember(Alias = "command", Order = 1)] - public required virtual string Command { get; set; } + [DataMember(Order = 1, Name = "command"), JsonPropertyOrder(1), JsonPropertyName("command")] + public required string Command { get; init; } /// /// Gets/sets the arguments of the shell command to run /// - [DataMember(Name = "arguments", Order = 2), JsonPropertyName("arguments"), JsonPropertyOrder(2), YamlMember(Alias = "arguments", Order = 2)] - public virtual EquatableList? Arguments { get; set; } + [Description("The arguments of the shell command to run")] + [DataMember(Order = 2, Name = "arguments"), JsonPropertyOrder(2), JsonPropertyName("arguments")] + public EquatableList? Arguments { get; init; } /// /// Gets/sets a key/value mapping of the environment variables, if any, to use when running the configured process /// - [DataMember(Name = "environment", Order = 3), JsonPropertyName("environment"), JsonPropertyOrder(3), YamlMember(Alias = "environment", Order = 3)] - public virtual EquatableDictionary? Environment { get; set; } + [Description("A key/value mapping of the environment variables, if any, to use when running the configured process")] + [DataMember(Order = 3, Name = "environment"), JsonPropertyOrder(3), JsonPropertyName("environment")] + public EquatableDictionary? Environment { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Processes/WorkflowProcessDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Processes/WorkflowProcessDefinition.cs index 2913975..7e3d42f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Processes/WorkflowProcessDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Processes/WorkflowProcessDefinition.cs @@ -11,43 +11,45 @@ // See the License for the specific language governing permissions and // limitations under the License. -using YamlDotNet.Core; - namespace ServerlessWorkflow.Sdk.Models.Processes; /// /// Represents the definition of a (sub)workflow process /// [DataContract] -public record WorkflowProcessDefinition +public sealed record WorkflowProcessDefinition : ProcessDefinition { /// /// Gets/sets the namespace the workflow to run belongs to /// - [Required, MinLength(1), MaxLength(63)] - [DataMember(Name = "namespace", Order = 1), JsonPropertyName("namespace"), JsonPropertyOrder(1), YamlMember(Alias = "namespace", Order = 1)] - public required virtual string Namespace { get; set; } + [Description("The namespace the workflow to run belongs to")] + [Required, StringLength(63, MinimumLength = 1)] + [DataMember(Order = 1, Name = "namespace"), JsonPropertyOrder(1), JsonPropertyName("namespace")] + public required string Namespace { get; init; } /// /// Gets/sets the name of the workflow to run /// - [Required, MinLength(1), MaxLength(63)] - [DataMember(Name = "name", Order = 2), JsonPropertyName("name"), JsonPropertyOrder(2), YamlMember(Alias = "name", Order = 2)] - public required virtual string Name { get; set; } + [Description("The name of the workflow to run")] + [Required, StringLength(63, MinimumLength = 1)] + [DataMember(Order = 2, Name = "name"), JsonPropertyOrder(2), JsonPropertyName("name")] + public required string Name { get; init; } /// /// Gets/sets the version of the workflow to run. Defaults to `latest` /// + [Description("The version of the workflow to run. Defaults to `latest`")] [SemanticVersion] - [DataMember(Name = "version", Order = 3), JsonPropertyName("version"), JsonPropertyOrder(3), YamlMember(Alias = "version", Order = 3, ScalarStyle = ScalarStyle.SingleQuoted)] - public virtual string Version { get; set; } = "latest"; + [DataMember(Order = 3, Name = "version"), JsonPropertyOrder(3), JsonPropertyName("version")] + public string Version { get; init; } = "latest"; /// /// Gets/sets the data, if any, to pass as input to the workflow to execute. The value should be validated against the target workflow's input schema, if specified /// - [DataMember(Name = "input", Order = 4), JsonPropertyName("input"), JsonPropertyOrder(4), YamlMember(Alias = "input", Order = 4)] - public virtual object? Input { get; set; } + [Description("The data, if any, to pass as input to the workflow to execute. The value should be validated against the target workflow's input schema, if specified")] + [DataMember(Order = 4, Name = "input"), JsonPropertyOrder(4), JsonPropertyName("input")] + public JsonObject? Input { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/RaiseErrorDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/RaiseErrorDefinition.cs index 7a38ef4..e5b4550 100644 --- a/src/ServerlessWorkflow.Sdk/Models/RaiseErrorDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/RaiseErrorDefinition.cs @@ -16,43 +16,17 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of the error to raise /// +[Description("Represents the definition of the error to raise")] [DataContract] -public record RaiseErrorDefinition +public sealed record RaiseErrorDefinition { - /// - /// Gets/sets the definition of the error to raise - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual ErrorDefinition? Error - { - get => this.ErrorValue.T1Value; - set - { - ArgumentNullException.ThrowIfNull(value); - this.ErrorValue = value; - } - } - - /// - /// Gets/sets the reference of the error to raise - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string? ErrorReference - { - get => this.ErrorValue.T2Value; - set - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.ErrorValue = value; - } - } - /// /// Gets/sets the error to raise /// + [Description("The error to raise")] [Required] - [DataMember(Name = "error", Order = 1), JsonInclude, JsonPropertyName("error"), JsonPropertyOrder(1), YamlMember(Alias = "error", Order = 1)] - protected virtual OneOf ErrorValue { get; set; } = null!; + [DataMember(Order = 1, Name = "error"), JsonPropertyOrder(1), JsonPropertyName("error"), JsonConverter(typeof(OneOfJsonConverter))] + public required OneOf Error { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/ReferenceableComponentDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/ReferenceableComponentDefinition.cs index 511d59b..e70334f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ReferenceableComponentDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/ReferenceableComponentDefinition.cs @@ -16,6 +16,7 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the base class for all ServerlessWorkflow referenceable workflow components /// +[Description("Represents the base class for all ServerlessWorkflow referenceable workflow components")] [DataContract] public abstract record ReferenceableComponentDefinition : ComponentDefinition, IReferenceable @@ -24,7 +25,8 @@ public abstract record ReferenceableComponentDefinition /// /// Gets/sets an URI, if any, that reference the component's definition /// - [DataMember(Order = 1, Name = "$ref"), JsonPropertyOrder(1), JsonPropertyName("$ref"), YamlMember(Order = 1, Alias = "$ref")] - public virtual Uri? Ref { get; set; } + [Description("An URI, if any, that reference the component's definition")] + [DataMember(Order = 1, Name = "ref"), JsonPropertyOrder(1), JsonPropertyName("ref")] + public Uri? Ref { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/RetryAttemptLimitDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/RetryAttemptLimitDefinition.cs index 256aca1..8d15014 100644 --- a/src/ServerlessWorkflow.Sdk/Models/RetryAttemptLimitDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/RetryAttemptLimitDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of the limits for all retry attempts of a given policy /// +[Description("Represents the definition of the limits for all retry attempts of a given policy")] [DataContract] -public record RetryAttemptLimitDefinition +public sealed record RetryAttemptLimitDefinition { /// /// Gets/sets the maximum attempts count /// - [DataMember(Name = "count", Order = 1), JsonPropertyName("count"), JsonPropertyOrder(1), YamlMember(Alias = "count", Order = 1)] - public virtual uint? Count { get; set; } + [Description("The maximum attempts count")] + [DataMember(Order = 1, Name = "count"), JsonPropertyOrder(1), JsonPropertyName("count")] + public uint? Count { get; init; } /// /// Gets/sets the duration limit, if any, for all retry attempts /// - [DataMember(Name = "duration", Order = 2), JsonPropertyName("duration"), JsonPropertyOrder(2), YamlMember(Alias = "duration", Order = 2)] - public virtual Duration? Duration { get; set; } + [Description("The duration limit, if any, for all retry attempts")] + [DataMember(Order = 2, Name = "duration"), JsonPropertyOrder(2), JsonPropertyName("duration")] + public Duration? Duration { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/RetryPolicyDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/RetryPolicyDefinition.cs index 2f408a8..7186c18 100644 --- a/src/ServerlessWorkflow.Sdk/Models/RetryPolicyDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/RetryPolicyDefinition.cs @@ -16,45 +16,52 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a retry policy /// +[Description("Represents the definition of a retry policy")] [DataContract] -public record RetryPolicyDefinition +public sealed record RetryPolicyDefinition : ReferenceableComponentDefinition { /// /// Gets/sets a runtime expression used to determine whether or not to retry running the task, in a given context /// - [DataMember(Name = "when", Order = 1), JsonPropertyName("when"), JsonPropertyOrder(1), YamlMember(Alias = "when", Order = 1)] - public virtual string? When { get; set; } + [Description("A runtime expression used to determine whether or not to retry running the task, in a given context")] + [DataMember(Order = 1, Name = "when"), JsonPropertyOrder(1), JsonPropertyName("when")] + public string? When { get; init; } /// /// Gets/sets a runtime expression used to determine whether or not to retry running the task, in a given context /// - [DataMember(Name = "exceptWhen", Order = 2), JsonPropertyName("exceptWhen"), JsonPropertyOrder(2), YamlMember(Alias = "exceptWhen", Order = 2)] - public virtual string? ExceptWhen { get; set; } + [Description("A runtime expression used to determine whether or not to retry running the task, in a given context")] + [DataMember(Order = 2, Name = "exceptWhen"), JsonPropertyOrder(2), JsonPropertyName("exceptWhen")] + public string? ExceptWhen { get; init; } /// /// Gets/sets the limits, if any, of the retry policy /// - [DataMember(Name = "limit", Order = 3), JsonPropertyName("limit"), JsonPropertyOrder(2), YamlMember(Alias = "limit", Order = 3)] - public virtual RetryPolicyLimitDefinition? Limit { get; set; } + [Description("The limits, if any, of the retry policy")] + [DataMember(Order = 3, Name = "limit"), JsonPropertyOrder(3), JsonPropertyName("limit")] + public RetryPolicyLimitDefinition? Limit { get; init; } /// /// Gets/sets the delay duration between retry attempts /// - [DataMember(Name = "delay", Order = 4), JsonPropertyName("delay"), JsonPropertyOrder(4), YamlMember(Alias = "delay", Order = 4)] - public virtual Duration? Delay { get; set; } + [Description("The delay duration between retry attempts")] + [DataMember(Order = 4, Name = "delay"), JsonPropertyOrder(4), JsonPropertyName("delay")] + public Duration? Delay { get; init; } /// /// Gets/sets the backoff strategy to use, if any /// - [DataMember(Name = "backoff", Order = 5), JsonPropertyName("backoff"), JsonPropertyOrder(5), YamlMember(Alias = "backoff", Order = 5)] - public virtual BackoffStrategyDefinition? Backoff { get; set; } + [Description("The backoff strategy to use, if any")] + [DataMember(Order = 5, Name = "backoff"), JsonPropertyOrder(5), JsonPropertyName("backoff")] + public BackoffStrategyDefinition? Backoff { get; init; } /// /// Gets/sets the parameters, if any, that control the randomness or variability of the delay between retry attempts /// - [DataMember(Name = "jitter", Order = 6), JsonPropertyName("jitter"), JsonPropertyOrder(6), YamlMember(Alias = "jitter", Order = 6)] - public virtual JitterDefinition? Jitter { get; set; } + [Description("The parameters, if any, that control the randomness or variability of the delay between retry attempts")] + [DataMember(Order = 6, Name = "jitter"), JsonPropertyOrder(6), JsonPropertyName("jitter")] + public JitterDefinition? Jitter { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/RetryPolicyLimitDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/RetryPolicyLimitDefinition.cs index 1e3f360..f29b4de 100644 --- a/src/ServerlessWorkflow.Sdk/Models/RetryPolicyLimitDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/RetryPolicyLimitDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the configuration of the limits of a retry policy /// +[Description("Represents the configuration of the limits of a retry policy")] [DataContract] -public record RetryPolicyLimitDefinition +public sealed record RetryPolicyLimitDefinition { /// /// Gets/sets the definition of the limits for all retry attempts of a given policy /// - [DataMember(Name = "attempt", Order = 1), JsonPropertyName("attempt"), JsonPropertyOrder(1), YamlMember(Alias = "attempt", Order = 1)] - public virtual RetryAttemptLimitDefinition? Attempt { get; set; } + [Description("The definition of the limits for all retry attempts of a given policy")] + [DataMember(Order = 1, Name = "attempt"), JsonPropertyOrder(1), JsonPropertyName("attempt")] + public RetryAttemptLimitDefinition? Attempt { get; init; } /// /// Gets/sets the maximum duration, if any, during which to retry a given task /// - [DataMember(Name = "duration", Order = 2), JsonPropertyName("duration"), JsonPropertyOrder(2), YamlMember(Alias = "duration", Order = 2)] - public virtual Duration? Duration { get; set; } + [Description("The maximum duration, if any, during which to retry a given task")] + [DataMember(Order = 2, Name = "duration"), JsonPropertyOrder(2), JsonPropertyName("duration")] + public Duration? Duration { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/RuntimeDescriptor.cs b/src/ServerlessWorkflow.Sdk/Models/RuntimeDescriptor.cs new file mode 100644 index 0000000..9da1500 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/RuntimeDescriptor.cs @@ -0,0 +1,47 @@ +// 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 an runtime expression argument used to describe the current runtime +/// +[Description("Represents an runtime expression argument used to describe the current runtime.")] +[DataContract] +public sealed record RuntimeDescriptor +{ + + /// + /// Gets/sets the runtime's name + /// + [Description("The runtime's name.")] + [Required, StringLength(int.MaxValue, MinimumLength = 1)] + [DataMember(Order = 1, Name = "name"), JsonPropertyOrder(1), JsonPropertyName("name")] + public required string Name { get; init; } + + /// + /// Gets/sets the runtime's version + /// + [Description("The runtime's version.")] + [Required, StringLength(int.MaxValue, MinimumLength = 1), SemanticVersion] + [DataMember(Order = 2, Name = "version"), JsonPropertyOrder(2), JsonPropertyName("version")] + public required string Version { get; init; } + + /// + /// Gets/sets a key/value mapping of the runtime's metadata, if any + /// + [Description("A key/value mapping of the runtime's metadata, if any.")] + [DataMember(Order = 3, Name = "metadata"), JsonPropertyOrder(3), JsonPropertyName("metadata")] + public JsonObject? Metadata { get; init; } + +} diff --git a/src/ServerlessWorkflow.Sdk/Models/RuntimeExpressionEvaluationConfiguration.cs b/src/ServerlessWorkflow.Sdk/Models/RuntimeExpressionEvaluationConfiguration.cs index 6f6c33b..871e632 100644 --- a/src/ServerlessWorkflow.Sdk/Models/RuntimeExpressionEvaluationConfiguration.cs +++ b/src/ServerlessWorkflow.Sdk/Models/RuntimeExpressionEvaluationConfiguration.cs @@ -16,20 +16,24 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents an object used to configure the workflow's runtime expression evaluation /// +[Description("Represents an object used to configure the workflow's runtime expression evaluation.")] [DataContract] -public record RuntimeExpressionEvaluationConfiguration +public sealed record RuntimeExpressionEvaluationConfiguration { /// /// Gets/sets the language used for writing runtime expressions. Defaults to . /// - [DataMember(Name = "language", Order = 1), JsonPropertyName("language"), JsonPropertyOrder(1), YamlMember(Alias = "language", Order = 1)] - public virtual string Language { get; set; } = RuntimeExpressions.Languages.JQ; + [Description("The language used for writing runtime expressions. Defaults to JQ.")] + [Required, StringLength(int.MaxValue, MinimumLength = 1)] + [DataMember(Order = 1, Name = "language"), JsonPropertyOrder(1), JsonPropertyName("language")] + public string Language { get; init; } = RuntimeExpressions.Languages.JQ; /// /// Gets/sets the language used for writing runtime expressions. Defaults to /// - [DataMember(Name = "mode", Order = 2), JsonPropertyName("mode"), JsonPropertyOrder(2), YamlMember(Alias = "mode", Order = 2)] - public virtual string? Mode { get; set; } + [Description("The mode used for evaluating runtime expressions. Defaults to Strict.")] + [DataMember(Order = 2, Name = "mode"), JsonPropertyOrder(2), JsonPropertyName("mode")] + public string? Mode { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/SchemaDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/SchemaDefinition.cs index b1e5fb0..7818cb7 100644 --- a/src/ServerlessWorkflow.Sdk/Models/SchemaDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/SchemaDefinition.cs @@ -16,27 +16,31 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a schema /// +[Description("Represents the definition of a schema")] [DataContract] -public record SchemaDefinition +public sealed record SchemaDefinition { /// /// Gets/sets the schema's format. Defaults to 'json'. The (optional) version of the format can be set using `{format}:{version}`. /// - [Required] - [DataMember(Name = "format", Order = 1), JsonPropertyName("format"), JsonPropertyOrder(1), YamlMember(Alias = "format", Order = 1)] - public virtual string Format { get; set; } = SchemaFormat.Json; + [Required, StringLength(int.MaxValue, MinimumLength = 1), DefaultValue(SchemaFormat.Json)] + [Description("The schema's format. Defaults to 'json'. The (optional) version of the format can be set using `{format}:{version}`.")] + [DataMember(Order = 1, Name = "format"), JsonPropertyOrder(1), JsonPropertyName("format")] + public string Format { get; init; } = SchemaFormat.Json; /// /// Gets/sets the schema's external resource, if any. Required if has not been set. /// - [DataMember(Name = "resource", Order = 2), JsonPropertyName("resource"), JsonPropertyOrder(2), YamlMember(Alias = "resource", Order = 2)] - public virtual ExternalResourceDefinition? Resource { get; set; } + [Description("The schema's external resource, if any. Required if Document has not been set.")] + [DataMember(Order = 2, Name = "resource"), JsonPropertyOrder(2), JsonPropertyName("resource")] + public ExternalResourceDefinition? Resource { get; init; } /// /// Gets/sets the inline definition of the schema to use. Required if has not been set. /// - [DataMember(Name = "document", Order = 3), JsonPropertyName("document"), JsonPropertyOrder(3), YamlMember(Alias = "document", Order = 3)] - public virtual object? Document { get; set; } + [Description("The inline definition of the schema to use. Required if Resource has not been set.")] + [DataMember(Order = 3, Name = "document"), JsonPropertyOrder(3), JsonPropertyName("document"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? Document { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/SubscriptionIteratorDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/SubscriptionIteratorDefinition.cs index 0c4b7f9..6666642 100644 --- a/src/ServerlessWorkflow.Sdk/Models/SubscriptionIteratorDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/SubscriptionIteratorDefinition.cs @@ -16,40 +16,46 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a subscription iterator, used to configure the processing of each event or message consumed by a subscription /// +[Description("Represents the definition of a subscription iterator, used to configure the processing of each event or message consumed by a subscription")] [DataContract] -public record SubscriptionIteratorDefinition +public sealed record SubscriptionIteratorDefinition { /// /// Gets/sets the name of the variable used to store the item being enumerated. /// Defaults to `item` /// - [DataMember(Name = "item", Order = 1), JsonPropertyName("item"), JsonPropertyOrder(1), YamlMember(Alias = "item", Order = 1)] - public virtual string? Item { get; set; } + [Description("The name of the variable used to store the item being enumerated. Defaults to `item`")] + [DataMember(Order = 1, Name = "item"), JsonPropertyOrder(1), JsonPropertyName("item")] + public string? Item { get; init; } /// /// Gets/sets the name of the variable used to store the index of the item being enumerates /// Defaults to `index` /// - [DataMember(Name = "at", Order = 2), JsonPropertyName("at"), JsonPropertyOrder(2), YamlMember(Alias = "at", Order = 2)] - public virtual string? At { get; set; } + [Description("The name of the variable used to store the index of the item being enumerates. Defaults to `index`")] + [DataMember(Order = 2, Name = "index"), JsonPropertyOrder(2), JsonPropertyName("index")] + public string? At { get; init; } /// /// Gets/sets the tasks to run for each consumed event or message /// - [DataMember(Name = "do", Order = 3), JsonPropertyName("do"), JsonPropertyOrder(3), YamlMember(Alias = "do", Order = 3)] - public virtual Map? Do { get; set; } + [Description("The tasks to run for each consumed event or message")] + [DataMember(Order = 3, Name = "do"), JsonPropertyOrder(3), JsonPropertyName("do")] + public Map? Do { get; init; } /// /// Gets/sets the definition, if any, of the data to output for each iteration /// - [DataMember(Name = "output", Order = 4), JsonPropertyName("output"), JsonPropertyOrder(4), YamlMember(Alias = "output", Order = 4)] - public virtual OutputDataModelDefinition? Output { get; set; } + [Description("The definition, if any, of the data to output for each iteration")] + [DataMember(Order = 4, Name = "output"), JsonPropertyOrder(4), JsonPropertyName("output")] + public OutputDataModelDefinition? Output { get; init; } /// /// Gets/sets the definition, if any, of the data to export for each iteration /// - [DataMember(Name = "export", Order = 5), JsonPropertyName("export"), JsonPropertyOrder(5), YamlMember(Alias = "export", Order = 5)] - public virtual OutputDataModelDefinition? Export { get; set; } + [Description("The definition, if any, of the data to export for each iteration")] + [DataMember(Order = 5, Name = "export"), JsonPropertyOrder(5), JsonPropertyName("export")] + public OutputDataModelDefinition? Export { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/SwitchCaseDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/SwitchCaseDefinition.cs index fc5c9d7..80e239b 100644 --- a/src/ServerlessWorkflow.Sdk/Models/SwitchCaseDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/SwitchCaseDefinition.cs @@ -16,20 +16,23 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a case within a switch task, defining a condition and corresponding tasks to execute if the condition is met /// +[Description("Represents the definition of a case within a switch task, defining a condition and corresponding tasks to execute if the condition is met")] [DataContract] -public record SwitchCaseDefinition +public sealed record SwitchCaseDefinition { /// /// Gets/sets the condition that determines whether or not the case should be executed in a switch task /// - [DataMember(Name = "when", Order = 1), JsonPropertyName("when"), JsonPropertyOrder(1), YamlMember(Alias = "when", Order = 1)] - public virtual string? When { get; set; } + [Description("The condition that determines whether or not the case should be executed in a switch task")] + [DataMember(Order = 1, Name = "when"), JsonPropertyOrder(1), JsonPropertyName("when")] + public string? When { get; init; } /// /// Gets/sets the transition to perform when the case matches /// - [DataMember(Name = "then", Order = 2), JsonPropertyName("then"), JsonPropertyOrder(2), YamlMember(Alias = "then", Order = 2)] - public virtual string? Then { get; set; } + [Description("The transition to perform when the case matches")] + [DataMember(Order = 2, Name = "then"), JsonPropertyOrder(2), JsonPropertyName("then")] + public string? Then { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs index f92e142..b7468a4 100644 --- a/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/TaskDefinition.cs @@ -11,13 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Serialization.Json; - namespace ServerlessWorkflow.Sdk.Models; /// -/// Represents the definition of a task +/// Represents the definition of a task. /// +[Description("Represents the definition of a task.")] [DataContract, JsonConverter(typeof(TaskDefinitionJsonConverter))] public abstract record TaskDefinition : ComponentDefinition @@ -26,78 +25,56 @@ public abstract record TaskDefinition /// /// Gets the type of the defined task /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public abstract string Type { get; } /// /// Gets/sets a runtime expression, if any, used to determine whether or not the execute the task in the current context /// - [DataMember(Name = "if", Order = 0), JsonPropertyName("if"), JsonPropertyOrder(0), YamlMember(Alias = "if", Order = 0)] - public virtual string? If { get; set; } + [Description("A runtime expression, if any, used to determine whether or not the execute the task in the current context")] + [DataMember(Order = 0, Name = "if"), JsonPropertyOrder(0), JsonPropertyName("if")] + public string? If { get; init; } /// /// Gets/sets the definition, if any, of the task's input data /// - [DataMember(Name = "input", Order = 10), JsonPropertyName("input"), JsonPropertyOrder(10), YamlMember(Alias = "input", Order = 10)] - public virtual InputDataModelDefinition? Input { get; set; } + [Description("The definition, if any, of the task's input data")] + [DataMember(Order = 90, Name = "input"), JsonPropertyOrder(90), JsonPropertyName("input")] + public InputDataModelDefinition? Input { get; init; } /// /// Gets/sets the definition, if any, of the task's output data /// - [DataMember(Name = "output", Order = 11), JsonPropertyName("output"), JsonPropertyOrder(11), YamlMember(Alias = "output", Order = 11)] - public virtual OutputDataModelDefinition? Output { get; set; } + [Description("The definition, if any, of the task's output data")] + [DataMember(Order = 91, Name = "output"), JsonPropertyOrder(91), JsonPropertyName("output")] + public OutputDataModelDefinition? Output { get; init; } /// /// Gets/sets the optional configuration for exporting data within the task's context /// - [DataMember(Name = "export", Order = 12), JsonPropertyName("export"), JsonPropertyOrder(12), YamlMember(Alias = "export", Order = 12)] - public virtual OutputDataModelDefinition? Export { get; set; } + [Description("The optional configuration for exporting data within the task's context")] + [DataMember(Order = 92, Name = "export"), JsonPropertyOrder(92), JsonPropertyName("export")] + public OutputDataModelDefinition? Export { get; init; } /// /// Gets/sets the task's timeout, if any /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual TimeoutDefinition? Timeout - { - get => this.TimeoutValue?.T1Value; - set - { - ArgumentNullException.ThrowIfNull(value); - this.TimeoutValue = value; - } - } - - /// - /// Gets/sets the reference to the task's timeout, if any - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string? TimeoutReference - { - get => this.TimeoutValue?.T2Value; - set - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.TimeoutValue = value; - } - } - - /// - /// Gets/sets the task's timeout, if any - /// - [DataMember(Name = "timeout", Order = 13), JsonPropertyName("timeout"), JsonPropertyOrder(13), YamlMember(Alias = "timeout", Order = 13)] - protected virtual OneOf? TimeoutValue { get; set; } = null!; + [Description("The task's timeout, if any")] + [DataMember(Order = 93, Name = "timeout"), JsonPropertyOrder(93), JsonPropertyName("timeout"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? Timeout { get; init; } /// /// Gets/sets the flow directive to be performed upon completion of the task /// - [DataMember(Name = "then", Order = 14), JsonPropertyName("then"), JsonPropertyOrder(14), YamlMember(Alias = "then", Order = 14)] - public virtual string? Then { get; set; } + [Description("The flow directive to be performed upon completion of the task")] + [DataMember(Order = 94, Name = "then"), JsonPropertyOrder(94), JsonPropertyName("then")] + public string? Then { get; init; } /// /// Gets/sets a key/value mapping of additional information associated with the task /// - [DataMember(Name = "metadata", Order = 15), JsonPropertyName("metadata"), JsonPropertyOrder(15), YamlMember(Alias = "metadata", Order = 15)] - public virtual EquatableDictionary? Metadata { get; set; } + [Description("A key/value mapping of additional information associated with the task")] + [DataMember(Order = 95, Name = "metadata"), JsonPropertyOrder(95), JsonPropertyName("metadata")] + public JsonObject? Metadata { get; init; } } - diff --git a/src/ServerlessWorkflow.Sdk/Models/TaskDescriptor.cs b/src/ServerlessWorkflow.Sdk/Models/TaskDescriptor.cs new file mode 100644 index 0000000..91b76cb --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/TaskDescriptor.cs @@ -0,0 +1,76 @@ +// 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 an runtime expression argument used to describe the task being executed +/// +[Description("Represents an runtime expression argument used to describe the task being executed.")] +[DataContract] +public sealed record TaskDescriptor +{ + + /// + /// Gets the task's unique identifier + /// + [Description("The task's unique identifier.")] + [Required] + [DataMember(Order = 1, Name = "id"), JsonPropertyOrder(1), JsonPropertyName("id")] + public required string Id { get; init; } + + /// + /// Gets/sets the task's name + /// + [Description("The task's name.")] + [DataMember(Order = 2, Name = "name"), JsonPropertyOrder(2), JsonPropertyName("name")] + public string? Name { get; init; } + + /// + /// Gets/sets the task's reference + /// + [Description("The task's reference.")] + [Required] + [DataMember(Order = 3, Name = "reference"), JsonPropertyOrder(3), JsonPropertyName("reference")] + public required JsonPointer Reference { get; init; } + + /// + /// Gets/sets the task's definition + /// + [Description("The task's definition.")] + [Required] + [DataMember(Order = 4, Name = "definition"), JsonPropertyOrder(4), JsonPropertyName("definition")] + public required TaskDefinition Definition { get; init; } + + /// + /// Gets/sets the task's raw, untransformed input + /// + [Description("The task's raw, untransformed input.")] + [DataMember(Order = 5, Name = "input"), JsonPropertyOrder(5), JsonPropertyName("input")] + public JsonNode? Input { get; init; } + + /// + /// Gets/sets the task's raw, untransformed output + /// + [Description("The task's raw, untransformed output.")] + [DataMember(Order = 6, Name = "output"), JsonPropertyOrder(6), JsonPropertyName("output")] + public JsonNode? Output { get; init; } + + /// + /// Gets/sets the date and time at which the task has started + /// + [Description("The date and time at which the task has started.")] + [DataMember(Order = 7, Name = "startedAt"), JsonPropertyOrder(7), JsonPropertyName("startedAt")] + public DateTimeDescriptor? StartedAt { get; init; } + +} diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/CallTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/CallTaskDefinition.cs index f52ae0c..aa4a7e0 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/CallTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/CallTaskDefinition.cs @@ -16,32 +16,36 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task used to call a predefined function /// +[Description("Represents the definition of a task used to call a predefined function")] [DataContract] -public record CallTaskDefinition +public sealed record CallTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Call; /// /// Gets/sets the reference to the function to call /// + [Description("The reference to the function to call")] [Required, MinLength(1)] - [DataMember(Name = "call", Order = 1), JsonPropertyName("call"), JsonPropertyOrder(1), YamlMember(Alias = "call", Order = 1)] - public required virtual string Call { get; set; } + [DataMember(Order = 1, Name = "call"), JsonPropertyOrder(1), JsonPropertyName("call")] + public required string Call { get; init; } /// - /// Gets/sets a key/value mapping of the call's arguments + /// Gets/sets a key/value mapping, if any, of the call's arguments /// - [DataMember(Name = "with", Order = 2), JsonPropertyName("with"), JsonPropertyOrder(2), YamlMember(Alias = "with", Order = 2)] - public virtual EquatableDictionary? With { get; set; } + [Description("A key/value mapping, if any, of the call's arguments")] + [DataMember(Order = 2, Name = "with"), JsonPropertyOrder(2), JsonPropertyName("with")] + public JsonObject? With { get; init; } /// /// Gets/sets a boolean indicating whether or not to wait for the called function to return. Defaults to true. /// - [DataMember(Name = "await", Order = 3), JsonPropertyName("await"), JsonPropertyOrder(3), YamlMember(Alias = "await", Order = 3)] - public virtual bool? Await { get; set; } + [Description("A boolean indicating whether or not to wait for the called function to return. Defaults to true.")] + [DataMember(Order = 3, Name = "await"), JsonPropertyOrder(3), JsonPropertyName("await")] + public bool? Await { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/DoTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/DoTaskDefinition.cs index 8d0b007..2c4ba1f 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/DoTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/DoTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the configuration of a task that is composed of multiple subtasks to run sequentially /// +[Description("Represents the configuration of a task that is composed of multiple subtasks to run sequentially")] [DataContract] -public record DoTaskDefinition +public sealed record DoTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Do; /// /// Gets/sets a name/definition mapping of the subtasks to perform sequentially /// [Required, MinLength(1)] - [DataMember(Name = "do", Order = 1), JsonPropertyName("do"), JsonPropertyOrder(1), YamlMember(Alias = "do", Order = 1)] - public required virtual Map Do { get; set; } + [Description("A name/definition mapping of the subtasks to perform sequentially")] + [DataMember(Order = 1, Name = "do"), JsonPropertyOrder(1), JsonPropertyName("do")] + public required Map Do { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/EmitTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/EmitTaskDefinition.cs index 3eced45..fe30de3 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/EmitTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/EmitTaskDefinition.cs @@ -16,19 +16,21 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the configuration of a task used to emit an event /// +[Description("Represents the configuration of a task used to emit an event")] [DataContract] -public record EmitTaskDefinition +public sealed record EmitTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Emit; /// /// Gets/sets the configuration of an event's emission /// - [DataMember(Name = "emit", Order = 1), JsonPropertyName("emit"), JsonPropertyOrder(1), YamlMember(Alias = "emit", Order = 1)] - public required virtual EventEmissionDefinition Emit { get; set; } + [Description("The configuration of an event's emission")] + [DataMember(Order = 1, Name = "emit"), JsonPropertyOrder(1), JsonPropertyName("emit")] + public required EventEmissionDefinition Emit { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/ExtensionTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/ExtensionTaskDefinition.cs index 67d5dc3..1a6283e 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/ExtensionTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/ExtensionTaskDefinition.cs @@ -16,19 +16,21 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of an extension's task /// +[Description("Represents the definition of an extension's task")] [DataContract] -public record ExtensionTaskDefinition - : TaskDefinition, IExtensible +public sealed record ExtensionTaskDefinition + : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Extension; /// /// Gets/sets the task definition's extension data, if any /// - [DataMember(Name = "extensionData", Order = 1), JsonExtensionData] - public virtual IDictionary? ExtensionData { get; set; } + [Description("The task definition's extension data, if any")] + [DataMember(Order = 1, Name = "extensionData"), JsonExtensionData] + public IDictionary? ExtensionData { get; set; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/ForTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/ForTaskDefinition.cs index a2a4741..13cfa04 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/ForTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/ForTaskDefinition.cs @@ -16,33 +16,37 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task that executes a set of subtasks iteratively for each element in a collection /// +[Description("Represents the definition of a task that executes a set of subtasks iteratively for each element in a collection")] [DataContract] -public record ForTaskDefinition +public sealed record ForTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.For; /// /// Gets/sets the definition of the loop that iterates over a range of values /// + [Description("The definition of the loop that iterates over a range of values")] [Required] - [DataMember(Name = "for", Order = 1), JsonPropertyName("for"), JsonPropertyOrder(1), YamlMember(Alias = "for", Order = 1)] - public required virtual ForLoopDefinition For { get; set; } + [DataMember(Order = 1, Name = "for"), JsonPropertyOrder(1), JsonPropertyName("for")] + public required ForLoopDefinition For { get; init; } /// /// Gets/sets a runtime expression that represents the condition, if any, that must be met for the iteration to continue /// - [DataMember(Name = "while", Order = 2), JsonPropertyName("while"), JsonPropertyOrder(2), YamlMember(Alias = "while", Order = 2)] - public virtual string? While { get; set; } + [Description("A runtime expression that represents the condition, if any, that must be met for the iteration to continue")] + [DataMember(Order = 2, Name = "while"), JsonPropertyOrder(2), JsonPropertyName("while")] + public string? While { get; init; } /// /// Gets/sets the tasks to perform for each item in the collection /// + [Description("The tasks to perform for each item in the collection")] [Required] - [DataMember(Name = "do", Order = 3), JsonPropertyName("do"), JsonPropertyOrder(3), YamlMember(Alias = "do", Order = 3)] - public required virtual Map Do { get; set; } + [DataMember(Order = 3, Name = "do"), JsonPropertyOrder(3), JsonPropertyName("do")] + public required Map Do { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/ForkTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/ForkTaskDefinition.cs index 356d85c..53fc36e 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/ForkTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/ForkTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the configuration of a task that is composed of multiple subtasks to run concurrently /// +[Description("Represents the configuration of a task that is composed of multiple subtasks to run concurrently")] [DataContract] -public record ForkTaskDefinition +public sealed record ForkTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Fork; /// /// Gets/sets the configuration of the branches to perform concurrently /// + [Description("The configuration of the branches to perform concurrently")] [Required] - [DataMember(Name = "fork", Order = 1), JsonPropertyName("fork"), JsonPropertyOrder(1), YamlMember(Alias = "fork", Order = 1)] - public required virtual BranchingDefinition Fork { get; set; } + [DataMember(Order = 1, Name = "fork"), JsonPropertyOrder(1), JsonPropertyName("fork")] + public required BranchingDefinition Fork { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/ListenTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/ListenTaskDefinition.cs index 88bc273..66bf48e 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/ListenTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/ListenTaskDefinition.cs @@ -16,26 +16,29 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the configuration of a task used to listen to specific events /// +[Description("Represents the configuration of a task used to listen to specific events")] [DataContract] -public record ListenTaskDefinition +public sealed record ListenTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Listen; /// /// Gets/sets the configuration of the listener to use /// + [Description("The configuration of the listener to use")] [Required] - [DataMember(Name = "listen", Order = 1), JsonPropertyName("listen"), JsonPropertyOrder(1), YamlMember(Alias = "listen", Order = 1)] - public required virtual ListenerDefinition Listen { get; set; } + [DataMember(Order = 1, Name = "listen"), JsonPropertyOrder(1), JsonPropertyName("listen")] + public required ListenerDefinition Listen { get; init; } /// /// Gets/sets the configuration of the iterator, if any, used to process each consumed event /// - [DataMember(Name = "foreach", Order = 2), JsonPropertyName("foreach"), JsonPropertyOrder(2), YamlMember(Alias = "foreach", Order = 2)] - public virtual SubscriptionIteratorDefinition? Foreach { get; set; } + [Description("The configuration of the iterator, if any, used to process each consumed event")] + [DataMember(Order = 2, Name = "foreach"), JsonPropertyOrder(2), JsonPropertyName("foreach")] + public SubscriptionIteratorDefinition? Foreach { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/RaiseTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/RaiseTaskDefinition.cs index f11b2ed..c3e219b 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/RaiseTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/RaiseTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task used to raise an error /// +[Description("Represents the definition of a task used to raise an error")] [DataContract] -public record RaiseTaskDefinition +public sealed record RaiseTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Raise; /// /// Gets/sets the definition of the error to raise /// + [Description("The definition of the error to raise")] [Required] - [DataMember(Name = "raise", Order = 1), JsonPropertyName("raise"), JsonPropertyOrder(1), YamlMember(Alias = "raise", Order = 1)] - public required virtual RaiseErrorDefinition Raise { get; set; } + [DataMember(Order = 1, Name = "raise"), JsonPropertyOrder(1), JsonPropertyName("raise")] + public required RaiseErrorDefinition Raise { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/RunTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/RunTaskDefinition.cs index 01cecc0..013aad9 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/RunTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/RunTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the configuration of a task used to run a given process /// +[Description("Represents the configuration of a task used to run a given process")] [DataContract] -public record RunTaskDefinition +public sealed record RunTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Run; /// /// Gets/sets the configuration of the process to execute /// + [Description("The configuration of the process to execute")] [Required] - [DataMember(Name = "run", Order = 1), JsonPropertyName("run"), JsonPropertyOrder(1), YamlMember(Alias = "run", Order = 1)] - public required virtual ProcessTypeDefinition Run { get; set; } = null!; + [DataMember(Order = 1, Name = "run"), JsonPropertyOrder(1), JsonPropertyName("run")] + public required ProcessTypeDefinition Run { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/SetTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/SetTaskDefinition.cs index ce54d40..5a9b4ba 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/SetTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/SetTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task used to set data /// +[Description("Represents the definition of a task used to set data")] [DataContract] -public record SetTaskDefinition +public sealed record SetTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Set; /// /// Gets/sets the data to set /// - [Required, MinLength(1)] - [DataMember(Name = "set", Order = 1), JsonPropertyName("set"), JsonPropertyOrder(1), YamlMember(Alias = "set", Order = 1)] - public required virtual EquatableDictionary Set { get; set; } + [Description("The data to set")] + [Required] + [DataMember(Order = 1, Name = "set"), JsonPropertyOrder(1), JsonPropertyName("set")] + public required JsonObject Set { get; init; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/SwitchTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/SwitchTaskDefinition.cs index d0eb8dd..bd3cbdd 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/SwitchTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/SwitchTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task that evaluates conditions and executes specific branches based on the result /// +[Description("Represents the definition of a task that evaluates conditions and executes specific branches based on the result")] [DataContract] -public record SwitchTaskDefinition +public sealed record SwitchTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Switch; /// /// Gets/sets the definition of the switch to use /// + [Description("The definition of the switch to use")] [Required] - [DataMember(Name = "switch", Order = 1), JsonPropertyName("switch"), JsonPropertyOrder(1), YamlMember(Alias = "switch", Order = 1)] - public required virtual Map Switch { get; set; } + [DataMember(Order = 1, Name = "switch"), JsonPropertyOrder(1), JsonPropertyName("switch")] + public required Map Switch { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs index fc12c88..17e0e77 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/TryTaskDefinition.cs @@ -16,27 +16,30 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task used to try one or more subtasks, and to catch/handle the errors that can potentially be raised during execution /// +[Description("Represents the definition of a task used to try one or more subtasks, and to catch/handle the errors that can potentially be raised during execution")] [DataContract] -public record TryTaskDefinition +public sealed record TryTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Try; /// /// Gets/sets a name/definition map of the tasks to try running /// + [Description("A name/definition map of the tasks to try running")] [Required] - [DataMember(Name = "try", Order = 1), JsonPropertyName("try"), JsonPropertyOrder(1), YamlMember(Alias = "try", Order = 1)] - public required virtual Map Try { get; set; } + [DataMember(Order = 1, Name = "try"), JsonPropertyOrder(1), JsonPropertyName("try")] + public required Map Try { get; init; } /// /// Gets/sets the object used to define the errors to catch /// + [Description("The object used to define the errors to catch")] [Required] - [DataMember(Name = "catch", Order = 2), JsonPropertyName("catch"), JsonPropertyOrder(2), YamlMember(Alias = "catch", Order = 2)] - public required virtual ErrorCatcherDefinition Catch { get; set; } + [DataMember(Order = 2, Name = "catch"), JsonPropertyOrder(2), JsonPropertyName("catch")] + public required ErrorCatcherDefinition Catch { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/Tasks/WaitTaskDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/Tasks/WaitTaskDefinition.cs index 8baf689..c2bbd28 100644 --- a/src/ServerlessWorkflow.Sdk/Models/Tasks/WaitTaskDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/Tasks/WaitTaskDefinition.cs @@ -16,20 +16,22 @@ namespace ServerlessWorkflow.Sdk.Models.Tasks; /// /// Represents the definition of a task used to wait a certain amount of time /// +[Description("Represents the definition of a task used to wait a certain amount of time")] [DataContract] -public record WaitTaskDefinition +public sealed record WaitTaskDefinition : TaskDefinition { /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] + [IgnoreDataMember, JsonIgnore] public override string Type => TaskType.Wait; /// /// Gets/sets the amount of time to wait before resuming workflow /// + [Description("The amount of time to wait before resuming workflow")] [Required] - [DataMember(Name = "wait", Order = 1), JsonPropertyName("wait"), JsonPropertyOrder(1), YamlMember(Alias = "wait", Order = 1)] - public required virtual Duration Wait { get; set; } + [DataMember(Order = 1, Name = "wait"), JsonPropertyOrder(1), JsonPropertyName("wait")] + public required Duration Wait { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/TimeoutDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/TimeoutDefinition.cs index 69c5455..2cc6505 100644 --- a/src/ServerlessWorkflow.Sdk/Models/TimeoutDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/TimeoutDefinition.cs @@ -11,51 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Xml; - namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a timeout /// +[Description("Represents the definition of a timeout")] [DataContract] -public record TimeoutDefinition +public sealed record TimeoutDefinition { /// /// Gets/sets the duration after which to timeout /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual Duration After - { - get => this.AfterValue.T1Value ?? Duration.FromTimeSpan(XmlConvert.ToTimeSpan(this.AfterValue.T2Value!)); - set - { - ArgumentNullException.ThrowIfNull(value); - this.AfterValue = value; - } - } - - /// - /// Gets/sets the ISO 8601 expression of the duration after which to timeout - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string AfterExpression - { - get => this.AfterValue.T2Value ?? XmlConvert.ToString(this.AfterValue.T1Value!.ToTimeSpan()); - set - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.AfterValue = value; - } - } - - /// - /// Gets/sets the duration after which to timeout - /// + [Description("The duration after which to timeout")] [Required] - [DataMember(Name = "after", Order = 1), JsonInclude, JsonPropertyName("after"), JsonPropertyOrder(1), YamlMember(Alias = "after", Order = 1)] - protected virtual OneOf AfterValue { get; set; } = null!; - -} + [DataMember(Order = 1, Name = "after"), JsonPropertyOrder(1), JsonPropertyName("after"), JsonConverter(typeof(OneOfJsonConverter))] + public required OneOf After { get; init; } +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinition.cs index 81b0c91..394fd5d 100644 --- a/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinition.cs @@ -16,92 +16,74 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a workflow /// +[Description("Represents the definition of a workflow.")] [DataContract] -public record WorkflowDefinition +public sealed record WorkflowDefinition { /// /// Gets/sets an object used to document the defined workflow /// + [Description("An object used to document the defined workflow.")] [Required] - [DataMember(Name = "document", Order = 1), JsonPropertyName("document"), JsonPropertyOrder(1), YamlMember(Alias = "document", Order = 1)] - public required virtual WorkflowDefinitionMetadata Document { get; set; } + [DataMember(Order = 1, Name = "document"), JsonPropertyOrder(1), JsonPropertyName("document")] + public required WorkflowDefinitionMetadata Document { get; init; } /// /// Gets/sets the workflow's input definition, if any /// - [DataMember(Name = "input", Order = 2), JsonPropertyName("input"), JsonPropertyOrder(2), YamlMember(Alias = "input", Order = 3)] - public virtual InputDataModelDefinition? Input { get; set; } + [Description("The workflow's input definition, if any.")] + [DataMember(Order = 2, Name = "input"), JsonPropertyOrder(2), JsonPropertyName("input")] + public InputDataModelDefinition? Input { get; init; } /// /// Gets/sets a collection that contains reusable components for the workflow definition /// - [DataMember(Name = "use", Order = 3), JsonPropertyName("use"), JsonPropertyOrder(3), YamlMember(Alias = "use", Order = 3)] - public virtual ComponentDefinitionCollection? Use { get; set; } + [Description("A collection that contains reusable components for the workflow definition.")] + [DataMember(Order = 3, Name = "use"), JsonPropertyOrder(3), JsonPropertyName("use")] + public ComponentDefinitionCollection? Use { get; init; } /// /// Gets/sets the workflow's timeout, if any /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual TimeoutDefinition? Timeout - { - get => this.TimeoutValue?.T1Value; - set - { - ArgumentNullException.ThrowIfNull(value); - this.TimeoutValue = value; - } - } - - /// - /// Gets/sets the reference to the workflow's timeout, if any - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public virtual string? TimeoutReference - { - get => this.TimeoutValue?.T2Value; - set - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - this.TimeoutValue = value; - } - } - - /// - /// Gets/sets the workflow's timeout, if any - /// - [DataMember(Name = "timeout", Order = 4), JsonPropertyName("timeout"), JsonPropertyOrder(4), YamlMember(Alias = "timeout", Order = 4)] - protected virtual OneOf? TimeoutValue { get; set; } = null!; + [Description("The workflow's timeout, if any.")] + [DataMember(Order = 4, Name = "timeout"), JsonPropertyOrder(4), JsonPropertyName("timeout"), JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? Timeout { get; init; } = null!; /// /// Gets/sets the workflow's output definition, if any /// - [DataMember(Name = "output", Order = 5), JsonPropertyName("output"), JsonPropertyOrder(5), YamlMember(Alias = "output", Order = 5)] - public virtual OutputDataModelDefinition? Output { get; set; } + [Description("The workflow's output definition, if any.")] + [DataMember(Order = 5, Name = "output"), JsonPropertyOrder(5), JsonPropertyName("output")] + public OutputDataModelDefinition? Output { get; init; } /// /// Gets/sets the definition of the workflow's schedule, if any /// - [DataMember(Name = "schedule", Order = 6), JsonPropertyName("schedule"), JsonPropertyOrder(6), YamlMember(Alias = "schedule", Order = 6)] - public virtual WorkflowScheduleDefinition? Schedule { get; set; } + [Description("The definition of the workflow's schedule, if any.")] + [DataMember(Order = 6, Name = "schedule"), JsonPropertyOrder(6), JsonPropertyName("schedule")] + public WorkflowScheduleDefinition? Schedule { get; init; } /// /// Gets/sets the configuration of how the runtime expressions /// - [DataMember(Name = "evaluate", Order = 7), JsonPropertyName("evaluate"), JsonPropertyOrder(7), YamlMember(Alias = "evaluate", Order = 7)] - public virtual RuntimeExpressionEvaluationConfiguration? Evaluate { get; set; } + [Description("The configuration of how the runtime expressions should be evaluated at runtime.")] + [DataMember(Order = 7, Name = "evaluate"), JsonPropertyOrder(7), JsonPropertyName("evaluate")] + public RuntimeExpressionEvaluationConfiguration? Evaluate { get; init; } /// /// Gets/sets a name/value mapping of the tasks to perform /// + [Description("A name/value mapping of the tasks to perform.")] [Required, MinLength(1)] - [DataMember(Name = "do", Order = 8), JsonPropertyName("do"), JsonPropertyOrder(8), YamlMember(Alias = "do", Order = 8)] - public required virtual Map Do { get; set; } = []; + [DataMember(Order = 8, Name = "do"), JsonPropertyOrder(8), JsonPropertyName("do")] + public required Map Do { get; init; } = []; /// /// Gets/sets a key/value mapping of additional information associated with the workflow /// - [DataMember(Name = "metadata", Order = 9), JsonPropertyName("metadata"), JsonPropertyOrder(9), YamlMember(Alias = "metadata", Order = 9)] - public virtual EquatableDictionary? Metadata { get; set; } + [Description("A key/value mapping of additional information associated with the workflow.")] + [DataMember(Order = 9, Name = "metadata"), JsonPropertyOrder(9), JsonPropertyName("metadata")] + public EquatableDictionary? Metadata { get; init; } } diff --git a/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionMetadata.cs b/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionMetadata.cs index afacd64..3af930e 100644 --- a/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionMetadata.cs +++ b/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionMetadata.cs @@ -11,15 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -using YamlDotNet.Core; - namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the metadata of a workflow, including its name, version, and description. /// +[Description("Represents the metadata of a workflow, including its name, version, and description.")] [DataContract] -public record WorkflowDefinitionMetadata +public sealed record WorkflowDefinitionMetadata { /// @@ -30,46 +29,53 @@ public record WorkflowDefinitionMetadata /// /// Gets/sets the version of the DSL used to define the workflow /// + [Description("The version of the DSL used to define the workflow.")] [Required, MinLength(1)] - [DataMember(Name = "dsl", Order = 1), JsonPropertyName("dsl"), JsonPropertyOrder(1), YamlMember(Alias = "dsl", Order = 1, ScalarStyle = ScalarStyle.SingleQuoted)] - public required virtual string Dsl { get; set; } = null!; + [DataMember(Order = 1, Name = "dsl"), JsonPropertyOrder(1), JsonPropertyName("dsl")] + public required string Dsl { get; init; } = null!; /// /// Gets/sets the workflow's namespace /// - [DataMember(Name = "namespace", Order = 2), JsonPropertyName("namespace"), JsonPropertyOrder(2), YamlMember(Alias = "namespace", Order = 2)] - public virtual string Namespace { get; set; } = DefaultNamespace; + [Description("The workflow's namespace.")] + [DataMember(Order = 2, Name = "namespace"), JsonPropertyOrder(2), JsonPropertyName("namespace")] + public string Namespace { get; init; } = DefaultNamespace; /// /// Gets/sets the workflow's name /// + [Description("The workflow's name.")] [Required, MinLength(1)] - [DataMember(Name = "name", Order = 3), JsonPropertyName("name"), JsonPropertyOrder(3), YamlMember(Alias = "name", Order = 3)] - public required virtual string Name { get; set; } = null!; + [DataMember(Order = 3, Name = "name"), JsonPropertyOrder(3), JsonPropertyName("name")] + public required string Name { get; init; } = null!; /// /// Gets/sets the workflow's semantic version /// + [Description("The workflow's semantic version.")] [Required, MinLength(1)] - [DataMember(Name = "version", Order = 4), JsonPropertyName("version"), JsonPropertyOrder(4), YamlMember(Alias = "version", Order = 4, ScalarStyle = ScalarStyle.SingleQuoted)] - public required virtual string Version { get; set; } = null!; + [DataMember(Order = 4, Name = "version"), JsonPropertyOrder(4), JsonPropertyName("version")] + public required string Version { get; init; } = null!; /// /// Gets/sets the workflow's title, if any /// - [DataMember(Name = "title", Order = 5), JsonPropertyName("title"), JsonPropertyOrder(5), YamlMember(Alias = "title", Order = 5)] - public virtual string? Title { get; set; } + [Description("The workflow's title, if any.")] + [DataMember(Order = 5, Name = "title"), JsonPropertyOrder(5), JsonPropertyName("title")] + public string? Title { get; init; } /// /// Gets/sets the workflow's Markdown summary, if any /// - [DataMember(Name = "summary", Order = 6), JsonPropertyName("summary"), JsonPropertyOrder(6), YamlMember(Alias = "summary", Order = 6)] - public virtual string? Summary { get; set; } + [Description("The workflow's Markdown summary, if any.")] + [DataMember(Order = 6, Name = "summary"), JsonPropertyOrder(6), JsonPropertyName("summary")] + public string? Summary { get; init; } /// /// Gets/sets a key/value mapping of the workflow's tags, if any /// - [DataMember(Name = "tags", Order = 7), JsonPropertyName("tags"), JsonPropertyOrder(7), YamlMember(Alias = "tags", Order = 7)] - public virtual EquatableDictionary? Tags { get; set; } + [Description("A key/value mapping of the workflow's tags, if any.")] + [DataMember(Order = 7, Name = "tags"), JsonPropertyOrder(7), JsonPropertyName("tags")] + public EquatableDictionary? Tags { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionReference.cs b/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionReference.cs new file mode 100644 index 0000000..f115dc1 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/WorkflowDefinitionReference.cs @@ -0,0 +1,106 @@ +// 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 a reference to a +/// +[Description("Represents a reference to a workflow definition.")] +[DataContract] +public sealed record WorkflowDefinitionReference +{ + + /// + /// Gets/sets the name of the referenced workflow definition + /// + [Description("The name of the referenced workflow definition.")] + [Required, MinLength(1)] + [DataMember(Order = 1, Name = "name"), JsonPropertyOrder(1), JsonPropertyName("name")] + public required string Name { get; set; } + + /// + /// Gets/sets the namespace of the referenced workflow definition + /// + [Description("The namespace of the referenced workflow definition.")] + [Required, MinLength(1)] + [DataMember(Order = 2, Name = "namespace"), JsonPropertyOrder(2), JsonPropertyName("namespace")] + public required string Namespace { get; set; } + + /// + /// Gets/sets the semantic version of the referenced workflow definition + /// + [Description("The semantic version of the referenced workflow definition.")] + [Required, MinLength(1)] + [DataMember(Order = 3, Name = "version"), JsonPropertyOrder(3), JsonPropertyName("version")] + public required string Version { get; set; } + + /// + public override string ToString() => $"{this.Name}.{this.Namespace}:{this.Version}"; + + /// + /// Parses the specified input into a new + /// + /// The input to parse + /// The parsed + public static WorkflowDefinitionReference Parse(string input) + { + if(string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException(nameof(input)); + var components = input.Trim().Split(':'); + var qualifiedName = components[0]; + var version = components[1]; + components = qualifiedName.Split('.'); + var @namespace = components[0]; + var name = components[1]; + return new() + { + Name = name, + Namespace = @namespace, + Version = version + }; + } + + /// + /// Attempts to parse the specified input into a new + /// + /// The input to parse + /// The The parsed , if any + /// A boolean indicating whether or not the specified input could be parsed into a new + public static bool TryParse(string input, out WorkflowDefinitionReference? reference) + { + if (string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException(nameof(input)); + reference = null; + try + { + reference = Parse(input); + return true; + } + catch + { + return false; + } + } + + /// + /// Implicitly converts the specified reference into string + /// + /// The reference to convert + public static implicit operator string(WorkflowDefinitionReference reference) => reference.ToString(); + + /// + /// Implicitly parses the specified string into a new reference + /// + /// The string to parse + public static implicit operator WorkflowDefinitionReference(string reference) => Parse(reference); + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/WorkflowDescriptor.cs b/src/ServerlessWorkflow.Sdk/Models/WorkflowDescriptor.cs new file mode 100644 index 0000000..3b05399 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Models/WorkflowDescriptor.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 an runtime expression argument used to describe the workflow being executed +/// +[Description("Represents an runtime expression argument used to describe the workflow being executed.")] +[DataContract] +public sealed record WorkflowDescriptor +{ + + /// + /// Gets/sets the workflow's id + /// + [Description("The workflow's id.")] + [Required, StringLength(int.MaxValue, MinimumLength = 1)] + [DataMember(Order = 1, Name = "id"), JsonPropertyOrder(1), JsonPropertyName("id")] + public required string Id { get; init; } + + /// + /// Gets/sets the workflow's definition + /// + [Description("The workflow's definition.")] + [Required] + [DataMember(Order = 2, Name = "definition"), JsonPropertyOrder(2), JsonPropertyName("definition")] + public required WorkflowDefinition Definition { get; init; } + + /// + /// Gets/sets the workflow's raw, untransformed input + /// + [Description("The workflow's raw, untransformed input.")] + [DataMember(Order = 3, Name = "input"), JsonPropertyOrder(3), JsonPropertyName("input")] + public JsonObject? Input { get; init; } + + /// + /// Gets/sets the date and time at which the workflow has started + /// + [Description("The date and time at which the workflow has started.")] + [Required] + [DataMember(Order = 4, Name = "startedAt"), JsonPropertyOrder(4), JsonPropertyName("startedAt")] + public DateTimeDescriptor? StartedAt { get; init; } + +} diff --git a/src/ServerlessWorkflow.Sdk/Models/WorkflowScheduleDefinition.cs b/src/ServerlessWorkflow.Sdk/Models/WorkflowScheduleDefinition.cs index 82231a4..c3b5dc0 100644 --- a/src/ServerlessWorkflow.Sdk/Models/WorkflowScheduleDefinition.cs +++ b/src/ServerlessWorkflow.Sdk/Models/WorkflowScheduleDefinition.cs @@ -16,32 +16,37 @@ namespace ServerlessWorkflow.Sdk.Models; /// /// Represents the definition of a workflow's schedule /// +[Description("Represents the definition of a workflow's schedule.")] [DataContract] -public record WorkflowScheduleDefinition +public sealed record WorkflowScheduleDefinition { /// /// Gets/sets an object used to document the defined workflow /// - [DataMember(Name = "every", Order = 1), JsonPropertyName("every"), JsonPropertyOrder(1), YamlMember(Alias = "every", Order = 1)] - public virtual Duration? Every { get; set; } + [Description("An object used to document the defined workflow.")] + [DataMember(Order = 1, Name = "document"), JsonPropertyOrder(1), JsonPropertyName("document")] + public Duration? Every { get; init; } /// /// Gets/sets the schedule using a CRON expression, e.g., '0 0 * * *' for daily at midnight. /// - [DataMember(Name = "cron", Order = 2), JsonPropertyName("cron"), JsonPropertyOrder(2), YamlMember(Alias = "cron", Order = 2)] - public virtual string? Cron { get; set; } + [Description("The schedule using a CRON expression, e.g., '0 0 * * *' for daily at midnight.")] + [DataMember(Order = 2, Name = "cron"), JsonPropertyOrder(2), JsonPropertyName("cron")] + public string? Cron { get; init; } /// /// Gets/sets a delay duration, if any, that the workflow must wait before starting again after it completes. In other words, when this workflow completes, it should run again after the specified amount of time. /// - [DataMember(Name = "after", Order = 3), JsonPropertyName("after"), JsonPropertyOrder(3), YamlMember(Alias = "after", Order = 3)] - public virtual Duration? After { get; set; } + [Description("A delay duration, if any, that the workflow must wait before starting again after it completes. In other words, when this workflow completes, it should run again after the specified amount of time.")] + [DataMember(Order = 3, Name = "after"), JsonPropertyOrder(3), JsonPropertyName("after")] + public Duration? After { get; init; } /// /// Gets/sets the events that trigger the workflow execution. /// - [DataMember(Name = "on", Order = 4), JsonPropertyName("on"), JsonPropertyOrder(4), YamlMember(Alias = "on", Order = 4)] - public virtual EventConsumptionStrategyDefinition? On { get; set; } + [Description("The events that trigger the workflow execution.")] + [DataMember(Order = 4, Name = "on"), JsonPropertyOrder(4), JsonPropertyName("on")] + public EventConsumptionStrategyDefinition? On { get; init; } } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/NamingConvention.cs b/src/ServerlessWorkflow.Sdk/NamingConvention.cs index 619e365..fb3bb95 100644 --- a/src/ServerlessWorkflow.Sdk/NamingConvention.cs +++ b/src/ServerlessWorkflow.Sdk/NamingConvention.cs @@ -19,7 +19,7 @@ namespace ServerlessWorkflow.Sdk; public static class NamingConvention { - static readonly int _nameMaxLength = 63; + static readonly int maxLength = 63; /// /// Determines whether or not the specified value is a valid name, following RFC 1123 DNS label name @@ -29,11 +29,10 @@ public static class NamingConvention public static bool IsValidName(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); - return name.Length <= _nameMaxLength - && name.IsLowercased() - && name.IsAlphanumeric('-') + return name.Length <= maxLength + && name.All(c => char.IsDigit(c) || c == '-' || (char.IsLetter(c) && char.IsLower(c))) && char.IsLetterOrDigit(name.First()) && char.IsLetterOrDigit(name.Last()); } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/OAuth2ClientAuthenticationMethod.cs b/src/ServerlessWorkflow.Sdk/OAuth2ClientAuthenticationMethod.cs index 6764ca8..63380e1 100644 --- a/src/ServerlessWorkflow.Sdk/OAuth2ClientAuthenticationMethod.cs +++ b/src/ServerlessWorkflow.Sdk/OAuth2ClientAuthenticationMethod.cs @@ -40,6 +40,11 @@ public static class OAuth2ClientAuthenticationMethod /// public const string None = "none"; + /// + /// Gets an containing all supported values + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing all supported values /// diff --git a/src/ServerlessWorkflow.Sdk/Models/OAuth2GrantType.cs b/src/ServerlessWorkflow.Sdk/OAuth2GrantType.cs similarity index 91% rename from src/ServerlessWorkflow.Sdk/Models/OAuth2GrantType.cs rename to src/ServerlessWorkflow.Sdk/OAuth2GrantType.cs index c3b0329..546b6a7 100644 --- a/src/ServerlessWorkflow.Sdk/Models/OAuth2GrantType.cs +++ b/src/ServerlessWorkflow.Sdk/OAuth2GrantType.cs @@ -44,6 +44,11 @@ public static class OAuth2GrantType /// public const string TokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange"; + /// + /// Gets an containing the OAUTH2 grant types supported by default + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing the OAUTH2 grant types supported by default /// diff --git a/src/ServerlessWorkflow.Sdk/Models/OAuth2RequestEncoding.cs b/src/ServerlessWorkflow.Sdk/OAuth2RequestEncoding.cs similarity index 88% rename from src/ServerlessWorkflow.Sdk/Models/OAuth2RequestEncoding.cs rename to src/ServerlessWorkflow.Sdk/OAuth2RequestEncoding.cs index 88287c8..49b7cc5 100644 --- a/src/ServerlessWorkflow.Sdk/Models/OAuth2RequestEncoding.cs +++ b/src/ServerlessWorkflow.Sdk/OAuth2RequestEncoding.cs @@ -27,6 +27,11 @@ public static class OAuth2RequestEncoding /// public const string Json = "application/json"; + /// + /// Gets an containing all supported values + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing all supported values /// @@ -37,4 +42,4 @@ public static IEnumerable AsEnumerable() yield return Json; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Polyfill.CodeAnalysis.cs b/src/ServerlessWorkflow.Sdk/Polyfill.CodeAnalysis.cs new file mode 100644 index 0000000..a481e9d --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Polyfill.CodeAnalysis.cs @@ -0,0 +1,18 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Constructor, Inherited = false)] +internal sealed class SetsRequiredMembersAttribute : Attribute { } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Polyfill.Compiler.cs b/src/ServerlessWorkflow.Sdk/Polyfill.Compiler.cs new file mode 100644 index 0000000..bd372aa --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Polyfill.Compiler.cs @@ -0,0 +1,24 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +#pragma warning disable CS9113 // Parameter is unread. +namespace System.Runtime.CompilerServices; + +internal static class IsExternalInit { } + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] +internal sealed class RequiredMemberAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] +internal sealed class CompilerFeatureRequiredAttribute(string featureName) : Attribute { } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Models/ProcessType.cs b/src/ServerlessWorkflow.Sdk/ProcessType.cs similarity index 88% rename from src/ServerlessWorkflow.Sdk/Models/ProcessType.cs rename to src/ServerlessWorkflow.Sdk/ProcessType.cs index 3b664f2..cfc5bf0 100644 --- a/src/ServerlessWorkflow.Sdk/Models/ProcessType.cs +++ b/src/ServerlessWorkflow.Sdk/ProcessType.cs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Models; +namespace ServerlessWorkflow.Sdk; /// /// Exposes process types @@ -40,6 +40,11 @@ public static class ProcessType /// public const string Extension = "extension"; + /// + /// Gets an containing all supported process types + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing all supported process types /// @@ -52,4 +57,4 @@ public static IEnumerable AsEnumerable() yield return Workflow; } -} +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Properties/PublishProfiles/FolderProfile.pubxml b/src/ServerlessWorkflow.Sdk/Properties/PublishProfiles/FolderProfile.pubxml deleted file mode 100644 index df5633a..0000000 --- a/src/ServerlessWorkflow.Sdk/Properties/PublishProfiles/FolderProfile.pubxml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Release - Any CPU - bin\Release\net8.0\publish\ - FileSystem - <_TargetId>Folder - - \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs b/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs deleted file mode 100644 index 814a7bb..0000000 --- a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.Designer.cs +++ /dev/null @@ -1,126 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace ServerlessWorkflow.Sdk.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class ValidationErrors { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ValidationErrors() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ServerlessWorkflow.Sdk.Properties.ValidationErrors", typeof(ValidationErrors).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// 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. - /// - internal static string UndefinedAuthenticationPolicy { - get { - return ResourceManager.GetString("UndefinedAuthenticationPolicy", resourceCulture); - } - } - - /// - /// 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. - /// - internal static string UndefinedFunction { - get { - return ResourceManager.GetString("UndefinedFunction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Undefined secret. - /// - internal static string UndefinedSecret { - get { - return ResourceManager.GetString("UndefinedSecret", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unreachable or undefined task. - /// - internal static string UndefinedTask { - get { - return ResourceManager.GetString("UndefinedTask", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Undefined timeout. - /// - internal static string UndefinedTimeout { - get { - return ResourceManager.GetString("UndefinedTimeout", resourceCulture); - } - } - } -} diff --git a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx b/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx deleted file mode 100644 index d1b7d5a..0000000 --- a/src/ServerlessWorkflow.Sdk/Properties/ValidationErrors.resx +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Undefined authentication policy - - - Undefined function - - - Undefined secret - - - Unreachable or undefined task - - - 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/RuntimeExpressionEvaluationMode.cs b/src/ServerlessWorkflow.Sdk/RuntimeExpressionEvaluationMode.cs index 60914ca..8c60d84 100644 --- a/src/ServerlessWorkflow.Sdk/RuntimeExpressionEvaluationMode.cs +++ b/src/ServerlessWorkflow.Sdk/RuntimeExpressionEvaluationMode.cs @@ -28,4 +28,19 @@ public static class RuntimeExpressionEvaluationMode /// public const string Loose = "loose"; + /// + /// Gets an that contains all supported runtime expression evaluation modes + /// + public static readonly IEnumerable All = AsEnumerable(); + + /// + /// Gets an that contains all supported runtime expression evaluation modes + /// + /// A new that contains all supported runtime expression evaluation modes + public static IEnumerable AsEnumerable() + { + yield return Strict; + yield return Loose; + } + } \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/RuntimeExpressions.cs b/src/ServerlessWorkflow.Sdk/RuntimeExpressions.cs index 138afa9..3d267a1 100644 --- a/src/ServerlessWorkflow.Sdk/RuntimeExpressions.cs +++ b/src/ServerlessWorkflow.Sdk/RuntimeExpressions.cs @@ -108,4 +108,4 @@ public static IEnumerable AsEnumerable() } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Models/SchemaFormat.cs b/src/ServerlessWorkflow.Sdk/SchemaFormat.cs similarity index 86% rename from src/ServerlessWorkflow.Sdk/Models/SchemaFormat.cs rename to src/ServerlessWorkflow.Sdk/SchemaFormat.cs index 5da69df..539b687 100644 --- a/src/ServerlessWorkflow.Sdk/Models/SchemaFormat.cs +++ b/src/ServerlessWorkflow.Sdk/SchemaFormat.cs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Models; +namespace ServerlessWorkflow.Sdk; /// /// Exposes all schema formats supported by default by ServerlessWorkflow @@ -32,6 +32,11 @@ public static class SchemaFormat /// public const string Xml = "xml"; + /// + /// Gets an containing all default schema format + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets an containing all default schema format /// @@ -43,4 +48,4 @@ public static IEnumerable AsEnumerable() yield return Xml; } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Json/JsonSerializationContext.cs b/src/ServerlessWorkflow.Sdk/Serialization/Json/JsonSerializationContext.cs new file mode 100644 index 0000000..ee7c618 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Serialization/Json/JsonSerializationContext.cs @@ -0,0 +1,143 @@ +// 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.Serialization.Json; + +/// +/// Represents the JSON serialization context for the Serverless Workflow SDK, providing configuration and metadata for serializing and deserializing all relevant types within the SDK to and from JSON format. +/// +[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(AsyncApiCallDefinition))] +[JsonSerializable(typeof(AsyncApiMessageDefinition))] +[JsonSerializable(typeof(AsyncApiSubscriptionDefinition))] +[JsonSerializable(typeof(AsyncApiSubscriptionLifetimeDefinition))] +[JsonSerializable(typeof(AuthenticationPolicyDefinition))] +[JsonSerializable(typeof(AuthenticationSchemeDefinition))] +[JsonSerializable(typeof(BackoffDefinition))] +[JsonSerializable(typeof(BackoffStrategyDefinition))] +[JsonSerializable(typeof(BasicAuthenticationSchemeDefinition))] +[JsonSerializable(typeof(BearerAuthenticationSchemeDefinition))] +[JsonSerializable(typeof(BranchingDefinition))] +[JsonSerializable(typeof(CatalogDefinition))] +[JsonSerializable(typeof(CallTaskDefinition))] +[JsonSerializable(typeof(CertificateAuthenticationSchemeDefinition))] +[JsonSerializable(typeof(ComponentDefinition))] +[JsonSerializable(typeof(ComponentDefinitionCollection))] +[JsonSerializable(typeof(ContainerLifetimeDefinition))] +[JsonSerializable(typeof(ContainerProcessDefinition))] +[JsonSerializable(typeof(ConstantBackoffDefinition))] +[JsonSerializable(typeof(CorrelationKeyDefinition))] +[JsonSerializable(typeof(DateTimeDescriptor))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(DigestAuthenticationSchemeDefinition))] +[JsonSerializable(typeof(DoTaskDefinition))] +[JsonSerializable(typeof(Duration))] +[JsonSerializable(typeof(EmitTaskDefinition))] +[JsonSerializable(typeof(EndpointDefinition))] +[JsonSerializable(typeof(Epoch))] +[JsonSerializable(typeof(Error))] +[JsonSerializable(typeof(ErrorCatcherDefinition))] +[JsonSerializable(typeof(ErrorDefinition))] +[JsonSerializable(typeof(ErrorFilterDefinition))] +[JsonSerializable(typeof(EventConsumptionStrategyDefinition))] +[JsonSerializable(typeof(EventDefinition))] +[JsonSerializable(typeof(EventEmissionDefinition))] +[JsonSerializable(typeof(EventFilterDefinition))] +[JsonSerializable(typeof(ExponentialBackoffDefinition))] +[JsonSerializable(typeof(ExtensionDefinition))] +[JsonSerializable(typeof(ExtensionTaskDefinition))] +[JsonSerializable(typeof(ExternalResourceDefinition))] +[JsonSerializable(typeof(ForkTaskDefinition))] +[JsonSerializable(typeof(ForLoopDefinition))] +[JsonSerializable(typeof(ForTaskDefinition))] +[JsonSerializable(typeof(GrpcCallDefinition))] +[JsonSerializable(typeof(GrpcServiceDefinition))] +[JsonSerializable(typeof(HttpCallDefinition))] +[JsonSerializable(typeof(HttpRequest))] +[JsonSerializable(typeof(HttpResponse))] +[JsonSerializable(typeof(IDictionary))] +[JsonSerializable(typeof(IDictionary))] +[JsonSerializable(typeof(InputDataModelDefinition))] +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(JitterDefinition))] +[JsonSerializable(typeof(JsonArray))] +[JsonSerializable(typeof(JsonNode))] +[JsonSerializable(typeof(JsonObject))] +[JsonSerializable(typeof(JsonValue))] +[JsonSerializable(typeof(LinearBackoffDefinition))] +[JsonSerializable(typeof(ListenerDefinition))] +[JsonSerializable(typeof(ListenTaskDefinition))] +[JsonSerializable(typeof(OAuth2AuthenticationClientDefinition))] +[JsonSerializable(typeof(OAuth2AuthenticationEndpointsDefinition))] +[JsonSerializable(typeof(OAuth2AuthenticationRequestDefinition))] +[JsonSerializable(typeof(OAuth2AuthenticationSchemeDefinition))] +[JsonSerializable(typeof(OAuth2AuthenticationSchemeDefinitionBase))] +[JsonSerializable(typeof(OAuth2TokenDefinition))] +[JsonSerializable(typeof(OpenApiCallDefinition))] +[JsonSerializable(typeof(OpenIDConnectSchemeDefinition))] +[JsonSerializable(typeof(OutputDataModelDefinition))] +[JsonSerializable(typeof(ProcessDefinition))] +[JsonSerializable(typeof(ProcessTypeDefinition))] +[JsonSerializable(typeof(RaiseErrorDefinition))] +[JsonSerializable(typeof(RaiseTaskDefinition))] +[JsonSerializable(typeof(ReferenceableComponentDefinition))] +[JsonSerializable(typeof(RetryAttemptLimitDefinition))] +[JsonSerializable(typeof(RetryingTaskEvent))] +[JsonSerializable(typeof(RetryPolicyDefinition))] +[JsonSerializable(typeof(RetryPolicyLimitDefinition))] +[JsonSerializable(typeof(RunTaskDefinition))] +[JsonSerializable(typeof(RuntimeDescriptor))] +[JsonSerializable(typeof(RuntimeExpressionEvaluationConfiguration))] +[JsonSerializable(typeof(SchemaDefinition))] +[JsonSerializable(typeof(ScriptProcessDefinition))] +[JsonSerializable(typeof(SetTaskDefinition))] +[JsonSerializable(typeof(ShellProcessDefinition))] +[JsonSerializable(typeof(SubscriptionIteratorDefinition))] +[JsonSerializable(typeof(SwitchCaseDefinition))] +[JsonSerializable(typeof(SwitchTaskDefinition))] +[JsonSerializable(typeof(TaskCancelledEvent))] +[JsonSerializable(typeof(TaskCompletedEvent))] +[JsonSerializable(typeof(TaskCreatedEvent))] +[JsonSerializable(typeof(TaskEndedEvent))] +[JsonSerializable(typeof(TaskFaultedEvent))] +[JsonSerializable(typeof(TaskResumedEvent))] +[JsonSerializable(typeof(TaskSkippedEvent))] +[JsonSerializable(typeof(TaskStartedEvent))] +[JsonSerializable(typeof(TaskSuspendedEvent))] +[JsonSerializable(typeof(TaskDefinition))] +[JsonSerializable(typeof(TaskDescriptor))] +[JsonSerializable(typeof(TimeoutDefinition))] +[JsonSerializable(typeof(TryTaskDefinition))] +[JsonSerializable(typeof(WaitTaskDefinition))] +[JsonSerializable(typeof(WorkflowCancelledEvent))] +[JsonSerializable(typeof(WorkflowCompletedEvent))] +[JsonSerializable(typeof(WorkflowCorrelationCompletedEvent))] +[JsonSerializable(typeof(WorkflowCorrelationStartedEvent))] +[JsonSerializable(typeof(WorkflowDefinition))] +[JsonSerializable(typeof(WorkflowEndedEvent))] +[JsonSerializable(typeof(WorkflowFaultedEvent))] +[JsonSerializable(typeof(WorkflowResumedEvent))] +[JsonSerializable(typeof(WorkflowStartedEvent))] +[JsonSerializable(typeof(WorkflowSuspendedEvent))] +[JsonSerializable(typeof(WorkflowDefinitionMetadata))] +[JsonSerializable(typeof(WorkflowDefinitionReference))] +[JsonSerializable(typeof(WorkflowDescriptor))] +[JsonSerializable(typeof(WorkflowProcessDefinition))] +[JsonSerializable(typeof(WorkflowScheduleDefinition))] +public partial class JsonSerializationContext + : JsonSerializerContext +{ + + + +} diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverter.cs index a9d793d..1865bad 100644 --- a/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverter.cs +++ b/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverter.cs @@ -11,37 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Text.Json; - namespace ServerlessWorkflow.Sdk.Serialization.Json; -/// -/// Represents the used to create -/// -public class MapEntryJsonConverter - : JsonConverterFactory -{ - - /// - public override bool CanConvert(Type typeToConvert) => typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(MapEntry<,>); - - /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - var keyType = typeToConvert.GetGenericArguments()[0]; - var valueType = typeToConvert.GetGenericArguments()[1]; - var converterType = typeof(MapEntryJsonConverter<,>).MakeGenericType(keyType, valueType); - return (JsonConverter)Activator.CreateInstance(converterType)!; - } - -} - /// /// Represents the used to write and read instances /// /// The type of the key /// The type of the value -public class MapEntryJsonConverter : JsonConverter> where TKey : notnull +public class MapEntryJsonConverter + : JsonConverter> where TKey : notnull { /// @@ -67,4 +45,4 @@ public override void Write(Utf8JsonWriter writer, MapEntry? value, writer.WriteEndObject(); } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverterFactory.cs b/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverterFactory.cs new file mode 100644 index 0000000..01cf158 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Serialization/Json/MapEntryJsonConverterFactory.cs @@ -0,0 +1,35 @@ +// 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.Serialization.Json; + +/// +/// Represents the used to create +/// +public class MapEntryJsonConverterFactory + : JsonConverterFactory +{ + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(MapEntry<,>); + + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var keyType = typeToConvert.GetGenericArguments()[0]; + var valueType = typeToConvert.GetGenericArguments()[1]; + var converterType = typeof(MapEntryJsonConverter<,>).MakeGenericType(keyType, valueType); + return (JsonConverter)Activator.CreateInstance(converterType)!; + } + +} diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Json/OneOfConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Json/OneOfConverter.cs deleted file mode 100644 index 8769693..0000000 --- a/src/ServerlessWorkflow.Sdk/Serialization/Json/OneOfConverter.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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 ServerlessWorkflow.Sdk.Models; -using System.Collections.Concurrent; -using System.Text.Json; - -namespace ServerlessWorkflow.Sdk.Serialization.Json; - -/// -/// Represents the used to serialize/deserialize to/from instances -/// -public class OneOfConverter - : JsonConverterFactory -{ - - static readonly ConcurrentDictionary ConverterCache = new(); - - /// - public override bool CanConvert(Type typeToConvert) - { - if (!typeToConvert.IsGenericType) return false; - var genericType = typeToConvert.GetGenericTypeDefinition(); - return genericType == typeof(OneOf<,>); - } - - /// - public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - return ConverterCache.GetOrAdd(typeToConvert, (type) => - { - var typeArgs = type.GetGenericArguments(); - var converterType = typeof(OneOfConverterInner<,>).MakeGenericType(typeArgs); - return (JsonConverter?)Activator.CreateInstance(converterType)!; - }); - } - - class OneOfConverterInner : JsonConverter> - { - - /// - public override OneOf? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) return null; - var document = JsonDocument.ParseValue(ref reader); - var rootElement = document.RootElement; - try - { - var value1 = JsonSerializer.Deserialize(rootElement.GetRawText(), options); - if (value1 != null) return new OneOf(value1); - } - catch (JsonException) { } - try - { - var value2 = JsonSerializer.Deserialize(rootElement.GetRawText(), options); - if (value2 != null) return new OneOf(value2); - } - catch (JsonException) { throw new JsonException($"Cannot deserialize {rootElement.GetRawText()} as either {typeof(T1).Name} or {typeof(T2).Name}"); } - throw new JsonException("Unexpected error during deserialization."); - } - - public override void Write(Utf8JsonWriter writer, OneOf value, JsonSerializerOptions options) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - switch (value.TypeIndex) - { - case 1: - JsonSerializer.Serialize(writer, value.T1Value, options); - break; - case 2: - JsonSerializer.Serialize(writer, value.T2Value, options); - break; - default: - throw new JsonException("Invalid index value."); - } - } - - } - -} - diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Json/OneOfJsonConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Json/OneOfJsonConverter.cs new file mode 100644 index 0000000..ec477bd --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/Serialization/Json/OneOfJsonConverter.cs @@ -0,0 +1,71 @@ +// 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.Serialization.Json; + +/// +/// Represents a JSON converter for types. +/// +/// The first possible type. +/// The second possible type. +public sealed class OneOfJsonConverter + : JsonConverter> +{ + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(OneOf) || typeToConvert == typeof(T1) || typeToConvert == typeof(T2); + + /// + public override OneOf Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var doc = JsonDocument.ParseValue(ref reader); + var raw = doc.RootElement.GetRawText(); + if (TryDeserialize(raw, options, out var t1)) return new OneOf(t1!); + if (TryDeserialize(raw, options, out var t2)) return new OneOf(t2!); + throw new JsonException($"Value does not match either {typeof(T1).Name} or {typeof(T2).Name}."); + } + + /// + public override void Write(Utf8JsonWriter writer, OneOf value, JsonSerializerOptions options) + { + value.Switch + ( + a => + { + var typeInfo = options.GetTypeInfo(typeof(T1)) ?? throw new JsonException($"Missing JsonTypeInfo for {typeof(T1).FullName}."); + JsonSerializer.Serialize(writer, a, typeInfo); + }, + b => + { + var typeInfo = options.GetTypeInfo(typeof(T2)) ?? throw new JsonException($"Missing JsonTypeInfo for {typeof(T2).FullName}."); + JsonSerializer.Serialize(writer, b, typeInfo); + } + ); + } + + static bool TryDeserialize(string json, JsonSerializerOptions options, out T? value) + { + try + { + var typeInfo = options.GetTypeInfo(typeof(T)) ?? throw new JsonException($"Missing JsonTypeInfo for {typeof(T).FullName}."); + value = (T?)JsonSerializer.Deserialize(json, typeInfo); + return value is not null || json == "null"; + } + catch + { + value = default; + return false; + } + } + +} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Json/TaskDefinitionJsonConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Json/TaskDefinitionJsonConverter.cs index f0b136d..728ac53 100644 --- a/src/ServerlessWorkflow.Sdk/Serialization/Json/TaskDefinitionJsonConverter.cs +++ b/src/ServerlessWorkflow.Sdk/Serialization/Json/TaskDefinitionJsonConverter.cs @@ -11,41 +11,58 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Models; -using ServerlessWorkflow.Sdk.Models.Tasks; -using System.Text.Json; - namespace ServerlessWorkflow.Sdk.Serialization.Json; /// -/// Represents the used to serialize and deserialize s to/from JSON +/// Represents a JSON converter for objects, responsible for serializing and deserializing instances of and its derived types to and from JSON format. /// -public class TaskDefinitionJsonConverter +public sealed class TaskDefinitionJsonConverter : JsonConverter { /// - public override TaskDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskDefinition? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("Expected start of object"); + var namingPolicy = options.PropertyNamingPolicy ?? JsonNamingPolicy.CamelCase; using var document = JsonDocument.ParseValue(ref reader); var root = document.RootElement; - if (root.TryGetProperty(nameof(CallTaskDefinition.Call).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(EmitTaskDefinition.Emit).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(ForTaskDefinition.For).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(ForkTaskDefinition.Fork).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(ListenTaskDefinition.Listen).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(RaiseTaskDefinition.Raise).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(RunTaskDefinition.Run).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(SetTaskDefinition.Set).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(SwitchTaskDefinition.Switch).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(TryTaskDefinition.Try).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(WaitTaskDefinition.Wait).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else if (root.TryGetProperty(nameof(DoTaskDefinition.Do).ToCamelCase(), out _)) return JsonSerializer.Deserialize(root.GetRawText(), options)!; - else return JsonSerializer.Deserialize(root.GetRawText(), options)!; + if (root.ValueKind != JsonValueKind.Object) throw new JsonException($"Expected a JSON object to deserialize a {nameof(TaskDefinition)}."); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(CallTaskDefinition.Call)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.CallTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(EmitTaskDefinition.Emit)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.EmitTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(ForkTaskDefinition.Fork)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.ForkTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(ForTaskDefinition.For)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.ForTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(DoTaskDefinition.Do)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.DoTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(ListenTaskDefinition.Listen)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.ListenTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(RaiseTaskDefinition.Raise)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.RaiseTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(RunTaskDefinition.Run)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.RunTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(SetTaskDefinition.Set)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.SetTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(SwitchTaskDefinition.Switch)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.SwitchTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(TryTaskDefinition.Try)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.TryTaskDefinition); + if (root.TryGetProperty(namingPolicy.ConvertName(nameof(WaitTaskDefinition.Wait)), out var _)) return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.WaitTaskDefinition); + return JsonSerializer.Deserialize(root, JsonSerializationContext.Default.ExtensionTaskDefinition); } /// - public override void Write(Utf8JsonWriter writer, TaskDefinition value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options); + public override void Write(Utf8JsonWriter writer, TaskDefinition value, JsonSerializerOptions options) + { + var json = value switch + { + CallTaskDefinition callTaskDefinition => JsonSerializer.Serialize(callTaskDefinition, JsonSerializationContext.Default.CallTaskDefinition), + DoTaskDefinition doTaskDefinition => JsonSerializer.Serialize(doTaskDefinition, JsonSerializationContext.Default.DoTaskDefinition), + EmitTaskDefinition emitTaskDefinition => JsonSerializer.Serialize(emitTaskDefinition, JsonSerializationContext.Default.EmitTaskDefinition), + ExtensionTaskDefinition extensionTaskDefinition => JsonSerializer.Serialize(extensionTaskDefinition, JsonSerializationContext.Default.ExtensionTaskDefinition), + ForkTaskDefinition forkTaskDefinition => JsonSerializer.Serialize(forkTaskDefinition, JsonSerializationContext.Default.ForkTaskDefinition), + ForTaskDefinition forTaskDefinition => JsonSerializer.Serialize(forTaskDefinition, JsonSerializationContext.Default.ForTaskDefinition), + ListenTaskDefinition listenTaskDefinition => JsonSerializer.Serialize(listenTaskDefinition, JsonSerializationContext.Default.ListenTaskDefinition), + RaiseTaskDefinition raiseTaskDefinition => JsonSerializer.Serialize(raiseTaskDefinition, JsonSerializationContext.Default.RaiseTaskDefinition), + RunTaskDefinition runTaskDefinition => JsonSerializer.Serialize(runTaskDefinition, JsonSerializationContext.Default.RunTaskDefinition), + SetTaskDefinition setTaskDefinition => JsonSerializer.Serialize(setTaskDefinition, JsonSerializationContext.Default.SetTaskDefinition), + SwitchTaskDefinition switchTaskDefinition => JsonSerializer.Serialize(switchTaskDefinition, JsonSerializationContext.Default.SwitchTaskDefinition), + TryTaskDefinition tryTaskDefinition => JsonSerializer.Serialize(tryTaskDefinition, JsonSerializationContext.Default.TryTaskDefinition), + WaitTaskDefinition waitTaskDefinition => JsonSerializer.Serialize(waitTaskDefinition, JsonSerializationContext.Default.WaitTaskDefinition), + _ => throw new NotSupportedException($"The type {value.GetType().FullName} is not supported for JSON serialization.") + }; + writer.WriteRawValue(json); + } -} \ No newline at end of file +} diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/MapEntryYamlConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Yaml/MapEntryYamlConverter.cs deleted file mode 100644 index e55c783..0000000 --- a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/MapEntryYamlConverter.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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 System.Text; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; - -namespace ServerlessWorkflow.Sdk.Serialization.Yaml; - -/// -/// Represents the service used to serialize/deserialize values to/from YAML -/// -/// A function used to create a new -/// A function used to create a new -public class MapEntryYamlConverter(Func serializerFactory, Func deserializerFactory) - : IYamlTypeConverter -{ - - /// - public virtual bool Accepts(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(MapEntry<,>); - - /// - public virtual object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) => this.CreateGenericConverter(type).ReadYaml(parser, type, rootDeserializer); - - /// - public virtual void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer rootSerializer) => this.CreateGenericConverter(type).WriteYaml(emitter, value, type, rootSerializer); - - /// - /// Creates a new generic - /// - /// The type to type to create a new for - /// - protected virtual IYamlTypeConverter CreateGenericConverter(Type type) - { - var typeArguments = type.GetGenericArguments(); - var converterType = typeof(MapEntryConverter<,>).MakeGenericType(typeArguments); - return (IYamlTypeConverter)Activator.CreateInstance(converterType, serializerFactory(), deserializerFactory())!; - } - - class MapEntryConverter (ISerializer serializer, IDeserializer deserializer) - : IYamlTypeConverter - where TKey : notnull - { - - /// - public bool Accepts(Type type) => type == typeof(MapEntry); - - /// - public virtual object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) - { - parser.Consume(); - var key = deserializer.Deserialize(parser); - var value = deserializer.Deserialize(parser); - parser.Consume(); - return new MapEntry(key, value); - } - - /// - public virtual void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer rootSerializer) - { - if (value == null) - { - emitter.Emit(new Scalar(null, null, string.Empty)); - return; - } - var entry = (MapEntry)value; - emitter.Emit(new MappingStart(null, null, true, MappingStyle.Block)); - var yaml = serializer.Serialize(entry.Key).Trim(); - using var keyStream = new MemoryStream(Encoding.UTF8.GetBytes(yaml)); - using var keyStreamReader = new StreamReader(keyStream); - var parser = new Parser(keyStreamReader); - while (parser.MoveNext()) - { - if (parser.Current == null || parser.Current is DocumentEnd) break; - if (parser.Current is StreamStart || parser.Current is DocumentStart) continue; - emitter.Emit(parser.Current); - } - yaml = serializer.Serialize(entry.Value).Trim(); - using var valueStream = new MemoryStream(Encoding.UTF8.GetBytes(yaml)); - using var valueStreamReader = new StreamReader(valueStream); - parser = new Parser(valueStreamReader); - while (parser.MoveNext()) - { - if (parser.Current == null || parser.Current is DocumentEnd) break; - if (parser.Current is StreamStart || parser.Current is DocumentStart) continue; - emitter.Emit(parser.Current); - } - emitter.Emit(new MappingEnd()); - } - - } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfConverter.cs deleted file mode 100644 index e6303bb..0000000 --- a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfConverter.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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 Neuroglia.Serialization.Json; -using Neuroglia.Serialization.Yaml; -using ServerlessWorkflow.Sdk.Models; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; - -namespace ServerlessWorkflow.Sdk.Serialization.Yaml; - -/// -/// Represents the used to serialize and deserialize instances -/// -public class OneOfConverter - : IYamlTypeConverter -{ - - /// - public virtual bool Accepts(Type type) => type.GetGenericType(typeof(OneOf<,>)) != null; - - /// - public virtual object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) => throw new NotImplementedException(); - - /// - public virtual void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer rootSerializer) - { - if (value == null || value is not IOneOf oneOf) - { - emitter.Emit(new Scalar(null, null, string.Empty)); - return; - } - var toSerialize = oneOf.GetValue(); - if (toSerialize == null) - { - emitter.Emit(new Scalar(null, null, string.Empty)); - return; - } - var jsonNode = JsonSerializer.Default.SerializeToNode(toSerialize); - new JsonNodeTypeConverter().WriteYaml(emitter, jsonNode, toSerialize.GetType(), rootSerializer); - } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfNodeDeserializer.cs b/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfNodeDeserializer.cs deleted file mode 100644 index 07528e1..0000000 --- a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfNodeDeserializer.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 ServerlessWorkflow.Sdk.Models; -using Neuroglia.Serialization.Json; -using YamlDotNet.Core; - -namespace ServerlessWorkflow.Sdk.Serialization.Yaml; - -/// -/// Represents the service used to deserialize s from YAML -/// -/// The inner -public class OneOfNodeDeserializer(INodeDeserializer inner) - : INodeDeserializer -{ - - /// - /// Gets the inner - /// - protected INodeDeserializer Inner { get; } = inner; - - /// - public virtual bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) - { - if (!typeof(IOneOf).IsAssignableFrom(expectedType)) return this.Inner.Deserialize(reader, expectedType, nestedObjectDeserializer, out value, rootDeserializer); - if (!this.Inner.Deserialize(reader, typeof(Dictionary), nestedObjectDeserializer, out value, rootDeserializer)) return false; - value = JsonSerializer.Default.Deserialize(JsonSerializer.Default.SerializeToText(value!), expectedType); - return true; - } - -} diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfScalarDeserializer.cs b/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfScalarDeserializer.cs deleted file mode 100644 index 2da9549..0000000 --- a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/OneOfScalarDeserializer.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 ServerlessWorkflow.Sdk.Models; -using Neuroglia.Serialization.Json; -using YamlDotNet.Core; - -namespace ServerlessWorkflow.Sdk.Serialization.Yaml; - -/// -/// Represents the service used to deserialize s from YAML -/// -/// The inner -public class OneOfScalarDeserializer(INodeDeserializer inner) - : INodeDeserializer -{ - - /// - /// Gets the inner - /// - protected INodeDeserializer Inner { get; } = inner; - - /// - public virtual bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) - { - if (!typeof(IOneOf).IsAssignableFrom(expectedType)) return this.Inner.Deserialize(reader, expectedType, nestedObjectDeserializer, out value, rootDeserializer); - if (!this.Inner.Deserialize(reader, typeof(object), nestedObjectDeserializer, out value, rootDeserializer)) return false; - value = JsonSerializer.Default.Deserialize(JsonSerializer.Default.SerializeToText(value!), expectedType); - return true; - } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/TaskDefinitionYamlConverter.cs b/src/ServerlessWorkflow.Sdk/Serialization/Yaml/TaskDefinitionYamlConverter.cs deleted file mode 100644 index 7bc2e22..0000000 --- a/src/ServerlessWorkflow.Sdk/Serialization/Yaml/TaskDefinitionYamlConverter.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 ServerlessWorkflow.Sdk.Models; -using Neuroglia.Serialization.Json; -using YamlDotNet.Core; - -namespace ServerlessWorkflow.Sdk.Serialization.Yaml; - -/// -/// Represents the service used to deserialize s from YAML -/// -/// -/// Initializes a new -/// -/// The inner -public class TaskDefinitionYamlDeserializer(INodeDeserializer inner) - : INodeDeserializer -{ - - /// - /// Gets the inner - /// - protected INodeDeserializer Inner { get; } = inner; - - /// - public virtual bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) - { - if (!typeof(TaskDefinition).IsAssignableFrom(expectedType)) return this.Inner.Deserialize(reader, expectedType, nestedObjectDeserializer, out value, rootDeserializer); - if (!this.Inner.Deserialize(reader, typeof(Dictionary), nestedObjectDeserializer, out value, rootDeserializer)) return false; - value = JsonSerializer.Default.Deserialize(JsonSerializer.Default.SerializeToText(value!)); - return true; - } - -} diff --git a/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj b/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj index d01894f..a8c72e9 100644 --- a/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj +++ b/src/ServerlessWorkflow.Sdk/ServerlessWorkflow.Sdk.csproj @@ -1,10 +1,12 @@ - + - net8.0;net9.0 + netstandard2.0 + latest enable enable - 1.0.1 + true + 1.0.2 $(VersionPrefix) $(VersionPrefix) en @@ -16,7 +18,7 @@ serverless-workflow;serverless;workflow;dsl;sdk true Apache-2.0 - readme.md + README.md Copyright © 2024-Present The Serverless Workflow Authors. All rights reserved. https://github.com/serverlessworkflow/sdk-net https://github.com/serverlessworkflow/sdk-net @@ -25,33 +27,17 @@ - - True - \ - + + + + - - - - - - - - - - True - True - ValidationErrors.resx - - - - - - ResXFileCodeGenerator - ValidationErrors.Designer.cs - + + True + \ + - + diff --git a/src/ServerlessWorkflow.Sdk/ServerlessWorkflowSpecificationDefaults.cs b/src/ServerlessWorkflow.Sdk/ServerlessWorkflowSpecificationDefaults.cs new file mode 100644 index 0000000..74b5822 --- /dev/null +++ b/src/ServerlessWorkflow.Sdk/ServerlessWorkflowSpecificationDefaults.cs @@ -0,0 +1,332 @@ +// 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; + +/// +/// Exposes the default values for the Serverless Workflow Specification +/// +public static class ServerlessWorkflowSpecificationDefaults +{ + + /// + /// Gets the current version of the Serverless Workflow Specification + /// + public const string Version = "1.1.0"; + + /// + /// Exposes constants about the cloud events defined by the Serverless Workflow Specification + /// + public static class CloudEvents + { + + /// + /// Gets the type prefix for all cloud events defined by the Serverless Workflow Specification + /// + public const string TypePrefix = "io.serverlessworkflow."; + + /// + /// Exposes constants about workflow-related cloud events + /// + public static class Workflow + { + + /// + /// Gets the type prefix for all workflow-related cloud events produced by the Serverless Workflow Specification + /// + public const string TypePrefix = CloudEvents.TypePrefix + "workflow."; + + /// + /// Exposes constants about the cloud event used to notify that a workflow started + /// + public static class Started + { + + const string TypeName = "started"; + /// + /// Gets the type of the workflow started cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a workflow has been suspended + /// + public static class Suspended + { + + const string TypeName = "suspended"; + /// + /// Gets the type of the workflow suspended cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a workflow has been resumed + /// + public static class Resumed + { + + const string TypeName = "resumed"; + /// + /// Gets the type of the workflow resumed cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a workflow has started correlating events + /// + public static class CorrelationStarted + { + + const string TypeName = "correlation-started"; + /// + /// Gets the type of the workflow correlation started cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a workflow has finished correlating events + /// + public static class CorrelationCompleted + { + + const string TypeName = "correlation-completed"; + /// + /// Gets the type of the workflow correlation completed cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a workflow has faulted + /// + public static class Faulted + { + + const string TypeName = "faulted"; + /// + /// Gets the type of the workflow faulted cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a workflow ran to completion + /// + public static class Completed + { + + const string TypeName = "completed"; + /// + /// Gets the type of the workflow completed cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that the execution of a workflow has been cancelled + /// + public static class Cancelled + { + + const string TypeName = "cancelled"; + /// + /// Gets the type of the workflow cancelled cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that the execution of a workflow has ended + /// + public static class Ended + { + + const string TypeName = "ended"; + /// + /// Gets the type of the workflow ended cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + } + + /// + /// Exposes constants about task-related cloud events + /// + public static class Task + { + + /// + /// Gets the type prefix for all task-related cloud events produced by the Serverless Workflow Specification + /// + public const string TypePrefix = CloudEvents.TypePrefix + "task."; + + /// + /// Exposes constants about the cloud event used to notify that a task has been created + /// + public static class Created + { + + const string TypeName = "created"; + /// + /// Gets the type of the task created cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task started + /// + public static class Started + { + + const string TypeName = "started"; + /// + /// Gets the type of the task started cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task has been suspended + /// + public static class Suspended + { + + const string TypeName = "suspended"; + /// + /// Gets the type of the task suspended cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task has been resumed + /// + public static class Resumed + { + + const string TypeName = "resumed"; + /// + /// Gets the type of the task resumed cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task is being retried + /// + public static class Retrying + { + + const string TypeName = "retrying"; + /// + /// Gets the type of the retrying task cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task has been skipped + /// + public static class Skipped + { + + const string TypeName = "skipped"; + /// + /// Gets the type of the task skipped cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task has faulted + /// + public static class Faulted + { + + const string TypeName = "faulted"; + /// + /// Gets the type of the task faulted cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that a task ran to completion + /// + public static class Completed + { + + const string TypeName = "completed"; + /// + /// Gets the type of the task completed cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that the execution of a task has been cancelled + /// + public static class Cancelled + { + + const string TypeName = "cancelled"; + /// + /// Gets the type of the task cancelled cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + /// + /// Exposes constants about the cloud event used to notify that the execution of a task has ended + /// + public static class Ended + { + + const string TypeName = "ended"; + /// + /// Gets the type of the task ended cloud event version 1 + /// + public const string v1 = TypePrefix + TypeName + ".v1"; + + } + + } + + } + +} diff --git a/src/ServerlessWorkflow.Sdk/Models/TaskType.cs b/src/ServerlessWorkflow.Sdk/TaskType.cs similarity index 92% rename from src/ServerlessWorkflow.Sdk/Models/TaskType.cs rename to src/ServerlessWorkflow.Sdk/TaskType.cs index e10f3f0..9616bca 100644 --- a/src/ServerlessWorkflow.Sdk/Models/TaskType.cs +++ b/src/ServerlessWorkflow.Sdk/TaskType.cs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace ServerlessWorkflow.Sdk.Models; +namespace ServerlessWorkflow.Sdk; /// /// Exposes task types @@ -72,6 +72,11 @@ public static class TaskType /// public const string Wait = "wait"; + /// + /// Gets an containing all supported task types + /// + public static readonly IEnumerable All = AsEnumerable(); + /// /// Gets a new containing all supported task types /// diff --git a/src/ServerlessWorkflow.Sdk/Usings.cs b/src/ServerlessWorkflow.Sdk/Usings.cs index a011d2a..0c5d9ae 100644 --- a/src/ServerlessWorkflow.Sdk/Usings.cs +++ b/src/ServerlessWorkflow.Sdk/Usings.cs @@ -11,8 +11,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +global using Json.Pointer; +global using ServerlessWorkflow.Sdk.Events.Tasks; +global using ServerlessWorkflow.Sdk.Events.Workflows; +global using ServerlessWorkflow.Sdk.Models; +global using ServerlessWorkflow.Sdk.Models.Authentication; +global using ServerlessWorkflow.Sdk.Models.Calls; +global using ServerlessWorkflow.Sdk.Models.Processes; +global using ServerlessWorkflow.Sdk.Models.Tasks; +global using ServerlessWorkflow.Sdk.Serialization.Json; +global using System.Collections; +global using System.ComponentModel; +global using System.ComponentModel.DataAnnotations; global using System.Runtime.Serialization; +global using System.Text.Json; +global using System.Text.Json.Nodes; global using System.Text.Json.Serialization; -global using YamlDotNet.Serialization; -global using System.ComponentModel.DataAnnotations; -global using Neuroglia; \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/AsyncApiCallDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/AsyncApiCallDefinitionValidator.cs deleted file mode 100644 index db36f32..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/AsyncApiCallDefinitionValidator.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Calls; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class AsyncApiCallDefinitionValidator - : AbstractValidator -{ - - /// - public AsyncApiCallDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(c => c.Authentication!) - .SetValidator(c => new AuthenticationPolicyDefinitionValidator(this.ServiceProvider, this.Components)) - .When(c => c.Authentication != null); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/AuthenticationPolicyDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/AuthenticationPolicyDefinitionValidator.cs deleted file mode 100644 index 213ec31..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/AuthenticationPolicyDefinitionValidator.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Properties; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class AuthenticationPolicyDefinitionValidator - : AbstractValidator -{ - - /// - public AuthenticationPolicyDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(auth => auth.Use!) - .Must(ReferenceAnExistingAuthentication) - .When(auth => !string.IsNullOrWhiteSpace(auth.Use)) - .WithMessage(ValidationErrors.UndefinedAuthenticationPolicy); - this.RuleFor(auth => auth.Basic!.Use!) - .Must(ReferenceAnExistingSecret) - .When(auth => !string.IsNullOrWhiteSpace(auth.Basic?.Use)) - .WithMessage(ValidationErrors.UndefinedSecret); - this.RuleFor(auth => auth.Bearer!.Use!) - .Must(ReferenceAnExistingSecret) - .When(auth => !string.IsNullOrWhiteSpace(auth.Bearer?.Use)) - .WithMessage(ValidationErrors.UndefinedSecret); - this.RuleFor(auth => auth.Certificate!.Use!) - .Must(ReferenceAnExistingSecret) - .When(auth => !string.IsNullOrWhiteSpace(auth.Certificate?.Use)) - .WithMessage(ValidationErrors.UndefinedSecret); - this.RuleFor(auth => auth.Digest!.Use!) - .Must(ReferenceAnExistingSecret) - .When(auth => !string.IsNullOrWhiteSpace(auth.Digest?.Use)) - .WithMessage(ValidationErrors.UndefinedSecret); - this.RuleFor(auth => auth.OAuth2!.Use!) - .Must(ReferenceAnExistingSecret) - .When(auth => !string.IsNullOrWhiteSpace(auth.OAuth2?.Use)) - .WithMessage(ValidationErrors.UndefinedSecret); - this.RuleFor(auth => auth.Oidc!.Use!) - .Must(ReferenceAnExistingSecret) - .When(auth => !string.IsNullOrWhiteSpace(auth.Oidc?.Use)) - .WithMessage(ValidationErrors.UndefinedSecret); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Determines whether or not the specified authentication exists - /// - /// The name of the authentication to check - /// A boolean indicating whether or not the specified authentication exists - protected virtual bool ReferenceAnExistingAuthentication(string name) - { - if (this.Components?.Authentications?.ContainsKey(name) == true) return true; - else return false; - } - - /// - /// Determines whether or not the specified secret exists - /// - /// The name of the secret to check - /// A boolean indicating whether or not the specified secret exists - protected virtual bool ReferenceAnExistingSecret(string name) - { - if (this.Components?.Secrets?.Contains(name) == true) return true; - else return false; - } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/AuthenticationPolicyKeyValuePairValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/AuthenticationPolicyKeyValuePairValidator.cs deleted file mode 100644 index 8a2b1ca..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/AuthenticationPolicyKeyValuePairValidator.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 AuthenticationPolicyKeyValuePairValidator - : AbstractValidator> -{ - - /// - public AuthenticationPolicyKeyValuePairValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(t => t.Value) - .Custom((value, context) => - { - var key = context.InstanceToValidate.Key; - var validator = new AuthenticationPolicyDefinitionValidator(serviceProvider, components); - 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; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs deleted file mode 100644 index 3304469..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/CallTaskDefinitionValidator.cs +++ /dev/null @@ -1,132 +0,0 @@ -// 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 Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; -using Semver; -using ServerlessWorkflow.Sdk.Models; -using ServerlessWorkflow.Sdk.Models.Calls; -using ServerlessWorkflow.Sdk.Models.Tasks; -using ServerlessWorkflow.Sdk.Properties; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class CallTaskDefinitionValidator - : AbstractValidator -{ - - /// - public CallTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(c => c.Call) - .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))!) - .SetValidator(c => new AsyncApiCallDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(c => c.Call == Function.Grpc, () => - { - this.RuleFor(c => (GrpcCallDefinition)this.JsonSerializer.Convert(c.With, typeof(GrpcCallDefinition))!) - .SetValidator(c => new GrpcCallDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(c => c.Call == Function.Http, () => - { - this.RuleFor(c => (HttpCallDefinition)this.JsonSerializer.Convert(c.With, typeof(HttpCallDefinition))!) - .SetValidator(c => new HttpCallDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(c => c.Call == Function.OpenApi, () => - { - this.RuleFor(c => (OpenApiCallDefinition)this.JsonSerializer.Convert(c.With, typeof(OpenApiCallDefinition))!) - .SetValidator(c => new OpenApiCallDefinitionValidator(this.ServiceProvider, this.Components)); - }); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the service used to serialize/deserialize data to/from JSON - /// - protected IJsonSerializer JsonSerializer => this.ServiceProvider.GetRequiredService(); - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Determines whether or not the specified function exists - /// - /// The name of the function to check - /// 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 deleted file mode 100644 index e37eca0..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/CatalogDefinitionValidator.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 deleted file mode 100644 index 99a726f..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/CatalogKeyValuePairValidator.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 deleted file mode 100644 index 2ff5d49..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/ComponentDefinitionCollectionValidator.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Properties; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class ComponentDefinitionCollectionValidator - : AbstractValidator -{ - - /// - 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)); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/DoTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/DoTaskDefinitionValidator.cs deleted file mode 100644 index 002c562..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/DoTaskDefinitionValidator.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Tasks; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class DoTaskDefinitionValidator - : AbstractValidator -{ - - /// - public DoTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleForEach(t => t.Do) - .SetValidator(t => new TaskMapEntryValidator(this.ServiceProvider, this.Components, t.Do.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/DslWorkflowDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/DslWorkflowDefinitionValidator.cs deleted file mode 100644 index fbbc4cc..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/DslWorkflowDefinitionValidator.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Properties; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class DslWorkflowDefinitionValidator - : AbstractValidator -{ - - /// - public DslWorkflowDefinitionValidator(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.RuleFor(w => w.Use) - .SetValidator(new ComponentDefinitionCollectionValidator(this.ServiceProvider)!); - this.RuleForEach(w => w.Do) - .SetValidator(w => new TaskMapEntryValidator(this.ServiceProvider, w.Use, w.Do.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); - this.RuleFor(w => w.TimeoutReference!) - .Must(ReferenceAnExistingTimeout) - .When(w => !string.IsNullOrWhiteSpace(w.TimeoutReference)) - .WithMessage(ValidationErrors.UndefinedTimeout); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Determines whether or not the specified retry policy is defined - /// - /// The workflow to check - /// The name of the timeout to check - /// A boolean indicating whether or not the specified retry policy is defined - protected virtual bool ReferenceAnExistingTimeout(WorkflowDefinition workflow, string name) => workflow.Use?.Timeouts?.ContainsKey(name) == true; - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/ForkTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/ForkTaskDefinitionValidator.cs deleted file mode 100644 index e2b8a94..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/ForkTaskDefinitionValidator.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Tasks; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class ForkTaskDefinitionValidator - : AbstractValidator -{ - - /// - public ForkTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleForEach(t => t.Fork.Branches) - .SetValidator(t => new TaskMapEntryValidator(this.ServiceProvider, this.Components, new Dictionary())); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/GrpcCallDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/GrpcCallDefinitionValidator.cs deleted file mode 100644 index a3bb1e4..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/GrpcCallDefinitionValidator.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Calls; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class GrpcCallDefinitionValidator - : AbstractValidator -{ - - /// - public GrpcCallDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(c => c.Service.Authentication!) - .SetValidator(c => new AuthenticationPolicyDefinitionValidator(this.ServiceProvider, this.Components)) - .When(c => c.Service.Authentication != null); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/HttpCallDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/HttpCallDefinitionValidator.cs deleted file mode 100644 index 7ca3005..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/HttpCallDefinitionValidator.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Calls; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class HttpCallDefinitionValidator - : AbstractValidator -{ - - /// - public HttpCallDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(c => c.Endpoint!.Authentication!) - .SetValidator(c => new AuthenticationPolicyDefinitionValidator(this.ServiceProvider, this.Components)) - .When(c => c.Endpoint?.Authentication != null); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/OpenApiCallDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/OpenApiCallDefinitionValidator.cs deleted file mode 100644 index 368690e..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/OpenApiCallDefinitionValidator.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Calls; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class OpenApiCallDefinitionValidator - : AbstractValidator -{ - - /// - public OpenApiCallDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(c => c.Authentication!) - .SetValidator(c => new AuthenticationPolicyDefinitionValidator(this.ServiceProvider, this.Components)) - .When(c => c.Authentication != null); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/RaiseTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/RaiseTaskDefinitionValidator.cs deleted file mode 100644 index 64fe7b7..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/RaiseTaskDefinitionValidator.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Tasks; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class RaiseTaskDefinitionValidator - : AbstractValidator -{ - - /// - public RaiseTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleFor(t => t.Raise.ErrorReference!) - .Must(ReferenceAnExistingError) - .When(t => !string.IsNullOrWhiteSpace(t.Raise.ErrorReference)); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Determines whether or not the specified error is defined - /// - /// The name of the error to check - /// A boolean indicating whether or not the specified error is defined - protected virtual bool ReferenceAnExistingError(string name) => this.Components?.Errors?.ContainsKey(name) == true; - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/SwitchCaseDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/SwitchCaseDefinitionValidator.cs deleted file mode 100644 index ee6c561..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/SwitchCaseDefinitionValidator.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Properties; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class SwitchCaseDefinitionValidator - : AbstractValidator> -{ - - /// - public SwitchCaseDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components, IDictionary? tasks) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.Tasks = tasks; - this.RuleFor(c => c.Value.Then) - .Must(ReferenceAnExistingTask) - .When(NotAFlowDirective) - .WithName(c => c.Key) - .WithMessage(ValidationErrors.UndefinedTask); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Gets a ke/definition mapping of all tasks in the scope of the task to validate - /// - protected IDictionary? Tasks { get; } - - /// - /// Determines whether or not the specified task is defined in the actual scope - /// - /// The name of the task to check - /// A boolean indicating whether or not the specified task is defined in the actual scope - protected virtual bool ReferenceAnExistingTask(string? name) => !string.IsNullOrWhiteSpace(name) && this.Tasks != null && this.Tasks.ContainsKey(name); - - /// - /// Determines whether or not the case's then is a flow directive - /// - /// The case to check - /// A boolean indicating whether or not the case's then is a flow directive - protected virtual bool NotAFlowDirective(MapEntry @case) - { - return @case.Value.Then switch - { - null or "" or FlowDirective.Continue or FlowDirective.End or FlowDirective.Exit => false, - _ => true - }; - } - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/SwitchTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/SwitchTaskDefinitionValidator.cs deleted file mode 100644 index e1c877d..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/SwitchTaskDefinitionValidator.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Tasks; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class SwitchTaskDefinitionValidator - : AbstractValidator -{ - - /// - public SwitchTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components, IDictionary? tasks) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.Tasks = tasks; - this.RuleForEach(t => t.Switch) - .SetValidator(t => new SwitchCaseDefinitionValidator(this.ServiceProvider, this.Components, this.Tasks)); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Gets a ke/definition mapping of all tasks in the scope of the task to validate - /// - protected IDictionary? Tasks { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs deleted file mode 100644 index 1d93519..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/TaskDefinitionValidator.cs +++ /dev/null @@ -1,117 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Tasks; -using ServerlessWorkflow.Sdk.Properties; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class TaskDefinitionValidator - : AbstractValidator -{ - - /// - public TaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components, IDictionary? tasks) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.Tasks = tasks; - this.RuleFor(t => t.Then) - .Must(ReferenceAnExistingTask) - .When(NotAFlowDirective) - .WithMessage(ValidationErrors.UndefinedTask); - this.RuleFor(t => t.TimeoutReference!) - .Must(ReferenceAnExistingTimeout) - .When(t => !string.IsNullOrWhiteSpace(t.TimeoutReference)) - .WithMessage(ValidationErrors.UndefinedTimeout); - this.When(t => t is CallTaskDefinition, () => - { - this.RuleFor(t => (CallTaskDefinition)t) - .SetValidator(t => new CallTaskDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(t => t is DoTaskDefinition, () => - { - this.RuleFor(t => (DoTaskDefinition)t) - .SetValidator(t => new DoTaskDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(t => t is ForkTaskDefinition, () => - { - this.RuleFor(t => (ForkTaskDefinition)t) - .SetValidator(t => new ForkTaskDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(t => t is RaiseTaskDefinition, () => - { - this.RuleFor(t => (RaiseTaskDefinition)t) - .SetValidator(t => new RaiseTaskDefinitionValidator(this.ServiceProvider, this.Components)); - }); - this.When(t => t is SwitchTaskDefinition, () => - { - this.RuleFor(t => (SwitchTaskDefinition)t) - .SetValidator(t => new SwitchTaskDefinitionValidator(this.ServiceProvider, this.Components, this.Tasks)); - }); - this.When(t => t is TryTaskDefinition, () => - { - this.RuleFor(t => (TryTaskDefinition)t) - .SetValidator(t => new TryTaskDefinitionValidator(this.ServiceProvider, this.Components)); - }); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Gets a ke/definition mapping of all tasks in the scope of the task to validate - /// - protected IDictionary? Tasks { get; } - - /// - /// Determines whether or not the specified task is defined in the actual scope - /// - /// The name of the task to check - /// A boolean indicating whether or not the specified task is defined in the actual scope - protected virtual bool ReferenceAnExistingTask(string? name) => !string.IsNullOrWhiteSpace(name) && this.Tasks != null && this.Tasks.ContainsKey(name); - - /// - /// Determines whether or not the task's then is a flow directive - /// - /// The task to check - /// A boolean indicating whether or not the task's then is a flow directive - protected virtual bool NotAFlowDirective(TaskDefinition task) - { - return task.Then switch - { - null or "" or FlowDirective.Continue or FlowDirective.End or FlowDirective.Exit => false, - _ => true - }; - } - - /// - /// Determines whether or not the specified retry policy is defined - /// - /// The name of the timeout to check - /// A boolean indicating whether or not the specified retry policy is defined - protected virtual bool ReferenceAnExistingTimeout(string name) => this.Components?.Timeouts?.ContainsKey(name) == true; - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/TaskKeyValuePairValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/TaskKeyValuePairValidator.cs deleted file mode 100644 index d367572..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/TaskKeyValuePairValidator.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 TaskKeyValuePairValidator - : AbstractValidator> -{ - - /// - public TaskKeyValuePairValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components, IDictionary? tasks) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.Tasks = tasks; - this.RuleFor(t => t.Value) - .Custom((value, context) => - { - var key = context.InstanceToValidate.Key; - var validator = new TaskDefinitionValidator(serviceProvider, components, tasks); - 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; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Gets a ke/definition mapping of all tasks in the scope of the task to validate - /// - protected IDictionary? Tasks { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/TaskMapEntryValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/TaskMapEntryValidator.cs deleted file mode 100644 index fc4b02a..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/TaskMapEntryValidator.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 map entries -/// -public class TaskMapEntryValidator - : AbstractValidator> -{ - - /// - public TaskMapEntryValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components, IDictionary? tasks) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.Tasks = tasks; - this.RuleFor(t => t.Value) - .Custom((value, context) => - { - var key = context.InstanceToValidate.Key; - var validator = new TaskDefinitionValidator(serviceProvider, components, tasks); - 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; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Gets a ke/definition mapping of all tasks in the scope of the task to validate - /// - protected IDictionary? Tasks { get; } - -} diff --git a/src/ServerlessWorkflow.Sdk/Validation/TryTaskDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/TryTaskDefinitionValidator.cs deleted file mode 100644 index cc07167..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/TryTaskDefinitionValidator.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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; -using ServerlessWorkflow.Sdk.Models.Tasks; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the used to validate s -/// -public class TryTaskDefinitionValidator - : AbstractValidator -{ - - /// - public TryTaskDefinitionValidator(IServiceProvider serviceProvider, ComponentDefinitionCollection? components) - { - this.ServiceProvider = serviceProvider; - this.Components = components; - this.RuleForEach(t => t.Try) - .SetValidator(t => new TaskMapEntryValidator(this.ServiceProvider, this.Components, t.Try.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); - this.RuleForEach(t => t.Catch.Do) - .SetValidator(t => new TaskMapEntryValidator(this.ServiceProvider, this.Components, t.Catch.Do?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))) - .When(t => t.Catch.Do != null); - this.RuleFor(t => t.Catch.RetryReference!) - .Must(ReferenceAnExistingRetryPolicy) - .When(t => !string.IsNullOrWhiteSpace(t.Catch.RetryReference)); - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the configured reusable components - /// - protected ComponentDefinitionCollection? Components { get; } - - /// - /// Determines whether or not the specified retry policy is defined - /// - /// The name of the retry policy to check - /// A boolean indicating whether or not the specified retry policy is defined - protected virtual bool ReferenceAnExistingRetryPolicy(string name) => this.Components?.Retries?.ContainsKey(name) == true; - -} \ No newline at end of file diff --git a/src/ServerlessWorkflow.Sdk/Validation/WorkflowDefinitionValidator.cs b/src/ServerlessWorkflow.Sdk/Validation/WorkflowDefinitionValidator.cs deleted file mode 100644 index ddcbb00..0000000 --- a/src/ServerlessWorkflow.Sdk/Validation/WorkflowDefinitionValidator.cs +++ /dev/null @@ -1,124 +0,0 @@ -// 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 Json.Schema; -using Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; -using ServerlessWorkflow.Sdk.Models; -using System.Collections.Concurrent; - -namespace ServerlessWorkflow.Sdk.Validation; - -/// -/// Represents the default implementation of the interface -/// -/// The used to perform HTTP requests -/// The service used to serialize/deserialize data to/from JSON -/// The service used to serialize/deserialize data to/from YAML -/// An containing all registered used to validate the DSL -public class WorkflowDefinitionValidator(HttpClient httpClient, IJsonSerializer jsonSerializer, IYamlSerializer yamlSerializer, IEnumerable> dslValidators) - : IWorkflowDefinitionValidator -{ - - /// - /// Gets the used to perform HTTP requests - /// - protected HttpClient HttpClient { get; } = httpClient; - - /// - /// Gets the service used to serialize/deserialize data to/from JSON - /// - protected IJsonSerializer JsonSerializer { get; } = jsonSerializer; - - /// - /// Gets the service used to serialize/deserialize data to/from YAML - /// - protected IYamlSerializer YamlSerializer { get; } = yamlSerializer; - - /// - /// Gets an containing all registered used to validate the DSL - /// - protected IEnumerable> Validators { get; } = dslValidators; - - /// - /// Gets a used to cache loaded s - /// - protected ConcurrentDictionary Schemas { get; } = new(); - - /// - public virtual async Task ValidateAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(workflowDefinition); - var node = this.JsonSerializer.SerializeToNode(workflowDefinition); - var schema = await this.GetOrLoadDslSchemaAsync(workflowDefinition.Document.Dsl, cancellationToken).ConfigureAwait(false); - var schemaValidationOptions = new EvaluationOptions() - { - OutputFormat = OutputFormat.List - }; - var schemaValidationResults = schema.Evaluate(node, schemaValidationOptions); - if (!schemaValidationResults.IsValid) return new ValidationResult() - { - Errors = schemaValidationResults.Details?.Select(d => new ValidationError() - { - Reference = d.InstanceLocation.ToString(), - Details = d.HasErrors && d.Errors?.Count > 0 ? string.Join(Environment.NewLine, d.Errors.Select(e => $"{e.Key}: {e.Value}")) : null - }).ToList().AsReadOnly() - }; - var dslValidationErrors = new List(); - foreach(var validator in this.Validators) - { - var dslValidationResult = await validator.ValidateAsync(workflowDefinition, cancellationToken).ConfigureAwait(false); - if (!dslValidationResult.IsValid && dslValidationResult.Errors.Count > 0) dslValidationErrors.AddRange(dslValidationResult.Errors.Select(e => new ValidationError() - { - Reference = $"/{e.PropertyName.Replace('.', '/')}", - Details = e.ErrorMessage - })); - } - return new ValidationResult() - { - Errors = dslValidationErrors?.ToList().AsReadOnly() - }; - } - - /// - /// Gets or loads the of the specified version of the Serverless Workflow DSL - /// - /// The version of the Serverless Workflow DSL to get the schema of - /// A - /// The of the specified version of the Serverless Workflow DSL - protected virtual async Task GetOrLoadDslSchemaAsync(string version, CancellationToken cancellationToken = default) - { - ArgumentException.ThrowIfNullOrWhiteSpace(version); - if (this.Schemas.TryGetValue(version, out var schema)) return schema; - var uri = new Uri($"https://raw.githubusercontent.com/serverlessworkflow/specification/v{version}/schema/workflow.yaml", UriKind.Absolute); - var yaml = await this.HttpClient.GetStringAsync(uri, cancellationToken).ConfigureAwait(false); - var yamlSchema = this.YamlSerializer.Deserialize(yaml); - var json = this.JsonSerializer.SerializeToText(yamlSchema); - schema = JsonSchema.FromText(json); - this.Schemas.TryAdd(version, schema); - return schema; - } - - /// - /// Creates a new - /// - /// A new - public static IWorkflowDefinitionValidator Create() - { - var services = new ServiceCollection(); - services.AddServerlessWorkflowValidation(); - return services.BuildServiceProvider().GetRequiredService(); - } - -} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/AuthenticationPolicyDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/AuthenticationPolicyDefinitionBuilderTests.cs new file mode 100644 index 0000000..de4d7c0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/AuthenticationPolicyDefinitionBuilderTests.cs @@ -0,0 +1,85 @@ +// 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.UnitTests.Cases.Builders; + +public class AuthenticationPolicyDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Basic_Authentication_Policy() + { + //arrange + var username = "admin"; + var password = "s3cret"; + var builder = new AuthenticationPolicyDefinitionBuilder(); + builder.Basic().WithUsername(username).WithPassword(password); + + //act + var policy = builder.Build(); + + //assert + policy.Basic.Should().NotBeNull(); + policy.Basic!.Username.Should().Be(username); + policy.Basic!.Password.Should().Be(password); + policy.Bearer.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Bearer_Authentication_Policy() + { + //arrange + var token = "eyJhbGciOi..."; + var builder = new AuthenticationPolicyDefinitionBuilder(); + builder.Bearer().WithToken(token); + + //act + var policy = builder.Build(); + + //assert + policy.Bearer.Should().NotBeNull(); + policy.Bearer!.Token.Should().Be(token); + policy.Basic.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Digest_Authentication_Policy() + { + //arrange + var username = "admin"; + var password = "digest-pass"; + var builder = new AuthenticationPolicyDefinitionBuilder(); + builder.Digest().WithUsername(username).WithPassword(password); + + //act + var policy = builder.Build(); + + //assert + policy.Basic.Should().BeNull(); + policy.Bearer.Should().BeNull(); + } + + [Fact] + public void Build_Should_Throw_When_No_Scheme_Configured() + { + //arrange + var builder = new AuthenticationPolicyDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BackoffStrategyDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BackoffStrategyDefinitionBuilderTests.cs new file mode 100644 index 0000000..2d45cc9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BackoffStrategyDefinitionBuilderTests.cs @@ -0,0 +1,82 @@ +// 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.UnitTests.Cases.Builders; + +public class BackoffStrategyDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Constant_Backoff() + { + //arrange + var builder = new BackoffStrategyDefinitionBuilder(); + builder.Constant(); + + //act + var definition = builder.Build(); + + //assert + definition.Constant.Should().NotBeNull(); + definition.Exponential.Should().BeNull(); + definition.Linear.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Exponential_Backoff() + { + //arrange + var builder = new BackoffStrategyDefinitionBuilder(); + builder.Exponential(); + + //act + var definition = builder.Build(); + + //assert + definition.Exponential.Should().NotBeNull(); + definition.Constant.Should().BeNull(); + definition.Linear.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Linear_Backoff_With_Increment() + { + //arrange + var increment = Duration.FromSeconds(2); + var builder = new BackoffStrategyDefinitionBuilder(); + builder.Linear(increment); + + //act + var definition = builder.Build(); + + //assert + definition.Linear.Should().NotBeNull(); + definition.Linear!.Increment.Should().Be(increment); + definition.Constant.Should().BeNull(); + definition.Exponential.Should().BeNull(); + } + + [Fact] + public void Build_Should_Throw_When_No_Strategy_Configured() + { + //arrange + var builder = new BackoffStrategyDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BasicAuthenticationSchemeDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BasicAuthenticationSchemeDefinitionBuilderTests.cs new file mode 100644 index 0000000..595d6dd --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BasicAuthenticationSchemeDefinitionBuilderTests.cs @@ -0,0 +1,90 @@ +// 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.UnitTests.Cases.Builders; + +public class BasicAuthenticationSchemeDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Username_And_Password() + { + //arrange + var username = "admin"; + var password = "secret"; + + //act + var scheme = new BasicAuthenticationSchemeDefinitionBuilder() + .WithUsername(username) + .WithPassword(password) + .Build(); + + //assert + scheme.Username.Should().Be(username); + scheme.Password.Should().Be(password); + scheme.Scheme.Should().Be(AuthenticationScheme.Basic); + } + + [Fact] + public void Build_Should_Set_Secret_Reference_Along_With_Credentials() + { + //arrange + var secret = "my-basic-secret"; + var username = "admin"; + var password = "secret"; + var builder = new BasicAuthenticationSchemeDefinitionBuilder(); + builder.Use(secret); + + //act + var scheme = builder + .WithUsername(username) + .WithPassword(password) + .Build(); + + //assert + scheme.Use.Should().Be(secret); + scheme.Username.Should().Be(username); + scheme.Password.Should().Be(password); + } + + [Fact] + public void Build_Should_Throw_When_Username_Missing() + { + //arrange + var password = "pass"; + + //act + var act = () => new BasicAuthenticationSchemeDefinitionBuilder() + .WithPassword(password) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Password_Missing() + { + //arrange + var username = "admin"; + + //act + var act = () => new BasicAuthenticationSchemeDefinitionBuilder() + .WithUsername(username) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BearerAuthenticationSchemeDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BearerAuthenticationSchemeDefinitionBuilderTests.cs new file mode 100644 index 0000000..ef3c210 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/BearerAuthenticationSchemeDefinitionBuilderTests.cs @@ -0,0 +1,67 @@ +// 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.UnitTests.Cases.Builders; + +public class BearerAuthenticationSchemeDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Token() + { + //arrange + var token = "jwt-token-value"; + + //act + var scheme = new BearerAuthenticationSchemeDefinitionBuilder() + .WithToken(token) + .Build(); + + //assert + scheme.Token.Should().Be(token); + scheme.Scheme.Should().Be(AuthenticationScheme.Bearer); + } + + [Fact] + public void Build_Should_Set_Secret_Reference_Along_With_Token() + { + //arrange + var secret = "my-bearer-secret"; + var token = "jwt-token"; + var builder = new BearerAuthenticationSchemeDefinitionBuilder(); + builder.Use(secret); + + //act + var scheme = builder + .WithToken(token) + .Build(); + + //assert + scheme.Use.Should().Be(secret); + scheme.Token.Should().Be(token); + } + + [Fact] + public void Build_Should_Throw_When_Token_Missing() + { + //arrange + var builder = new BearerAuthenticationSchemeDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/CallTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/CallTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..b078a34 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/CallTaskDefinitionBuilderTests.cs @@ -0,0 +1,83 @@ +// 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.UnitTests.Cases.Builders; + +public class CallTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Function_And_Arguments() + { + //arrange + var functionName = "myFunction"; + var argName = "arg1"; + var argValue = "value1"; + + //act + var task = new CallTaskDefinitionBuilder() + .Function(functionName) + .With(argName, JsonValue.Create(argValue)) + .Build(); + + //assert + task.Call.Should().Be(functionName); + task.With![argName]!.GetValue().Should().Be(argValue); + } + + [Fact] + public void Build_Should_Accept_Function_Via_Constructor() + { + //arrange + var functionName = "presetFunc"; + + //act + var task = new CallTaskDefinitionBuilder(functionName).Build(); + + //assert + task.Call.Should().Be(functionName); + } + + [Fact] + public void Build_Should_Accept_Prebuilt_Arguments() + { + //arrange + var functionName = "fn"; + var key = "key"; + var value = "val"; + var args = new JsonObject { [key] = value }; + + //act + var task = new CallTaskDefinitionBuilder() + .Function(functionName) + .With(args) + .Build(); + + //assert + task.With![key]!.GetValue().Should().Be(value); + } + + [Fact] + public void Build_Should_Throw_When_Function_Missing() + { + //arrange + var builder = new CallTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/CertificateAuthenticationSchemeDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/CertificateAuthenticationSchemeDefinitionBuilderTests.cs new file mode 100644 index 0000000..2ae51d8 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/CertificateAuthenticationSchemeDefinitionBuilderTests.cs @@ -0,0 +1,35 @@ +// 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.UnitTests.Cases.Builders; + +public class CertificateAuthenticationSchemeDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Secret_Reference_And_Scheme() + { + //arrange + var secret = "my-cert-secret"; + var builder = new CertificateAuthenticationSchemeDefinitionBuilder(); + builder.Use(secret); + + //act + var scheme = builder.Build(); + + //assert + scheme.Use.Should().Be(secret); + scheme.Scheme.Should().Be(AuthenticationScheme.Certificate); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ConstantBackoffDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ConstantBackoffDefinitionBuilderTests.cs new file mode 100644 index 0000000..81e27d0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ConstantBackoffDefinitionBuilderTests.cs @@ -0,0 +1,32 @@ +// 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.UnitTests.Cases.Builders; + +public class ConstantBackoffDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Constant_Backoff() + { + //arrange + var builder = new ConstantBackoffDefinitionBuilder(); + + //act + var definition = builder.Build(); + + //assert + definition.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ContainerProcessDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ContainerProcessDefinitionBuilderTests.cs new file mode 100644 index 0000000..55264ff --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ContainerProcessDefinitionBuilderTests.cs @@ -0,0 +1,92 @@ +// 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.UnitTests.Cases.Builders; + +public class ContainerProcessDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Container_With_All_Properties() + { + //arrange + var image = "alpine:latest"; + var name = "my-container"; + var command = "echo hello"; + ushort hostPort = 8080; + ushort containerPort = 80; + var volumeHost = "/host"; + var volumeContainer = "/container"; + var envKey = "KEY"; + var envValue = "VALUE"; + + //act + var container = new ContainerProcessDefinitionBuilder() + .WithImage(image) + .WithName(name) + .WithCommand(command) + .WithPort(hostPort, containerPort) + .WithVolume(volumeHost, volumeContainer) + .WithEnvironment(envKey, envValue) + .Build(); + + //assert + container.Image.Should().Be(image); + container.Name.Should().Be(name); + container.Command.Should().Be(command); + container.Ports![hostPort].Should().Be(containerPort); + container.Volumes![volumeHost].Should().Be(volumeContainer); + container.Environment![envKey].Should().Be(envValue); + } + + [Fact] + public void Build_Should_Accept_Bulk_Ports_And_Volumes() + { + //arrange + var image = "nginx"; + ushort httpPort = 80; + ushort httpsPort = 443; + var dataVolume = "/data"; + var envName = "ENV"; + var envValue = "prod"; + var expectedPortCount = 2; + + //act + var container = new ContainerProcessDefinitionBuilder() + .WithImage(image) + .WithPorts(new Dictionary { [httpPort] = httpPort, [httpsPort] = httpsPort }) + .WithVolumes(new Dictionary { [dataVolume] = dataVolume }) + .WithEnvironment(new Dictionary { [envName] = envValue }) + .Build(); + + //assert + container.Ports.Should().HaveCount(expectedPortCount); + container.Ports![httpPort].Should().Be(httpPort); + container.Volumes![dataVolume].Should().Be(dataVolume); + container.Environment![envName].Should().Be(envValue); + } + + [Fact] + public void Build_Should_Throw_When_Image_Missing() + { + //arrange + var builder = new ContainerProcessDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/DigestAuthenticationSchemeDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/DigestAuthenticationSchemeDefinitionBuilderTests.cs new file mode 100644 index 0000000..b08c50d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/DigestAuthenticationSchemeDefinitionBuilderTests.cs @@ -0,0 +1,38 @@ +// 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.UnitTests.Cases.Builders; + +public class DigestAuthenticationSchemeDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Username_And_Password() + { + //arrange + var username = "admin"; + var password = "secret"; + + //act + var scheme = new DigestAuthenticationSchemeDefinitionBuilder() + .WithUsername(username) + .WithPassword(password) + .Build(); + + //assert + scheme.Username.Should().Be(username); + scheme.Password.Should().Be(password); + scheme.Scheme.Should().Be(AuthenticationScheme.Digest); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/DoTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/DoTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..a732f08 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/DoTaskDefinitionBuilderTests.cs @@ -0,0 +1,76 @@ +// 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.UnitTests.Cases.Builders; + +public class DoTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Do_With_Subtasks() + { + //arrange + var step1Name = "step1"; + var step1Key = "a"; + var step1Value = "1"; + var step2Name = "step2"; + var step2Key = "b"; + var step2Value = "2"; + var expectedCount = 2; + + //act + var task = new DoTaskDefinitionBuilder() + .Do(tasks => + { + tasks.Do(step1Name, t => t.Set(step1Key, step1Value)); + tasks.Do(step2Name, t => t.Set(step2Key, step2Value)); + }) + .Build(); + + //assert + task.Do.Should().HaveCount(expectedCount); + task.Do.Keys.Should().Contain(step1Name); + task.Do.Keys.Should().Contain(step2Name); + } + + [Fact] + public void Build_Should_Throw_When_Less_Than_Two_Tasks() + { + //arrange + var taskName = "only-one"; + var key = "k"; + var value = "v"; + + //act + var act = () => new DoTaskDefinitionBuilder() + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Do_Not_Set() + { + //arrange + var builder = new DoTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EmitTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EmitTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..7b3673d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EmitTaskDefinitionBuilderTests.cs @@ -0,0 +1,68 @@ +// 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.UnitTests.Cases.Builders; + +public class EmitTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Event_Via_Builder() + { + //arrange + var typeKey = "type"; + var typeValue = "com.test"; + var sourceKey = "source"; + var sourceValue = "https://test.com"; + + //act + var task = new EmitTaskDefinitionBuilder() + .Event(e => e.With(typeKey, JsonValue.Create(typeValue)).With(sourceKey, JsonValue.Create(sourceValue))) + .Build(); + + //assert + task.Emit.Event.With[typeKey]!.GetValue().Should().Be(typeValue); + task.Emit.Event.With[sourceKey]!.GetValue().Should().Be(sourceValue); + } + + [Fact] + public void Build_Should_Set_Event_Via_Definition() + { + //arrange + var typeKey = "type"; + var typeValue = "com.direct"; + var eventDef = new EventDefinition { With = new JsonObject { [typeKey] = typeValue } }; + + //act + var task = new EmitTaskDefinitionBuilder() + .Event(eventDef) + .Build(); + + //assert + task.Emit.Event.With[typeKey]!.GetValue().Should().Be(typeValue); + } + + [Fact] + public void Build_Should_Throw_When_Event_Missing() + { + //arrange + var builder = new EmitTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EndpointDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EndpointDefinitionBuilderTests.cs new file mode 100644 index 0000000..a9b4a9e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EndpointDefinitionBuilderTests.cs @@ -0,0 +1,65 @@ +// 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.UnitTests.Cases.Builders; + +public class EndpointDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Endpoint_With_Uri() + { + //arrange + var uri = new Uri("https://api.example.com/v1"); + + //act + var endpoint = new EndpointDefinitionBuilder() + .WithUri(uri) + .Build(); + + //assert + endpoint.Uri.Should().Be(uri); + } + + [Fact] + public void Build_Should_Create_Endpoint_With_Authentication() + { + //arrange + var uri = new Uri("https://api.example.com/v1"); + var token = "my-token"; + + //act + var endpoint = new EndpointDefinitionBuilder() + .WithUri(uri) + .UseAuthentication(auth => auth.Bearer().WithToken(token)) + .Build(); + + //assert + endpoint.Uri.Should().Be(uri); + endpoint.Authentication.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Throw_When_Uri_Missing() + { + //arrange + var builder = new EndpointDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorCatcherDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorCatcherDefinitionBuilderTests.cs new file mode 100644 index 0000000..d786274 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorCatcherDefinitionBuilderTests.cs @@ -0,0 +1,76 @@ +// 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.UnitTests.Cases.Builders; + +public class ErrorCatcherDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Catcher_With_All_Properties() + { + //arrange + var asVar = "error"; + var whenExpr = "${ .status == 404 }"; + var exceptWhenExpr = "${ .ignore }"; + + //act + var catcher = new ErrorCatcherDefinitionBuilder() + .As(asVar) + .When(whenExpr) + .ExceptWhen(exceptWhenExpr) + .Build(); + + //assert + catcher.As.Should().Be(asVar); + catcher.When.Should().Be(whenExpr); + catcher.ExceptWhen.Should().Be(exceptWhenExpr); + } + + [Fact] + public void Build_Should_Configure_Do_Tasks() + { + //arrange + var taskName = "log"; + var key = "logged"; + var value = "true"; + var expectedCount = 1; + + //act + var catcher = new ErrorCatcherDefinitionBuilder() + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + catcher.Do.Should().HaveCount(expectedCount); + catcher.Do!.Keys.Should().Contain(taskName); + } + + [Fact] + public void Build_Should_Create_Empty_Catcher_With_Null_Properties() + { + //arrange + var builder = new ErrorCatcherDefinitionBuilder(); + + //act + var catcher = builder.Build(); + + //assert + catcher.As.Should().BeNull(); + catcher.When.Should().BeNull(); + catcher.ExceptWhen.Should().BeNull(); + catcher.Do.Should().BeNull(); + catcher.Errors.Should().BeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorDefinitionBuilderTests.cs new file mode 100644 index 0000000..a2d15cc --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorDefinitionBuilderTests.cs @@ -0,0 +1,88 @@ +// 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.UnitTests.Cases.Builders; + +public class ErrorDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Error_With_All_Properties() + { + //arrange + var type = "https://errors.com/not-found"; + var title = "Not Found"; + var status = "404"; + var detail = "Resource not found"; + var instance = "/items/123"; + + //act + var error = new ErrorDefinitionBuilder() + .WithType(type) + .WithTitle(title) + .WithStatus(status) + .WithDetail(detail) + .WithInstance(instance) + .Build(); + + //assert + error.Type.Should().Be(type); + error.Title.Should().Be(title); + error.Status.Should().Be(status); + error.Detail.Should().Be(detail); + error.Instance.Should().Be(instance); + } + + [Fact] + public void Build_Should_Throw_When_Type_Missing() + { + //arrange + var title = "t"; + var status = "400"; + + //act + var act = () => new ErrorDefinitionBuilder().WithTitle(title).WithStatus(status).Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Title_Missing() + { + //arrange + var type = "t"; + var status = "400"; + + //act + var act = () => new ErrorDefinitionBuilder().WithType(type).WithStatus(status).Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Status_Missing() + { + //arrange + var type = "t"; + var title = "t"; + + //act + var act = () => new ErrorDefinitionBuilder().WithType(type).WithTitle(title).Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorFilterDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorFilterDefinitionBuilderTests.cs new file mode 100644 index 0000000..6615691 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ErrorFilterDefinitionBuilderTests.cs @@ -0,0 +1,66 @@ +// 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.UnitTests.Cases.Builders; + +public class ErrorFilterDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Filter_With_Attributes() + { + //arrange + var attrName = "status"; + var attrValue = "500"; + + //act + var filter = new ErrorFilterDefinitionBuilder() + .With(attrName, attrValue) + .Build(); + + //assert + filter.With?[attrName]?.GetValue().Should().Be(attrValue); + } + + [Fact] + public void Build_Should_Create_Filter_With_Prebuilt_Attributes() + { + //arrange + var typeKey = "type"; + var errorType = "https://errors.com/timeout"; + + //act + var filter = new ErrorFilterDefinitionBuilder() + .With(typeKey, errorType) + .Build(); + + //assert + filter.With?[typeKey]?.GetValue().Should().Be(errorType); + } + + [Fact] + public void Build_Should_Create_Filter_From_Constructor() + { + //arrange + var statusKey = "status"; + var status = "404"; + var attributes = new JsonObject { [statusKey] = status }; + + //act + var filter = new ErrorFilterDefinitionBuilder(attributes).Build(); + + //assert + filter.With?[statusKey]?.GetValue().Should().Be(status); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventDefinitionBuilderTests.cs new file mode 100644 index 0000000..ea3fa7d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventDefinitionBuilderTests.cs @@ -0,0 +1,57 @@ +// 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.UnitTests.Cases.Builders; + +public class EventDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Event_With_Attributes() + { + //arrange + var typeKey = "type"; + var typeValue = "com.example.test"; + var sourceKey = "source"; + var sourceValue = "https://example.com"; + + //act + var e = new EventDefinitionBuilder() + .With(typeKey, JsonValue.Create(typeValue)) + .With(sourceKey, JsonValue.Create(sourceValue)) + .Build(); + + //assert + e.With[typeKey]!.GetValue().Should().Be(typeValue); + e.With[sourceKey]!.GetValue().Should().Be(sourceValue); + } + + [Fact] + public void Build_Should_Accept_Prebuilt_Attributes() + { + //arrange + var typeKey = "type"; + var typeValue = "com.test"; + var sourceKey = "source"; + var sourceValue = "https://test.com"; + var attrs = new JsonObject { [typeKey] = typeValue, [sourceKey] = sourceValue }; + + //act + var e = new EventDefinitionBuilder().With(attrs).Build(); + + //assert + e.With[typeKey]!.GetValue().Should().Be(typeValue); + e.With[sourceKey]!.GetValue().Should().Be(sourceValue); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventFilterDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventFilterDefinitionBuilderTests.cs new file mode 100644 index 0000000..809b3a7 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventFilterDefinitionBuilderTests.cs @@ -0,0 +1,52 @@ +// 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.UnitTests.Cases.Builders; + +public class EventFilterDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Filter_With_Attributes() + { + //arrange + var attrName = "type"; + var attrValue = "com.example.test"; + + //act + var filter = new EventFilterDefinitionBuilder() + .With(attrName, JsonValue.Create(attrValue)) + .Build(); + + //assert + filter.With?[attrName]?.GetValue().Should().Be(attrValue); + } + + [Fact] + public void Build_Should_Create_Filter_With_Prebuilt_Attributes() + { + //arrange + var sourceKey = "source"; + var sourceValue = "https://example.com"; + var attributes = new JsonObject { [sourceKey] = sourceValue }; + + //act + var filter = new EventFilterDefinitionBuilder() + .With(attributes) + .Build(); + + //assert + filter.With?[sourceKey]?.GetValue().Should().Be(sourceValue); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventFilterDefinitionCollectionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventFilterDefinitionCollectionBuilderTests.cs new file mode 100644 index 0000000..68120d6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/EventFilterDefinitionCollectionBuilderTests.cs @@ -0,0 +1,68 @@ +// 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.UnitTests.Cases.Builders; + +public class EventFilterDefinitionCollectionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Collection_With_Single_Filter() + { + //arrange + var attrName = "type"; + var attrValue = JsonValue.Create("com.example.test"); + var expectedCount = 1; + + //act + var collection = new EventFilterDefinitionCollectionBuilder() + .Event(f => f.With(attrName, attrValue)) + .Build(); + + //assert + collection.Should().HaveCount(expectedCount); + } + + [Fact] + public void Build_Should_Create_Collection_With_Multiple_Filters() + { + //arrange + var typeKey = "type"; + var typeA = JsonValue.Create("com.a"); + var typeB = JsonValue.Create("com.b"); + var expectedCount = 2; + + //act + var collection = new EventFilterDefinitionCollectionBuilder() + .Event(f => f.With(typeKey, typeA)) + .Event(f => f.With(typeKey, typeB)) + .Build(); + + //assert + collection.Should().HaveCount(expectedCount); + } + + [Fact] + public void Build_Should_Throw_When_No_Filters() + { + //arrange + var builder = new EventFilterDefinitionCollectionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExponentialBackoffDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExponentialBackoffDefinitionBuilderTests.cs new file mode 100644 index 0000000..052f172 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExponentialBackoffDefinitionBuilderTests.cs @@ -0,0 +1,32 @@ +// 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.UnitTests.Cases.Builders; + +public class ExponentialBackoffDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Exponential_Backoff() + { + //arrange + var builder = new ExponentialBackoffDefinitionBuilder(); + + //act + var definition = builder.Build(); + + //assert + definition.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExtensionDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExtensionDefinitionBuilderTests.cs new file mode 100644 index 0000000..a26804f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExtensionDefinitionBuilderTests.cs @@ -0,0 +1,65 @@ +// 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.UnitTests.Cases.Builders; + +public class ExtensionDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Extension_With_Extend_Type() + { + //arrange + var extendType = "call"; + + //act + var extension = new ExtensionDefinitionBuilder() + .Extend(extendType) + .Build(); + + //assert + extension.Extend.Should().Be(extendType); + } + + [Fact] + public void Build_Should_Create_Extension_With_When_Condition() + { + //arrange + var extendType = "all"; + var whenExpr = "${ .logging }"; + + //act + var extension = new ExtensionDefinitionBuilder() + .Extend(extendType) + .When(whenExpr) + .Build(); + + //assert + extension.Extend.Should().Be(extendType); + extension.When.Should().Be(whenExpr); + } + + [Fact] + public void Build_Should_Throw_When_Extend_Missing() + { + //arrange + var builder = new ExtensionDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExternalResourceDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExternalResourceDefinitionBuilderTests.cs new file mode 100644 index 0000000..b8593b1 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ExternalResourceDefinitionBuilderTests.cs @@ -0,0 +1,50 @@ +// 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.UnitTests.Cases.Builders; + +public class ExternalResourceDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Resource_With_Endpoint() + { + //arrange + var uri = new Uri("https://resources.example.com/schema.json"); + var resourceName = "my-schema"; + + //act + var resource = new ExternalResourceDefinitionBuilder() + .WithName(resourceName) + .WithEndpoint(e => e.WithUri(uri)) + .Build(); + + //assert + resource.Name.Should().Be(resourceName); + resource.Endpoint.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Throw_When_Endpoint_Missing() + { + //arrange + var builder = new ExternalResourceDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ForTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ForTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..6aae5da --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ForTaskDefinitionBuilderTests.cs @@ -0,0 +1,102 @@ +// 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.UnitTests.Cases.Builders; + +public class ForTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Each_In_At_And_Do() + { + //arrange + var eachVar = "item"; + var inExpr = "${ .items }"; + var atVar = "index"; + var taskName = "process"; + var key = "processed"; + var value = "true"; + var expectedCount = 1; + + //act + var task = new ForTaskDefinitionBuilder() + .Each(eachVar) + .In(inExpr) + .At(atVar) + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + task.For.Each.Should().Be(eachVar); + task.For.In.Should().Be(inExpr); + task.For.At.Should().Be(atVar); + task.Do.Should().HaveCount(expectedCount); + task.Do.Keys.Should().Contain(taskName); + } + + [Fact] + public void Build_Should_Throw_When_Each_Missing() + { + //arrange + var inExpr = "${ .items }"; + var taskName = "step"; + var key = "k"; + var value = "v"; + + //act + var act = () => new ForTaskDefinitionBuilder() + .In(inExpr) + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_In_Missing() + { + //arrange + var eachVar = "item"; + var taskName = "step"; + var key = "k"; + var value = "v"; + + //act + var act = () => new ForTaskDefinitionBuilder() + .Each(eachVar) + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Do_Missing() + { + //arrange + var eachVar = "item"; + var inExpr = "${ .items }"; + + //act + var act = () => new ForTaskDefinitionBuilder() + .Each(eachVar) + .In(inExpr) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ForkTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ForkTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..24e8b90 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ForkTaskDefinitionBuilderTests.cs @@ -0,0 +1,89 @@ +// 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.UnitTests.Cases.Builders; + +public class ForkTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Branches_And_Compete() + { + //arrange + var branch1Name = "branch1"; + var branch1Key = "a"; + var branch1Value = "1"; + var branch2Name = "branch2"; + var branch2Key = "b"; + var branch2Value = "2"; + var expectedCount = 2; + + //act + var task = new ForkTaskDefinitionBuilder() + .Branch(tasks => + { + tasks.Do(branch1Name, t => t.Set(branch1Key, branch1Value)); + tasks.Do(branch2Name, t => t.Set(branch2Key, branch2Value)); + }) + .Compete() + .Build(); + + //assert + task.Fork.Branches.Should().HaveCount(expectedCount); + task.Fork.Branches.Keys.Should().Contain(branch1Name); + task.Fork.Branches.Keys.Should().Contain(branch2Name); + task.Fork.Compete.Should().BeTrue(); + } + + [Fact] + public void Build_Should_Default_Compete_To_False() + { + //arrange + var b1Name = "b1"; + var b1Key = "a"; + var b1Value = "1"; + var b2Name = "b2"; + var b2Key = "b"; + var b2Value = "2"; + + //act + var task = new ForkTaskDefinitionBuilder() + .Branch(tasks => + { + tasks.Do(b1Name, t => t.Set(b1Key, b1Value)); + tasks.Do(b2Name, t => t.Set(b2Key, b2Value)); + }) + .Build(); + + //assert + task.Fork.Compete.Should().BeFalse(); + } + + [Fact] + public void Build_Should_Throw_When_Less_Than_Two_Branches() + { + //arrange + var taskName = "only-one"; + var key = "k"; + var value = "v"; + + //act + var act = () => new ForkTaskDefinitionBuilder() + .Branch(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/GenericTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/GenericTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..28167f4 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/GenericTaskDefinitionBuilderTests.cs @@ -0,0 +1,297 @@ +// 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.UnitTests.Cases.Builders; + +public class GenericTaskDefinitionBuilderTests +{ + + [Fact] + public void Call_Should_Return_CallTaskDefinition() + { + //arrange + var functionName = "myFunc"; + var argName = "arg"; + var argValue = "val"; + var builder = new GenericTaskDefinitionBuilder(); + builder.Call(functionName).With(argName, JsonValue.Create(argValue)); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + ((CallTaskDefinition)task).Call.Should().Be(functionName); + } + + [Fact] + public void Set_With_Name_Value_Should_Return_SetTaskDefinition() + { + //arrange + var key = "greeting"; + var value = "hello"; + var builder = new GenericTaskDefinitionBuilder(); + builder.Set(key, value); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Set_With_JsonObject_Should_Return_SetTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var key = "k"; + var value = "v"; + builder.Set(new JsonObject { [key] = value }); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Wait_Should_Return_WaitTaskDefinition() + { + //arrange + var duration = Duration.FromSeconds(5); + var builder = new GenericTaskDefinitionBuilder(); + builder.Wait(duration); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Emit_With_EventDefinition_Should_Return_EmitTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var typeKey = "type"; + var typeValue = "com.test"; + var e = new EventDefinition { With = new JsonObject { [typeKey] = typeValue } }; + builder.Emit(e); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Emit_With_Setup_Should_Return_EmitTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var typeKey = "type"; + var typeValue = "com.test"; + builder.Emit(e => e.With(typeKey, JsonValue.Create(typeValue))); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Raise_With_ErrorDefinition_Should_Return_RaiseTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var errorType = "https://err.com/t"; + var errorTitle = "T"; + var errorStatus = "500"; + var error = new ErrorDefinition { Type = errorType, Title = errorTitle, Status = errorStatus }; + builder.Raise(error); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Raise_With_Setup_Should_Return_RaiseTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var errorType = "https://err.com/t"; + var errorTitle = "T"; + var errorStatus = "500"; + builder.Raise(e => e.WithType(errorType).WithTitle(errorTitle).WithStatus(errorStatus)); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void For_Should_Return_ForTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var eachVar = "item"; + var inExpr = "${ .items }"; + var taskName = "process"; + var key = "k"; + var value = "v"; + builder.For().Each(eachVar).In(inExpr).Do(tasks => tasks.Do(taskName, t => t.Set(key, value))); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Fork_Should_Return_ForkTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var b1Name = "b1"; + var b1Key = "a"; + var b1Value = "1"; + var b2Name = "b2"; + var b2Key = "b"; + var b2Value = "2"; + builder.Fork().Branch(tasks => + { + tasks.Do(b1Name, t => t.Set(b1Key, b1Value)); + tasks.Do(b2Name, t => t.Set(b2Key, b2Value)); + }); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Switch_Should_Return_SwitchTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var caseName = "default"; + builder.Switch().Case(caseName, c => c.Then(FlowDirective.End)); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Try_Should_Return_TryTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var taskName = "risky"; + var key = "k"; + var value = "v"; + builder.Try() + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Catch(c => { }); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Run_Should_Return_RunTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var command = "echo test"; + builder.Run().Shell().WithCommand(command); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Listen_Should_Return_ListenTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var typeKey = "type"; + var typeValue = "com.test"; + builder.Listen().To(l => l.One().With(typeKey, JsonValue.Create(typeValue))); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Do_Should_Return_DoTaskDefinition() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + var s1Name = "s1"; + var s1Key = "a"; + var s1Value = "1"; + var s2Name = "s2"; + var s2Key = "b"; + var s2Value = "2"; + builder.Do(tasks => + { + tasks.Do(s1Name, t => t.Set(s1Key, s1Value)); + tasks.Do(s2Name, t => t.Set(s2Key, s2Value)); + }); + + //act + var task = builder.Build(); + + //assert + task.Should().BeOfType(); + } + + [Fact] + public void Build_Should_Throw_When_No_Task_Configured() + { + //arrange + var builder = new GenericTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/HttpCallDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/HttpCallDefinitionBuilderTests.cs new file mode 100644 index 0000000..a5b640e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/HttpCallDefinitionBuilderTests.cs @@ -0,0 +1,113 @@ +// 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.UnitTests.Cases.Builders; + +public class HttpCallDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Http_Call_With_Method_And_Endpoint() + { + //arrange + var method = "GET"; + var uri = new Uri("https://api.example.com/items"); + + //act + var call = new HttpCallDefinitionBuilder() + .WithMethod(method) + .WithEndpoint(e => e.WithUri(uri)) + .Build(); + + //assert + call.Method.Should().Be(method); + var endpoint = call.Endpoint.Match(e => e, _ => null); + endpoint.Should().NotBeNull(); + endpoint!.Uri.Should().Be(uri); + } + + [Fact] + public void Build_Should_Create_Http_Call_With_Headers_And_Body() + { + //arrange + var method = "POST"; + var uri = new Uri("https://api.example.com/items"); + var headerName = "Content-Type"; + var headerValue = "application/json"; + var bodyKey = "name"; + var bodyValue = "test"; + var body = new JsonObject { [bodyKey] = bodyValue }; + + //act + var call = new HttpCallDefinitionBuilder() + .WithMethod(method) + .WithEndpoint(e => e.WithUri(uri)) + .WithHeader(headerName, headerValue) + .WithBody(body) + .Build(); + + //assert + call.Headers![headerName].Should().Be(headerValue); + call.Body![bodyKey]!.GetValue().Should().Be(bodyValue); + } + + [Fact] + public void Build_Should_Create_Http_Call_With_Output_Format() + { + //arrange + var method = "GET"; + var uri = new Uri("https://api.example.com/items"); + var format = "json"; + + //act + var call = new HttpCallDefinitionBuilder() + .WithMethod(method) + .WithEndpoint(e => e.WithUri(uri)) + .WithOutputFormat(format) + .Build(); + + //assert + call.Output.Should().Be(format); + } + + [Fact] + public void Build_Should_Throw_When_Method_Missing() + { + //arrange + var uri = new Uri("https://api.example.com"); + + //act + var act = () => new HttpCallDefinitionBuilder() + .WithEndpoint(e => e.WithUri(uri)) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Endpoint_Missing() + { + //arrange + var method = "GET"; + + //act + var act = () => new HttpCallDefinitionBuilder() + .WithMethod(method) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/InputDataModelDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/InputDataModelDefinitionBuilderTests.cs new file mode 100644 index 0000000..f7a8dcb --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/InputDataModelDefinitionBuilderTests.cs @@ -0,0 +1,62 @@ +// 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.UnitTests.Cases.Builders; + +public class InputDataModelDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Input_With_From_Expression() + { + //arrange + var fromExpr = "${ .data }"; + + //act + var input = new InputDataModelDefinitionBuilder() + .From(fromExpr) + .Build(); + + //assert + input.From.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Input_With_Schema() + { + //arrange + var format = "json"; + + //act + var input = new InputDataModelDefinitionBuilder() + .WithSchema(s => s.WithFormat(format)) + .Build(); + + //assert + input.Schema.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Empty_Input() + { + //arrange + var builder = new InputDataModelDefinitionBuilder(); + + //act + var input = builder.Build(); + + //assert + input.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/JitterDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/JitterDefinitionBuilderTests.cs new file mode 100644 index 0000000..01e6654 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/JitterDefinitionBuilderTests.cs @@ -0,0 +1,52 @@ +// 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.UnitTests.Cases.Builders; + +public class JitterDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Jitter_With_Range() + { + //arrange + var from = Duration.FromMilliseconds(100); + var to = Duration.FromMilliseconds(500); + + //act + var definition = new JitterDefinitionBuilder() + .From(from) + .To(to) + .Build(); + + //assert + definition.From.Should().Be(from); + definition.To.Should().Be(to); + } + + [Fact] + public void Build_Should_Create_Jitter_From_Constructor() + { + //arrange + var from = Duration.FromMilliseconds(50); + var to = Duration.FromMilliseconds(200); + + //act + var definition = new JitterDefinitionBuilder(from, to).Build(); + + //assert + definition.From.Should().Be(from); + definition.To.Should().Be(to); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/LinearBackoffDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/LinearBackoffDefinitionBuilderTests.cs new file mode 100644 index 0000000..79538c0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/LinearBackoffDefinitionBuilderTests.cs @@ -0,0 +1,46 @@ +// 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.UnitTests.Cases.Builders; + +public class LinearBackoffDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Linear_Backoff_With_Increment() + { + //arrange + var increment = Duration.FromSeconds(2); + + //act + var definition = new LinearBackoffDefinitionBuilder(increment).Build(); + + //assert + definition.Should().NotBeNull(); + definition.Increment.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Linear_Backoff_Without_Increment() + { + //arrange + var builder = new LinearBackoffDefinitionBuilder(); + + //act + var definition = builder.Build(); + + //assert + definition.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..4be759b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenTaskDefinitionBuilderTests.cs @@ -0,0 +1,36 @@ +// 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.UnitTests.Cases.Builders; + +public class ListenTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Listener_Target() + { + //arrange + var typeKey = "type"; + var typeValue = "com.test"; + + //act + var task = new ListenTaskDefinitionBuilder() + .To(listener => listener.One().With(typeKey, JsonValue.Create(typeValue))) + .Build(); + + //assert + task.Listen.To.One.Should().NotBeNull(); + task.Listen.To.One.With?[typeKey]?.GetValue().Should().Be(typeValue); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenerDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenerDefinitionBuilderTests.cs new file mode 100644 index 0000000..5fadaee --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenerDefinitionBuilderTests.cs @@ -0,0 +1,68 @@ +// 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.UnitTests.Cases.Builders; + +public class ListenerDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Listener_With_One_Event() + { + //arrange + var typeKey = "type"; + var typeValue = "com.example.test"; + var builder = new ListenerDefinitionBuilder(); + builder.One().With(typeKey, JsonValue.Create(typeValue)); + + //act + var result = builder.Build(); + + //assert + result.To.One.Should().NotBeNull(); + result.To.One.With?[typeKey]?.GetValue().Should().Be(typeValue); + } + + [Fact] + public void Build_Should_Create_Listener_With_Read_Mode() + { + //arrange + var readMode = EventReadMode.Envelope; + var typeKey = "type"; + var typeValue = "com.test"; + var builder = new ListenerDefinitionBuilder(); + builder.One().With(typeKey, JsonValue.Create(typeValue)); + builder.Read(readMode); + + //act + var result = builder.Build(); + + //assert + result.Read.Should().Be(readMode); + result.To.One.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Throw_When_No_Target() + { + //arrange + var builder = new ListenerDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenerTargetDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenerTargetDefinitionBuilderTests.cs new file mode 100644 index 0000000..e26d3ec --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ListenerTargetDefinitionBuilderTests.cs @@ -0,0 +1,86 @@ +// 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.UnitTests.Cases.Builders; + +public class ListenerTargetDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Target_With_All_Events() + { + //arrange + var typeKey = "type"; + var eventType = JsonValue.Create("com.test"); + var builder = new ListenerTargetDefinitionBuilder(); + builder.All().Event(f => f.With(typeKey, eventType)); + + //act + var target = builder.Build(); + + //assert + target.All.Should().NotBeNull(); + target.Any.Should().BeNull(); + target.One.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Target_With_Any_Events() + { + //arrange + var typeKey = "type"; + var eventType = JsonValue.Create("com.test"); + var builder = new ListenerTargetDefinitionBuilder(); + builder.Any().Event(f => f.With(typeKey, eventType)); + + //act + var target = builder.Build(); + + //assert + target.Any.Should().NotBeNull(); + target.All.Should().BeNull(); + target.One.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Target_With_One_Event() + { + //arrange + var typeKey = "type"; + var eventType = JsonValue.Create("com.test"); + var builder = new ListenerTargetDefinitionBuilder(); + builder.One().With(typeKey, eventType); + + //act + var target = builder.Build(); + + //assert + target.One.Should().NotBeNull(); + target.All.Should().BeNull(); + target.Any.Should().BeNull(); + } + + [Fact] + public void Build_Should_Throw_When_No_Strategy() + { + //arrange + var builder = new ListenerTargetDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationClientDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationClientDefinitionBuilderTests.cs new file mode 100644 index 0000000..f28f7dc --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationClientDefinitionBuilderTests.cs @@ -0,0 +1,59 @@ +// 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.UnitTests.Cases.Builders; + +public class OAuth2AuthenticationClientDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Client_With_All_Properties() + { + //arrange + var clientId = "my-client-id"; + var clientSecret = "my-client-secret"; + var assertion = "jwt-assertion"; + var authMethod = "client_secret_post"; + + //act + var client = new OAuth2AuthenticationClientDefinitionBuilder() + .WithId(clientId) + .WithSecret(clientSecret) + .WithAssertion(assertion) + .WithAuthenticationMethod(authMethod) + .Build(); + + //assert + client.Id.Should().Be(clientId); + client.Secret.Should().Be(clientSecret); + client.Assertion.Should().Be(assertion); + client.Authentication.Should().Be(authMethod); + } + + [Fact] + public void Build_Should_Create_Client_With_Minimal_Properties() + { + //arrange + var clientId = "minimal-client"; + + //act + var client = new OAuth2AuthenticationClientDefinitionBuilder() + .WithId(clientId) + .Build(); + + //assert + client.Id.Should().Be(clientId); + client.Secret.Should().BeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationEndpointsDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationEndpointsDefinitionBuilderTests.cs new file mode 100644 index 0000000..667865f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationEndpointsDefinitionBuilderTests.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.UnitTests.Cases.Builders; + +public class OAuth2AuthenticationEndpointsDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Endpoints_With_Custom_Uris() + { + //arrange + var tokenUri = new Uri("/custom/token", UriKind.Relative); + var revocationUri = new Uri("/custom/revoke", UriKind.Relative); + var introspectionUri = new Uri("/custom/introspect", UriKind.Relative); + + //act + var endpoints = new OAuth2AuthenticationEndpointsDefinitionBuilder() + .WithTokenEndpoint(tokenUri) + .WithRevocationEndpoint(revocationUri) + .WithIntrospectionEndpoint(introspectionUri) + .Build(); + + //assert + endpoints.Token.Should().Be(tokenUri); + endpoints.Revocation.Should().Be(revocationUri); + endpoints.Introspection.Should().Be(introspectionUri); + } + + [Fact] + public void Build_Should_Use_Default_Endpoints_When_Not_Configured() + { + //arrange + var builder = new OAuth2AuthenticationEndpointsDefinitionBuilder(); + + //act + var endpoints = builder.Build(); + + //assert + endpoints.Token.Should().NotBeNull(); + endpoints.Revocation.Should().NotBeNull(); + endpoints.Introspection.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationRequestDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationRequestDefinitionBuilderTests.cs new file mode 100644 index 0000000..853f197 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationRequestDefinitionBuilderTests.cs @@ -0,0 +1,34 @@ +// 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.UnitTests.Cases.Builders; + +public class OAuth2AuthenticationRequestDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Request_With_Encoding() + { + //arrange + var encoding = "application/x-www-form-urlencoded"; + + //act + var request = new OAuth2AuthenticationRequestDefinitionBuilder() + .WithEncoding(encoding) + .Build(); + + //assert + request.Encoding.Should().Be(encoding); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationSchemeDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationSchemeDefinitionBuilderTests.cs new file mode 100644 index 0000000..503513e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OAuth2AuthenticationSchemeDefinitionBuilderTests.cs @@ -0,0 +1,131 @@ +// 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.UnitTests.Cases.Builders; + +public class OAuth2AuthenticationSchemeDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Authority_And_GrantType() + { + //arrange + var authority = new Uri("https://auth.example.com"); + var grantType = "client_credentials"; + + //act + var scheme = new OAuth2AuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .WithGrantType(grantType) + .Build(); + + //assert + scheme.Authority.Should().Be(authority); + scheme.Grant.Should().Be(grantType); + } + + [Fact] + public void Build_Should_Set_Client_Via_Builder() + { + //arrange + var authority = new Uri("https://auth.example.com"); + var grantType = "client_credentials"; + var clientId = "my-client"; + + //act + var scheme = new OAuth2AuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .WithGrantType(grantType) + .WithClient(c => c.WithId(clientId)) + .Build(); + + //assert + scheme.Client.Should().NotBeNull(); + scheme.Client!.Id.Should().Be(clientId); + } + + [Fact] + public void Build_Should_Set_Scopes_And_Audiences() + { + //arrange + var authority = new Uri("https://auth.example.com"); + var grantType = "client_credentials"; + var scope = "read"; + var audience = "api"; + + //act + var scheme = new OAuth2AuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .WithGrantType(grantType) + .WithScopes(scope) + .WithAudiences(audience) + .Build(); + + //assert + scheme.Scopes.Should().Contain(scope); + scheme.Audiences.Should().Contain(audience); + } + + [Fact] + public void Build_Should_Set_Password_Grant_Credentials() + { + //arrange + var authority = new Uri("https://auth.example.com"); + var grantType = "password"; + var username = "user"; + var password = "pass"; + + //act + var scheme = new OAuth2AuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .WithGrantType(grantType) + .WithUsername(username) + .WithPassword(password) + .Build(); + + //assert + scheme.Username.Should().Be(username); + scheme.Password.Should().Be(password); + } + + [Fact] + public void Build_Should_Throw_When_Authority_Missing() + { + //arrange + var grantType = "client_credentials"; + + //act + var act = () => new OAuth2AuthenticationSchemeDefinitionBuilder() + .WithGrantType(grantType) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_GrantType_Missing() + { + //arrange + var authority = new Uri("https://auth.example.com"); + + //act + var act = () => new OAuth2AuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilderTests.cs new file mode 100644 index 0000000..b29b6bf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OpenIDConnectAuthenticationSchemeDefinitionBuilderTests.cs @@ -0,0 +1,86 @@ +// 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.UnitTests.Cases.Builders; + +public class OpenIDConnectAuthenticationSchemeDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Authority_And_GrantType() + { + //arrange + var authority = new Uri("https://oidc.example.com"); + var grantType = "authorization_code"; + + //act + var scheme = new OpenIDConnectAuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .WithGrantType(grantType) + .Build(); + + //assert + scheme.Authority.Should().Be(authority); + scheme.Grant.Should().Be(grantType); + } + + [Fact] + public void Build_Should_Set_Issuers() + { + //arrange + var authority = new Uri("https://oidc.example.com"); + var grantType = "authorization_code"; + var issuer = "https://oidc.example.com"; + + //act + var scheme = new OpenIDConnectAuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .WithGrantType(grantType) + .WithIssuers(issuer) + .Build(); + + //assert + scheme.Issuers.Should().Contain(issuer); + } + + [Fact] + public void Build_Should_Throw_When_Authority_Missing() + { + //arrange + var grantType = "authorization_code"; + + //act + var act = () => new OpenIDConnectAuthenticationSchemeDefinitionBuilder() + .WithGrantType(grantType) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_GrantType_Missing() + { + //arrange + var authority = new Uri("https://oidc.example.com"); + + //act + var act = () => new OpenIDConnectAuthenticationSchemeDefinitionBuilder() + .WithAuthority(authority) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OutputDataModelDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OutputDataModelDefinitionBuilderTests.cs new file mode 100644 index 0000000..b000956 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/OutputDataModelDefinitionBuilderTests.cs @@ -0,0 +1,62 @@ +// 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.UnitTests.Cases.Builders; + +public class OutputDataModelDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Output_With_As_Expression() + { + //arrange + var asExpr = "${ .result }"; + + //act + var output = new OutputDataModelDefinitionBuilder() + .As(asExpr) + .Build(); + + //assert + output.As.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Output_With_Schema() + { + //arrange + var format = "json"; + + //act + var output = new OutputDataModelDefinitionBuilder() + .WithSchema(s => s.WithFormat(format)) + .Build(); + + //assert + output.Schema.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Empty_Output() + { + //arrange + var builder = new OutputDataModelDefinitionBuilder(); + + //act + var output = builder.Build(); + + //assert + output.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RaiseTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RaiseTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..6e316c4 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RaiseTaskDefinitionBuilderTests.cs @@ -0,0 +1,73 @@ +// 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.UnitTests.Cases.Builders; + +public class RaiseTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Error_Via_Builder() + { + //arrange + var errorType = "https://errors.com/not-found"; + var errorTitle = "Not Found"; + var errorStatus = "404"; + + //act + var task = new RaiseTaskDefinitionBuilder() + .Error(e => e.WithType(errorType).WithTitle(errorTitle).WithStatus(errorStatus)) + .Build(); + + //assert + var error = task.Raise.Error.Match(e => e, _ => null); + error.Should().NotBeNull(); + error!.Type.Should().Be(errorType); + error!.Title.Should().Be(errorTitle); + error!.Status.Should().Be(errorStatus); + } + + [Fact] + public void Build_Should_Set_Error_Via_Definition() + { + //arrange + var errorType = "https://err.com/t"; + var errorTitle = "T"; + var errorStatus = "500"; + var errorDef = new ErrorDefinition { Type = errorType, Title = errorTitle, Status = errorStatus }; + + //act + var task = new RaiseTaskDefinitionBuilder() + .Error(errorDef) + .Build(); + + //assert + var error = task.Raise.Error.Match(e => e, _ => null); + error.Should().NotBeNull(); + error!.Type.Should().Be(errorType); + } + + [Fact] + public void Build_Should_Throw_When_Error_Missing() + { + //arrange + var builder = new RaiseTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryAttemptLimitDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryAttemptLimitDefinitionBuilderTests.cs new file mode 100644 index 0000000..5643acb --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryAttemptLimitDefinitionBuilderTests.cs @@ -0,0 +1,67 @@ +// 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.UnitTests.Cases.Builders; + +public class RetryAttemptLimitDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Limit_With_Count() + { + //arrange + uint count = 3; + + //act + var limit = new RetryAttemptLimitDefinitionBuilder() + .Count(count) + .Build(); + + //assert + limit.Count.Should().Be(count); + } + + [Fact] + public void Build_Should_Create_Limit_With_Duration() + { + //arrange + var duration = Duration.FromSeconds(30); + + //act + var limit = new RetryAttemptLimitDefinitionBuilder() + .Duration(duration) + .Build(); + + //assert + limit.Duration.Should().Be(duration); + } + + [Fact] + public void Build_Should_Create_Limit_With_Count_And_Duration() + { + //arrange + uint count = 5; + var duration = Duration.FromMinutes(1); + + //act + var limit = new RetryAttemptLimitDefinitionBuilder() + .Count(count) + .Duration(duration) + .Build(); + + //assert + limit.Count.Should().Be(count); + limit.Duration.Should().Be(duration); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryPolicyDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryPolicyDefinitionBuilderTests.cs new file mode 100644 index 0000000..633264b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryPolicyDefinitionBuilderTests.cs @@ -0,0 +1,115 @@ +// 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.UnitTests.Cases.Builders; + +public class RetryPolicyDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Policy_With_When_Expression() + { + //arrange + var whenExpr = "${ .retryable }"; + + //act + var policy = new RetryPolicyDefinitionBuilder() + .When(whenExpr) + .Build(); + + //assert + policy.When.Should().Be(whenExpr); + } + + [Fact] + public void Build_Should_Create_Policy_With_ExceptWhen() + { + //arrange + var exceptWhenExpr = "${ .fatal }"; + + //act + var policy = new RetryPolicyDefinitionBuilder() + .ExceptWhen(exceptWhenExpr) + .Build(); + + //assert + policy.ExceptWhen.Should().Be(exceptWhenExpr); + } + + [Fact] + public void Build_Should_Create_Policy_With_Delay() + { + //arrange + var delay = Duration.FromSeconds(5); + + //act + var policy = new RetryPolicyDefinitionBuilder() + .Delay(delay) + .Build(); + + //assert + policy.Delay.Should().Be(delay); + } + + [Fact] + public void Build_Should_Create_Policy_With_Backoff() + { + //arrange + var builder = new RetryPolicyDefinitionBuilder(); + + //act + var policy = builder + .Backoff(b => b.Exponential()) + .Build(); + + //assert + policy.Backoff.Should().NotBeNull(); + policy.Backoff!.Exponential.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Policy_With_Jitter() + { + //arrange + var from = Duration.FromMilliseconds(100); + var to = Duration.FromMilliseconds(500); + + //act + var policy = new RetryPolicyDefinitionBuilder() + .Jitter(j => j.From(from).To(to)) + .Build(); + + //assert + policy.Jitter.Should().NotBeNull(); + policy.Jitter!.From.Should().Be(from); + policy.Jitter!.To.Should().Be(to); + } + + [Fact] + public void Build_Should_Create_Policy_With_Limit() + { + //arrange + uint maxAttempts = 3; + + //act + var policy = new RetryPolicyDefinitionBuilder() + .Limit(l => l.Attempt().Count(maxAttempts)) + .Build(); + + //assert + policy.Limit.Should().NotBeNull(); + policy.Limit!.Attempt.Should().NotBeNull(); + policy.Limit!.Attempt!.Count.Should().Be(maxAttempts); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryPolicyLimitDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryPolicyLimitDefinitionBuilderTests.cs new file mode 100644 index 0000000..90ce4bf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RetryPolicyLimitDefinitionBuilderTests.cs @@ -0,0 +1,50 @@ +// 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.UnitTests.Cases.Builders; + +public class RetryPolicyLimitDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Limit_With_Attempt() + { + //arrange + uint maxAttempts = 5; + var builder = new RetryPolicyLimitDefinitionBuilder(); + builder.Attempt().Count(maxAttempts); + + //act + var limit = builder.Build(); + + //assert + limit.Attempt.Should().NotBeNull(); + limit.Attempt!.Count.Should().Be(maxAttempts); + } + + [Fact] + public void Build_Should_Create_Limit_With_Duration() + { + //arrange + var maxDuration = Duration.FromMinutes(5); + + //act + var limit = new RetryPolicyLimitDefinitionBuilder() + .Duration(maxDuration) + .Build(); + + //assert + limit.Duration.Should().Be(maxDuration); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RunTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RunTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..98451d5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/RunTaskDefinitionBuilderTests.cs @@ -0,0 +1,83 @@ +// 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.UnitTests.Cases.Builders; + +public class RunTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Container_Run() + { + //arrange + var image = "nginx:latest"; + var builder = new RunTaskDefinitionBuilder(); + builder.Container().WithImage(image); + + //act + var task = builder.Build(); + + //assert + task.Run.Container.Should().NotBeNull(); + task.Run.Container!.Image.Should().Be(image); + task.Run.Shell.Should().BeNull(); + } + + [Fact] + public void Build_Should_Create_Shell_Run() + { + //arrange + var command = "echo hello"; + var argument = "--verbose"; + var builder = new RunTaskDefinitionBuilder(); + builder.Shell().WithCommand(command).WithArgument(argument); + + //act + var task = builder.Build(); + + //assert + task.Run.Shell.Should().NotBeNull(); + task.Run.Shell!.Command.Should().Be(command); + task.Run.Shell!.Arguments.Should().Contain(argument); + task.Run.Container.Should().BeNull(); + } + + [Fact] + public void Build_Should_Set_Await_Flag() + { + //arrange + var image = "alpine"; + var builder = new RunTaskDefinitionBuilder(); + builder.Container().WithImage(image); + + //act + var task = builder.Await(false).Build(); + + //assert + task.Run.Await.Should().BeFalse(); + } + + [Fact] + public void Build_Should_Throw_When_No_Process() + { + //arrange + var builder = new RunTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SchemaDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SchemaDefinitionBuilderTests.cs new file mode 100644 index 0000000..bd3a34e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SchemaDefinitionBuilderTests.cs @@ -0,0 +1,66 @@ +// 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.UnitTests.Cases.Builders; + +public class SchemaDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Schema_With_Format() + { + //arrange + var format = "json"; + + //act + var schema = new SchemaDefinitionBuilder() + .WithFormat(format) + .Build(); + + //assert + schema.Format.Should().Be(format); + } + + [Fact] + public void Build_Should_Create_Schema_With_Document() + { + //arrange + var typeKey = "type"; + var typeValue = "object"; + var document = new JsonObject { [typeKey] = typeValue }; + + //act + var schema = new SchemaDefinitionBuilder() + .WithDocument(document) + .Build(); + + //assert + schema.Document.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Schema_With_Resource() + { + //arrange + var uri = new Uri("https://schemas.example.com/schema.json"); + + //act + var schema = new SchemaDefinitionBuilder() + .WithResource(r => r.WithEndpoint(e => e.WithUri(uri))) + .Build(); + + //assert + schema.Resource.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ScriptProcessDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ScriptProcessDefinitionBuilderTests.cs new file mode 100644 index 0000000..d926035 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ScriptProcessDefinitionBuilderTests.cs @@ -0,0 +1,94 @@ +// 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.UnitTests.Cases.Builders; + +public class ScriptProcessDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Script_With_Language_And_Code() + { + //arrange + var language = "javascript"; + var code = "console.log('hello')"; + + //act + var script = new ScriptProcessDefinitionBuilder() + .WithLanguage(language) + .WithCode(code) + .Build(); + + //assert + script.Language.Should().Be(language); + script.Code.Should().Be(code); + } + + [Fact] + public void Build_Should_Create_Script_With_Source() + { + //arrange + var language = "python"; + var sourceUri = new Uri("https://scripts.example.com/run.py"); + + //act + var script = new ScriptProcessDefinitionBuilder() + .WithLanguage(language) + .WithSource(s => s.WithEndpoint(e => e.WithUri(sourceUri))) + .Build(); + + //assert + script.Language.Should().Be(language); + script.Source.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Script_With_Arguments_And_Environment() + { + //arrange + var language = "bash"; + var code = "echo $MSG"; + var argName = "verbose"; + var argValue = "true"; + var envName = "MSG"; + var envValue = "hello"; + + //act + var script = new ScriptProcessDefinitionBuilder() + .WithLanguage(language) + .WithCode(code) + .WithArgument(argName, argValue) + .WithEnvironment(envName, envValue) + .Build(); + + //assert + script.Arguments.Should().ContainKey(argName); + script.Environment.Should().ContainKey(envName); + } + + [Fact] + public void Build_Should_Throw_When_Language_Missing() + { + //arrange + var code = "print('hi')"; + + //act + var act = () => new ScriptProcessDefinitionBuilder() + .WithCode(code) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SetTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SetTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..4ccfa59 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SetTaskDefinitionBuilderTests.cs @@ -0,0 +1,130 @@ +// 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.UnitTests.Cases.Builders; + +public class SetTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Named_Variables() + { + //arrange + var greetingKey = "greeting"; + var greetingValue = "hello"; + var countKey = "count"; + var countValue = 42; + + //act + var task = new SetTaskDefinitionBuilder() + .Set(greetingKey, JsonValue.Create(greetingValue)) + .Set(countKey, JsonValue.Create(countValue)) + .Build(); + + //assert + task.Set[greetingKey]!.GetValue().Should().Be(greetingValue); + task.Set[countKey]!.GetValue().Should().Be(countValue); + } + + [Fact] + public void Build_Should_Accept_JsonObject() + { + //arrange + var xKey = "x"; + var xValue = 1; + var yKey = "y"; + var yValue = 2; + var variables = new JsonObject { [xKey] = xValue, [yKey] = yValue }; + + //act + var task = new SetTaskDefinitionBuilder() + .Set(variables) + .Build(); + + //assert + task.Set[xKey]!.GetValue().Should().Be(xValue); + task.Set[yKey]!.GetValue().Should().Be(yValue); + } + + [Fact] + public void Build_Should_Configure_If_Condition() + { + //arrange + var condition = "${ .enabled }"; + var key = "k"; + var value = "v"; + + //act + var task = new SetTaskDefinitionBuilder() + .If(condition) + .Set(key, JsonValue.Create(value)) + .Build(); + + //assert + task.If.Should().Be(condition); + } + + [Fact] + public void Build_Should_Configure_Then_Directive() + { + //arrange + var key = "k"; + var value = "v"; + + //act + var task = new SetTaskDefinitionBuilder() + .Set(key, JsonValue.Create(value)) + .Then(FlowDirective.End) + .Build(); + + //assert + task.Then.Should().Be(FlowDirective.End); + } + + [Fact] + public void Build_Should_Configure_Timeout_Via_Builder() + { + //arrange + var key = "k"; + var value = "v"; + var timeoutDuration = Duration.FromSeconds(10); + + //act + var task = new SetTaskDefinitionBuilder() + .Set(key, JsonValue.Create(value)) + .WithTimeout(t => t.After(timeoutDuration)) + .Build(); + + //assert + task.Timeout.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Configure_Timeout_Via_Reference() + { + //arrange + var key = "k"; + var value = "v"; + var timeoutRef = "my-timeout"; + + //act + var task = new SetTaskDefinitionBuilder() + .Set(key, JsonValue.Create(value)) + .WithTimeout(timeoutRef) + .Build(); + + //assert + task.Timeout.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ShellProcessDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ShellProcessDefinitionBuilderTests.cs new file mode 100644 index 0000000..68e0640 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/ShellProcessDefinitionBuilderTests.cs @@ -0,0 +1,80 @@ +// 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.UnitTests.Cases.Builders; + +public class ShellProcessDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Shell_With_All_Properties() + { + //arrange + var command = "echo hello"; + var argument = "--verbose"; + var envKey = "PATH"; + var envValue = "/usr/bin"; + + //act + var shell = new ShellProcessDefinitionBuilder() + .WithCommand(command) + .WithArgument(argument) + .WithEnvironment(envKey, envValue) + .Build(); + + //assert + shell.Command.Should().Be(command); + shell.Arguments.Should().Contain(argument); + shell.Environment.Should().ContainKey(envKey); + } + + [Fact] + public void Build_Should_Accept_Bulk_Arguments_And_Environment() + { + //arrange + var command = "ls"; + var arg1 = "-l"; + var arg2 = "-a"; + var envKey = "HOME"; + var envValue = "/root"; + var expectedArgCount = 2; + + //act + var shell = new ShellProcessDefinitionBuilder() + .WithCommand(command) + .WithArguments([arg1, arg2]) + .WithEnvironment(new Dictionary { [envKey] = envValue }) + .Build(); + + //assert + shell.Command.Should().Be(command); + shell.Arguments.Should().HaveCount(expectedArgCount); + shell.Arguments.Should().Contain(arg1); + shell.Arguments.Should().Contain(arg2); + shell.Environment![envKey].Should().Be(envValue); + } + + [Fact] + public void Build_Should_Throw_When_Command_Missing() + { + //arrange + var builder = new ShellProcessDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SubscriptionIteratorDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SubscriptionIteratorDefinitionBuilderTests.cs new file mode 100644 index 0000000..4ed31c9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SubscriptionIteratorDefinitionBuilderTests.cs @@ -0,0 +1,50 @@ +// 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.UnitTests.Cases.Builders; + +public class SubscriptionIteratorDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Iterator_With_Item_And_At() + { + //arrange + var itemVar = "event"; + var atVar = "index"; + + //act + var iterator = new SubscriptionIteratorDefinitionBuilder() + .Item(itemVar) + .At(atVar) + .Build(); + + //assert + iterator.Item.Should().Be(itemVar); + iterator.At.Should().Be(atVar); + } + + [Fact] + public void Build_Should_Create_Empty_Iterator() + { + //arrange + var builder = new SubscriptionIteratorDefinitionBuilder(); + + //act + var iterator = builder.Build(); + + //assert + iterator.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SwitchCaseDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SwitchCaseDefinitionBuilderTests.cs new file mode 100644 index 0000000..3d62ae9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SwitchCaseDefinitionBuilderTests.cs @@ -0,0 +1,65 @@ +// 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.UnitTests.Cases.Builders; + +public class SwitchCaseDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Case_With_When_And_Then() + { + //arrange + var whenExpr = "${ .status == \"active\" }"; + + //act + var @case = new SwitchCaseDefinitionBuilder() + .When(whenExpr) + .Then(FlowDirective.Continue) + .Build(); + + //assert + @case.When.Should().Be(whenExpr); + @case.Then.Should().Be(FlowDirective.Continue); + } + + [Fact] + public void Build_Should_Throw_When_Then_Missing() + { + //arrange + var whenExpr = "${ true }"; + + //act + var act = () => new SwitchCaseDefinitionBuilder().When(whenExpr).Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Create_Default_Case_Without_When() + { + //arrange + var builder = new SwitchCaseDefinitionBuilder(); + + //act + var @case = builder + .Then(FlowDirective.End) + .Build(); + + //assert + @case.When.Should().BeNull(); + @case.Then.Should().Be(FlowDirective.End); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SwitchTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SwitchTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..5d4e22e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/SwitchTaskDefinitionBuilderTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Builders; + +public class SwitchTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Switch_With_Cases() + { + //arrange + var activeCaseName = "active"; + var defaultCaseName = "default"; + var whenExpr = "${ .status == \"active\" }"; + var expectedCount = 2; + + //act + var task = new SwitchTaskDefinitionBuilder() + .Case(activeCaseName, c => c.When(whenExpr).Then(FlowDirective.Continue)) + .Case(defaultCaseName, c => c.Then(FlowDirective.End)) + .Build(); + + //assert + task.Switch.Should().HaveCount(expectedCount); + task.Switch[activeCaseName].When.Should().Be(whenExpr); + task.Switch[activeCaseName].Then.Should().Be(FlowDirective.Continue); + task.Switch[defaultCaseName].When.Should().BeNull(); + task.Switch[defaultCaseName].Then.Should().Be(FlowDirective.End); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TaskDefinitionMapBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TaskDefinitionMapBuilderTests.cs new file mode 100644 index 0000000..0cdf9a6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TaskDefinitionMapBuilderTests.cs @@ -0,0 +1,75 @@ +// 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.UnitTests.Cases.Builders; + +public class TaskDefinitionMapBuilderTests +{ + + [Fact] + public void Build_Should_Create_Map_With_Tasks() + { + //arrange + var step1Name = "step1"; + var step1Key = "a"; + var step1Value = "1"; + var step2Name = "step2"; + var step2Key = "b"; + var step2Value = "2"; + var expectedCount = 2; + + //act + var map = new TaskDefinitionMapBuilder() + .Do(step1Name, task => task.Set(step1Key, step1Value)) + .Do(step2Name, task => task.Set(step2Key, step2Value)) + .Build(); + + //assert + map.Should().HaveCount(expectedCount); + map.Keys.Should().Contain(step1Name); + map.Keys.Should().Contain(step2Name); + } + + [Fact] + public void Build_Should_Accept_Prebuilt_Task() + { + //arrange + var taskName = "step"; + var key = "k"; + var value = "v"; + var task = new SetTaskDefinition { Set = new JsonObject { [key] = value } }; + var expectedCount = 1; + + //act + var map = new TaskDefinitionMapBuilder() + .Do(taskName, task) + .Build(); + + //assert + map.Should().HaveCount(expectedCount); + } + + [Fact] + public void Build_Should_Throw_When_Empty() + { + //arrange + var builder = new TaskDefinitionMapBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TimeoutDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TimeoutDefinitionBuilderTests.cs new file mode 100644 index 0000000..386c3b5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TimeoutDefinitionBuilderTests.cs @@ -0,0 +1,62 @@ +// 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.UnitTests.Cases.Builders; + +public class TimeoutDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Timeout_From_Duration() + { + //arrange + var duration = Duration.FromSeconds(30); + + //act + var timeout = new TimeoutDefinitionBuilder() + .After(duration) + .Build(); + + //assert + timeout.After.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Create_Timeout_From_String() + { + //arrange + var durationString = "PT30S"; + + //act + var timeout = new TimeoutDefinitionBuilder() + .After(durationString) + .Build(); + + //assert + timeout.After.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Throw_When_After_Missing() + { + //arrange + var builder = new TimeoutDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TryTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TryTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..44c24d6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/TryTaskDefinitionBuilderTests.cs @@ -0,0 +1,72 @@ +// 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.UnitTests.Cases.Builders; + +public class TryTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Try_With_Tasks_And_Catch() + { + //arrange + var taskName = "risky"; + var key = "k"; + var value = "v"; + var expectedCount = 1; + + //act + var task = new TryTaskDefinitionBuilder() + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Catch(c => { }) + .Build(); + + //assert + task.Try.Should().HaveCount(expectedCount); + task.Try.Keys.Should().Contain(taskName); + task.Catch.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Throw_When_Try_Tasks_Missing() + { + //arrange + var builder = new TryTaskDefinitionBuilder(); + + //act + var act = () => builder + .Catch(c => { }) + .Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Catch_Missing() + { + //arrange + var taskName = "step"; + var key = "k"; + var value = "v"; + + //act + var act = () => new TryTaskDefinitionBuilder() + .Do(tasks => tasks.Do(taskName, t => t.Set(key, value))) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WaitTaskDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WaitTaskDefinitionBuilderTests.cs new file mode 100644 index 0000000..b913ffe --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WaitTaskDefinitionBuilderTests.cs @@ -0,0 +1,60 @@ +// 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.UnitTests.Cases.Builders; + +public class WaitTaskDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Set_Duration() + { + //arrange + var duration = Duration.FromSeconds(5); + + //act + var task = new WaitTaskDefinitionBuilder() + .For(duration) + .Build(); + + //assert + task.Wait.Should().Be(duration); + } + + [Fact] + public void Build_Should_Accept_Duration_Via_Constructor() + { + //arrange + var duration = Duration.FromMilliseconds(500); + + //act + var task = new WaitTaskDefinitionBuilder(duration).Build(); + + //assert + task.Wait.Should().Be(duration); + } + + [Fact] + public void Build_Should_Throw_When_Duration_Missing() + { + //arrange + var builder = new WaitTaskDefinitionBuilder(); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowDefinitionBuilderTests.cs index 2d83186..ff32597 100644 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowDefinitionBuilderTests.cs +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowDefinitionBuilderTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -11,186 +11,206 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Builders; -using Neuroglia.Serialization.Yaml; - namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Builders; public class WorkflowDefinitionBuilderTests { [Fact] - public void Build_Should_Work() + public void Build_Should_Create_Minimal_Workflow() { //arrange - var name = "fake-workflow"; - var version = "1.0.0-alpha2"; - var title = "Fake Title"; - var summary = "fake Markdown summary"; - var fakeTagName = "fakeTagName"; - var fakeTagValue = "fakeTagValue"; + var workflowName = "test-workflow"; + var version = "1.0.0"; + var taskName = "greet"; + var key = "message"; + var value = "hello"; + var expectedTaskCount = 1; //act var workflow = new WorkflowDefinitionBuilder() - .WithName(name) + .WithName(workflowName) + .WithVersion(version) + .Do(taskName, task => task.Set(key, value)) + .Build(); + + //assert + workflow.Should().NotBeNull(); + workflow.Document.Name.Should().Be(workflowName); + workflow.Document.Version.Should().Be(version); + workflow.Document.Namespace.Should().Be(WorkflowDefinitionMetadata.DefaultNamespace); + workflow.Do.Should().HaveCount(expectedTaskCount); + } + + [Fact] + public void Build_Should_Set_All_Document_Properties() + { + //arrange + var dsl = "1.0.0"; + var ns = "my-namespace"; + var workflowName = "my-workflow"; + var version = "2.0.0"; + var title = "My Workflow"; + var summary = "A test workflow"; + var tagKey = "env"; + var tagValue = "test"; + var taskName = "step1"; + var key = "k"; + var value = "v"; + + //act + var workflow = new WorkflowDefinitionBuilder() + .UseDsl(dsl) + .WithNamespace(ns) + .WithName(workflowName) .WithVersion(version) .WithTitle(title) .WithSummary(summary) - .WithTag(fakeTagName, fakeTagValue) - .UseAuthentication("fakeBasic", authentication => authentication - .Basic() - .WithUsername("fake-user") - .WithPassword("fake-password")) - .UseAuthentication("fakeBearer", authentication => authentication - .Bearer() - .WithToken("fake-token")) - .UseAuthentication("fakeOAuth2", authentication => authentication - .OAuth2() - .WithAuthority(new("https://fake-authority.com")) - .WithGrantType(OAuth2GrantType.ClientCredentials) - .WithClient(client => client - .WithId("fake-client-id") - .WithSecret("fake-client-secret"))) - .UseFunction("fakeFunction1", function => function - .Call() - .Function("http") - .With("method", "post") - .With("uri", "https://test.com")) - .UseFunction("fakeFunction2", function => function - .Run() - .Shell() - .WithCommand(@"echo ""Hello, World!""")) - .UseExtension("fakeLoggingExtension", extension => extension - .ExtendAll() - .When("fake-expression") - .Before(tasks => tasks - .Do("fake-http-call", task => task - .Call("http") - .With("method", "post") - .With("uri", "https://fake.log.collector.com") - .With("body", new - { - message = @"${ ""Executing task '\($task.reference)'..."" }" - }))) - .After(tasks => tasks - .Do("fake-http-call", task => task - .Call("http") - .With("method", "post") - .With("uri", "https://fake.log.collector.com") - .With("body", new - { - message = @"${ ""Executed task '\($task.reference)'..."" }" - })))) - .UseSecret("fake-secret") - .Do("todo-1", task => task - .Call("http") - .If("fake-condition") - .With("method", "get") - .With("uri", "https://unit-tests.serverlessworkflow.io")) - .Do("todo-2", task => task - .Emit(e => e - .With("type", "io.serverlessworkflow.unit-tests.fake.event.type.v1"))) - .Do("todo-3", task => task - .For() - .Each("color") - .In(".colors") - .At("index") - .Do(tasks => tasks - .Do("fake-http-call", subtask => subtask - .Set("processed", ".processed + [$color]")))) - .Do("todo-4", task => task - .Listen() - .To(to => to - .Any() - .Event(e => e - .With("foo", "bar")) - .Event(e => e - .With(new Dictionary() { { "foo", "bar" }, { "bar", "baz" } })))) - .Do("todo-5", task => task - .Raise(error => error - .WithType("fake-error-type") - .WithStatus("400") - .WithTitle("fake-error-title"))) - .Do("todo-6", task => task - .Run() - .Container() - .WithImage("fake-image:latest") - .WithCommand("fake command --arg1 arg1") - .WithEnvironment("ASPNET_ENVIRONMENT", "Development")) - .Do("todo-7", task => task - .Run() - .Shell() - .WithCommand("fake command --arg1 arg1") - .WithArgument("--arg2 arg2") - .WithEnvironment("ASPNET_ENVIRONMENT", "Development")) - .Do("todo-8", task => task - .Run() - .Script() - .WithLanguage("js") - .WithCode(@"console.log(""Hello, World!"")")) - .Do("todo-9", task => task - .Run() - .Workflow() - .WithName("fake-workflow") - .WithVersion("1.0.0") - .WithInput(new { foo = "bar" })) - .Do("todo-10", task => task - .Set("foo", "bar") - .Set("bar", new { baz = "foo" })) - .Do("todo-11", task => task - .Switch() - .Case("case-1", @case => @case - .When("fake-condition") - .Then(FlowDirective.Continue)) - .Case("case-2", @case => @case - .When("another-fake-condition") - .Then(FlowDirective.Exit)) - .Case("default", @case => @case - .Then(FlowDirective.End))) - .Do("todo-12", task => task - .Try() - .Do(tasks => tasks - .Do("setFoo", subtask => subtask - .Set("foo", "bar"))) - .Catch(error => error - .Errors(filter => filter - .With("status", ". == 400")) - .As("error") - .When("fake-condition") - .ExceptWhen("another-fake-condition") - .Retry(retry => retry - .When("fake-condition") - .ExceptWhen("another-fake-condition") - .Limit(limits => limits - .Attempt() - .Count(10))) - .Do(tasks => tasks - .Do("setFoo", subtask => subtask - .Set("foo", "bar"))))) - .Do("todo-13", task => task - .Wait() - .For(Duration.FromMinutes(5))) - .Do("todo-14", task => task - .Do(tasks => tasks - .Do("todo-14-1", task => task - .Call("http") - .With("method", "get") - .With("uri", "https://unit-tests.serverlessworkflow.io")) - .Do("todo-14-2", task => task - .Emit(e => e - .With("type", "io.serverlessworkflow.unit-tests.fake.event.type.v1"))) - .Do("todo-14-3", task => task - .For() - .Each("color") - .In(".colors") - .At("index") - .Do(tasks => tasks - .Do("setProcessed", subtask => subtask - .Set("processed", ".processed + [$color]")))))) + .WithTag(tagKey, tagValue) + .Do(taskName, task => task.Set(key, value)) + .Build(); + + //assert + workflow.Document.Dsl.Should().Be(dsl); + workflow.Document.Namespace.Should().Be(ns); + workflow.Document.Name.Should().Be(workflowName); + workflow.Document.Version.Should().Be(version); + workflow.Document.Title.Should().Be(title); + workflow.Document.Summary.Should().Be(summary); + workflow.Document.Tags.Should().ContainKey(tagKey); + } + + [Fact] + public void Build_Should_Throw_When_Name_Missing() + { + //arrange + var version = "1.0.0"; + var taskName = "step"; + var key = "k"; + var value = "v"; + var builder = new WorkflowDefinitionBuilder() + .WithVersion(version) + .Do(taskName, task => task.Set(key, value)); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_Version_Missing() + { + //arrange + var workflowName = "test"; + var taskName = "step"; + var key = "k"; + var value = "v"; + var builder = new WorkflowDefinitionBuilder() + .WithName(workflowName) + .Do(taskName, task => task.Set(key, value)); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Throw_When_No_Tasks() + { + //arrange + var workflowName = "test"; + var version = "1.0.0"; + var builder = new WorkflowDefinitionBuilder() + .WithName(workflowName) + .WithVersion(version); + + //act + var act = () => builder.Build(); + + //assert + act.Should().Throw(); + } + + [Fact] + public void WithVersion_Should_Throw_For_Invalid_SemVer() + { + //arrange + var invalidVersion = "not-semver"; + + //act + var act = () => new WorkflowDefinitionBuilder().WithVersion(invalidVersion); + + //assert + act.Should().Throw(); + } + + [Fact] + public void WithName_Should_Throw_For_Invalid_Name() + { + //arrange + var invalidName = "INVALID NAME!"; + + //act + var act = () => new WorkflowDefinitionBuilder().WithName(invalidName); + + //assert + act.Should().Throw(); + } + + [Fact] + public void Build_Should_Configure_Timeout() + { + //arrange + var workflowName = "test"; + var version = "1.0.0"; + var timeoutDuration = Duration.FromSeconds(30); + var taskName = "step"; + var key = "k"; + var value = "v"; + + //act + var workflow = new WorkflowDefinitionBuilder() + .WithName(workflowName) + .WithVersion(version) + .WithTimeout(timeout => timeout.After(timeoutDuration)) + .Do(taskName, task => task.Set(key, value)) + .Build(); + + //assert + workflow.Timeout.Should().NotBeNull(); + } + + [Fact] + public void Build_Should_Configure_Components() + { + //arrange + var workflowName = "test"; + var version = "1.0.0"; + var secret1 = "my-secret"; + var secret2 = "secret1"; + var secret3 = "secret2"; + var taskName = "step"; + var key = "k"; + var value = "v"; + + //act + var workflow = new WorkflowDefinitionBuilder() + .WithName(workflowName) + .WithVersion(version) + .UseSecret(secret1) + .UseSecret(secret2) + .UseSecret(secret3) + .Do(taskName, task => task.Set(key, value)) .Build(); //assert - var yaml = YamlSerializer.Default.Serialize(workflow); + workflow.Use.Should().NotBeNull(); + workflow.Use!.Secrets.Should().Contain(secret1); + workflow.Use.Secrets.Should().Contain(secret2); } } diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowProcessDefinitionBuilderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowProcessDefinitionBuilderTests.cs new file mode 100644 index 0000000..43df69e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Builders/WorkflowProcessDefinitionBuilderTests.cs @@ -0,0 +1,97 @@ +// 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.UnitTests.Cases.Builders; + +public class WorkflowProcessDefinitionBuilderTests +{ + + [Fact] + public void Build_Should_Create_Workflow_Process_With_All_Properties() + { + //arrange + var ns = "my-namespace"; + var name = "sub-workflow"; + var version = "1.0.0"; + var inputKey = "key"; + var inputValue = "value"; + var inputData = new JsonObject { [inputKey] = inputValue }; + + //act + var process = new WorkflowProcessDefinitionBuilder() + .WithNamespace(ns) + .WithName(name) + .WithVersion(version) + .WithInput(inputData) + .Build(); + + //assert + process.Namespace.Should().Be(ns); + process.Name.Should().Be(name); + process.Version.Should().Be(version); + process.Input![inputKey]!.GetValue().Should().Be(inputValue); + } + + [Fact] + public void Build_Should_Use_Default_Namespace_When_Not_Set() + { + //arrange + var name = "sub-workflow"; + var version = "1.0.0"; + + //act + var process = new WorkflowProcessDefinitionBuilder() + .WithName(name) + .WithVersion(version) + .Build(); + + //assert + process.Namespace.Should().Be(WorkflowDefinitionMetadata.DefaultNamespace); + } + + [Fact] + public void Build_Should_Use_Default_Version_When_Not_Set() + { + //arrange + var name = "sub-workflow"; + var ns = "my-namespace"; + var expectedDefaultVersion = "latest"; + + //act + var process = new WorkflowProcessDefinitionBuilder() + .WithNamespace(ns) + .WithName(name) + .Build(); + + //assert + process.Version.Should().Be(expectedDefaultVersion); + } + + [Fact] + public void Build_Should_Throw_When_Name_Missing() + { + //arrange + var ns = "ns"; + var version = "1.0.0"; + + //act + var act = () => new WorkflowProcessDefinitionBuilder() + .WithNamespace(ns) + .WithVersion(version) + .Build(); + + //assert + act.Should().Throw(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiCallDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiCallDefinitionTests.cs new file mode 100644 index 0000000..e48bafc --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiCallDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class AsyncApiCallDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = AsyncApiCallDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AsyncApiCallDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AsyncApiCallDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = AsyncApiCallDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiMessageDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiMessageDefinitionTests.cs new file mode 100644 index 0000000..017dac7 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiMessageDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class AsyncApiMessageDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = AsyncApiMessageDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AsyncApiMessageDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AsyncApiMessageDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = AsyncApiMessageDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiSubscriptionDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiSubscriptionDefinitionTests.cs new file mode 100644 index 0000000..55caad0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiSubscriptionDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class AsyncApiSubscriptionDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = AsyncApiSubscriptionDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AsyncApiSubscriptionDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AsyncApiSubscriptionDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = AsyncApiSubscriptionDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiSubscriptionLifetimeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiSubscriptionLifetimeDefinitionTests.cs new file mode 100644 index 0000000..a6ca184 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AsyncApiSubscriptionLifetimeDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class AsyncApiSubscriptionLifetimeDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = AsyncApiSubscriptionLifetimeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AsyncApiSubscriptionLifetimeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AsyncApiSubscriptionLifetimeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = AsyncApiSubscriptionLifetimeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AuthenticationPolicyDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AuthenticationPolicyDefinitionTests.cs new file mode 100644 index 0000000..f41d006 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AuthenticationPolicyDefinitionTests.cs @@ -0,0 +1,154 @@ +// 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.UnitTests.Cases.Core.Models; + +public class AuthenticationPolicyDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateBasic(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateBasic(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Bearer_Json_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateBearer(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Bearer_Yaml_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateBearer(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Certificate_Json_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateCertificate(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Digest_Json_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateDigest(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_OAuth2_Json_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateOAuth2(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Oidc_Json_Should_Work() + { + //arrange + var toSerialize = AuthenticationPolicyDefinitionFactory.CreateOidc(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.AuthenticationPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_Basic_When_Basic_Is_Set() + { + //arrange + var policy = AuthenticationPolicyDefinitionFactory.CreateBasic(); + //act + var scheme = policy.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.Basic); + } + + [Fact] + public void Scheme_Should_Return_Bearer_When_Bearer_Is_Set() + { + //arrange + var policy = AuthenticationPolicyDefinitionFactory.CreateBearer(); + //act + var scheme = policy.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.Bearer); + } + + [Fact] + public void Scheme_Should_Return_OAuth2_When_OAuth2_Is_Set() + { + //arrange + var policy = AuthenticationPolicyDefinitionFactory.CreateOAuth2(); + //act + var scheme = policy.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.OAuth2); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AuthenticationSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AuthenticationSchemeDefinitionTests.cs new file mode 100644 index 0000000..7af3876 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/AuthenticationSchemeDefinitionTests.cs @@ -0,0 +1,72 @@ +// 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.UnitTests.Cases.Core.Models; + +public class AuthenticationSchemeDefinitionTests +{ + + [Fact] + public void Basic_Should_Have_Correct_Scheme() + { + //arrange & act + var scheme = new BasicAuthenticationSchemeDefinition(); + + //assert + scheme.Scheme.Should().Be(AuthenticationScheme.Basic); + } + + [Fact] + public void Bearer_Should_Have_Correct_Scheme() + { + //arrange & act + var scheme = new BearerAuthenticationSchemeDefinition(); + + //assert + scheme.Scheme.Should().Be(AuthenticationScheme.Bearer); + } + + [Fact] + public void Certificate_Should_Have_Correct_Scheme() + { + //arrange & act + var scheme = new CertificateAuthenticationSchemeDefinition(); + + //assert + scheme.Scheme.Should().Be(AuthenticationScheme.Certificate); + } + + [Fact] + public void Digest_Should_Have_Correct_Scheme() + { + //arrange & act + var scheme = new DigestAuthenticationSchemeDefinition(); + + //assert + scheme.Scheme.Should().Be(AuthenticationScheme.Digest); + } + + [Fact] + public void Use_Property_Should_Be_Settable() + { + //arrange + var secret = "my-secret"; + + //act + var scheme = new BasicAuthenticationSchemeDefinition { Use = secret }; + + //assert + scheme.Use.Should().Be(secret); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BackoffDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BackoffDefinitionTests.cs new file mode 100644 index 0000000..c534a2a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BackoffDefinitionTests.cs @@ -0,0 +1,53 @@ +// 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.UnitTests.Cases.Core.Models; + +public class BackoffDefinitionTests +{ + + [Fact] + public void ConstantBackoffDefinition_Should_Be_BackoffDefinition() + { + //arrange & act + var definition = new ConstantBackoffDefinition(); + + //assert + definition.Should().BeAssignableTo(); + } + + [Fact] + public void ExponentialBackoffDefinition_Should_Be_BackoffDefinition() + { + //arrange & act + var definition = new ExponentialBackoffDefinition(); + + //assert + definition.Should().BeAssignableTo(); + } + + [Fact] + public void LinearBackoffDefinition_Should_Set_Increment() + { + //arrange + var increment = Duration.FromSeconds(2); + + //act + var definition = new LinearBackoffDefinition { Increment = increment }; + + //assert + definition.Should().BeAssignableTo(); + definition.Increment.Should().Be(increment); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BackoffStrategyDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BackoffStrategyDefinitionTests.cs new file mode 100644 index 0000000..4c44a27 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BackoffStrategyDefinitionTests.cs @@ -0,0 +1,69 @@ +// 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.UnitTests.Cases.Core.Models; + +public class BackoffStrategyDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Constant_Json_Should_Work() + { + //arrange + var toSerialize = BackoffStrategyDefinitionFactory.CreateConstant(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.BackoffStrategyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.BackoffStrategyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Constant_Yaml_Should_Work() + { + //arrange + var toSerialize = BackoffStrategyDefinitionFactory.CreateConstant(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Exponential_Json_Should_Work() + { + //arrange + var toSerialize = BackoffStrategyDefinitionFactory.CreateExponential(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.BackoffStrategyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.BackoffStrategyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Linear_Json_Should_Work() + { + //arrange + var toSerialize = BackoffStrategyDefinitionFactory.CreateLinear(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.BackoffStrategyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.BackoffStrategyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BasicAuthenticationSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BasicAuthenticationSchemeDefinitionTests.cs new file mode 100644 index 0000000..dee0cdb --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BasicAuthenticationSchemeDefinitionTests.cs @@ -0,0 +1,54 @@ +// 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.UnitTests.Cases.Core.Models; + +public class BasicAuthenticationSchemeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = BasicAuthenticationSchemeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.BasicAuthenticationSchemeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.BasicAuthenticationSchemeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = BasicAuthenticationSchemeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_Basic() + { + //arrange + var definition = BasicAuthenticationSchemeDefinitionFactory.Create(); + //act + var scheme = definition.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.Basic); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BearerAuthenticationSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BearerAuthenticationSchemeDefinitionTests.cs new file mode 100644 index 0000000..9dd8758 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BearerAuthenticationSchemeDefinitionTests.cs @@ -0,0 +1,54 @@ +// 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.UnitTests.Cases.Core.Models; + +public class BearerAuthenticationSchemeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = BearerAuthenticationSchemeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.BearerAuthenticationSchemeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.BearerAuthenticationSchemeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = BearerAuthenticationSchemeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_Bearer() + { + //arrange + var definition = BearerAuthenticationSchemeDefinitionFactory.Create(); + //act + var scheme = definition.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.Bearer); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BranchingDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BranchingDefinitionTests.cs new file mode 100644 index 0000000..bfa1e12 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/BranchingDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class BranchingDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = BranchingDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.BranchingDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.BranchingDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = BranchingDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CallDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CallDefinitionTests.cs new file mode 100644 index 0000000..44bf49f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CallDefinitionTests.cs @@ -0,0 +1,34 @@ +// 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.UnitTests.Cases.Core.Models; + +public class CallDefinitionTests +{ + + [Fact] + public void HttpCallDefinition_Should_Be_CallDefinition() + { + //arrange + var method = "GET"; + var endpoint = new Uri("https://api.example.com"); + + //act + var definition = new HttpCallDefinition { Method = method, Endpoint = endpoint }; + + //assert + definition.Should().BeAssignableTo(); + definition.Method.Should().Be(method); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CallTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CallTaskDefinitionTests.cs new file mode 100644 index 0000000..87b7d2f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CallTaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class CallTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = CallTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.CallTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.CallTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = CallTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CatalogDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CatalogDefinitionTests.cs new file mode 100644 index 0000000..b9534b8 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CatalogDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class CatalogDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = CatalogDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.CatalogDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.CatalogDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = CatalogDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CertificateAuthenticationSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CertificateAuthenticationSchemeDefinitionTests.cs new file mode 100644 index 0000000..95a3331 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CertificateAuthenticationSchemeDefinitionTests.cs @@ -0,0 +1,54 @@ +// 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.UnitTests.Cases.Core.Models; + +public class CertificateAuthenticationSchemeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = new CertificateAuthenticationSchemeDefinition(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.CertificateAuthenticationSchemeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.CertificateAuthenticationSchemeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = new CertificateAuthenticationSchemeDefinition(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_Certificate() + { + //arrange + var definition = new CertificateAuthenticationSchemeDefinition(); + //act + var scheme = definition.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.Certificate); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ComponentDefinitionCollectionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ComponentDefinitionCollectionTests.cs new file mode 100644 index 0000000..60e32b5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ComponentDefinitionCollectionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ComponentDefinitionCollectionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = ComponentDefinitionCollectionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ComponentDefinitionCollection); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ComponentDefinitionCollection); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = ComponentDefinitionCollectionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ComponentDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ComponentDefinitionTests.cs new file mode 100644 index 0000000..f15571f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ComponentDefinitionTests.cs @@ -0,0 +1,58 @@ +// 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.UnitTests.Cases.Core.Models; + +public class ComponentDefinitionTests +{ + + [Fact] + public void TaskDefinition_Should_Be_ComponentDefinition() + { + //arrange & act + var definition = new SetTaskDefinition { Set = new JsonObject { ["k"] = "v" } }; + + //assert + definition.Should().BeAssignableTo(); + } + + [Fact] + public void ErrorDefinition_Should_Be_ReferenceableComponentDefinition() + { + //arrange + var type = "https://errors.com/not-found"; + var title = "Not Found"; + var status = "404"; + + //act + var definition = new ErrorDefinition { Type = type, Title = title, Status = status }; + + //assert + definition.Should().BeAssignableTo(); + definition.Should().BeAssignableTo(); + } + + [Fact] + public void ReferenceableComponentDefinition_Should_Support_Ref() + { + //arrange + var refUri = new Uri("https://schemas.example.com/error.json"); + + //act + var definition = new ErrorDefinition { Type = "t", Title = "t", Status = "400", Ref = refUri }; + + //assert + definition.Ref.Should().Be(refUri); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ConstantBackoffDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ConstantBackoffDefinitionTests.cs new file mode 100644 index 0000000..2516d2e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ConstantBackoffDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ConstantBackoffDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ConstantBackoffDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ConstantBackoffDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ConstantBackoffDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ConstantBackoffDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ContainerLifetimeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ContainerLifetimeDefinitionTests.cs new file mode 100644 index 0000000..e458ebf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ContainerLifetimeDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ContainerLifetimeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ContainerLifetimeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ContainerLifetimeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ContainerLifetimeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ContainerLifetimeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ContainerProcessDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ContainerProcessDefinitionTests.cs new file mode 100644 index 0000000..8216e22 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ContainerProcessDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ContainerProcessDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ContainerProcessDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ContainerProcessDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ContainerProcessDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ContainerProcessDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CorrelationKeyDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CorrelationKeyDefinitionTests.cs new file mode 100644 index 0000000..2710a82 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/CorrelationKeyDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class CorrelationKeyDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = CorrelationKeyDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.CorrelationKeyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.CorrelationKeyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = CorrelationKeyDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DateTimeDescriptorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DateTimeDescriptorTests.cs new file mode 100644 index 0000000..613c510 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DateTimeDescriptorTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class DateTimeDescriptorTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = DateTimeDescriptorFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.DateTimeDescriptor); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.DateTimeDescriptor); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = DateTimeDescriptorFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DigestAuthenticationSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DigestAuthenticationSchemeDefinitionTests.cs new file mode 100644 index 0000000..16f58dd --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DigestAuthenticationSchemeDefinitionTests.cs @@ -0,0 +1,54 @@ +// 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.UnitTests.Cases.Core.Models; + +public class DigestAuthenticationSchemeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = DigestAuthenticationSchemeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.DigestAuthenticationSchemeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.DigestAuthenticationSchemeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = DigestAuthenticationSchemeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_Digest() + { + //arrange + var definition = DigestAuthenticationSchemeDefinitionFactory.Create(); + //act + var scheme = definition.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.Digest); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DoTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DoTaskDefinitionTests.cs new file mode 100644 index 0000000..4b24151 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DoTaskDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class DoTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = DoTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.DoTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.DoTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = DoTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DurationTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DurationTests.cs new file mode 100644 index 0000000..c5cde36 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/DurationTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class DurationTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = DurationFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Duration); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.Duration); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = DurationFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} \ No newline at end of file diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EmitTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EmitTaskDefinitionTests.cs new file mode 100644 index 0000000..6f58044 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EmitTaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class EmitTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = EmitTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EmitTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EmitTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = EmitTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EndpointDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EndpointDefinitionTests.cs new file mode 100644 index 0000000..dccf7b7 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EndpointDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class EndpointDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = EndpointDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EndpointDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EndpointDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = EndpointDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EpochTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EpochTests.cs new file mode 100644 index 0000000..54890c8 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EpochTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class EpochTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = EpochFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Epoch); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.Epoch); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = EpochFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} \ No newline at end of file diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorCatcherDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorCatcherDefinitionTests.cs new file mode 100644 index 0000000..a5a450a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorCatcherDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ErrorCatcherDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ErrorCatcherDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ErrorCatcherDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ErrorCatcherDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ErrorCatcherDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorDefinitionTests.cs new file mode 100644 index 0000000..d31f7c1 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class ErrorDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = ErrorDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ErrorDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ErrorDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = ErrorDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorFilterDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorFilterDefinitionTests.cs new file mode 100644 index 0000000..42a96e6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ErrorFilterDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ErrorFilterDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ErrorFilterDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ErrorFilterDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ErrorFilterDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ErrorFilterDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventConsumptionStrategyDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventConsumptionStrategyDefinitionTests.cs new file mode 100644 index 0000000..25a57cd --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventConsumptionStrategyDefinitionTests.cs @@ -0,0 +1,69 @@ +// 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.UnitTests.Cases.Core.Models; + +public class EventConsumptionStrategyDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_One_Json_Should_Work() + { + //arrange + var toSerialize = EventConsumptionStrategyDefinitionFactory.CreateOne(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EventConsumptionStrategyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EventConsumptionStrategyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_One_Yaml_Should_Work() + { + //arrange + var toSerialize = EventConsumptionStrategyDefinitionFactory.CreateOne(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_All_Json_Should_Work() + { + //arrange + var toSerialize = EventConsumptionStrategyDefinitionFactory.CreateAll(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EventConsumptionStrategyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EventConsumptionStrategyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Any_Json_Should_Work() + { + //arrange + var toSerialize = EventConsumptionStrategyDefinitionFactory.CreateAny(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EventConsumptionStrategyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EventConsumptionStrategyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventDefinitionTests.cs new file mode 100644 index 0000000..d6700a1 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class EventDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = EventDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EventDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EventDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = EventDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventEmissionDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventEmissionDefinitionTests.cs new file mode 100644 index 0000000..9a85e9a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventEmissionDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class EventEmissionDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = EventEmissionDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EventEmissionDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EventEmissionDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = EventEmissionDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventFilterDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventFilterDefinitionTests.cs new file mode 100644 index 0000000..1cc5f2f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/EventFilterDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class EventFilterDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = EventFilterDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.EventFilterDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.EventFilterDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = EventFilterDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExponentialBackoffDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExponentialBackoffDefinitionTests.cs new file mode 100644 index 0000000..2faa59a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExponentialBackoffDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ExponentialBackoffDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ExponentialBackoffDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ExponentialBackoffDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ExponentialBackoffDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ExponentialBackoffDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExtensionDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExtensionDefinitionTests.cs new file mode 100644 index 0000000..7a7ba17 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExtensionDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ExtensionDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = ExtensionDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ExtensionDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ExtensionDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = ExtensionDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExtensionTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExtensionTaskDefinitionTests.cs new file mode 100644 index 0000000..3a2f7d2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExtensionTaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ExtensionTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = ExtensionTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ExtensionTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ExtensionTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = ExtensionTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExternalResourceDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExternalResourceDefinitionTests.cs new file mode 100644 index 0000000..81e9d3f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ExternalResourceDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ExternalResourceDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ExternalResourceDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ExternalResourceDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ExternalResourceDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ExternalResourceDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForLoopDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForLoopDefinitionTests.cs new file mode 100644 index 0000000..73e8d1a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForLoopDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ForLoopDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ForLoopDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ForLoopDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ForLoopDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ForLoopDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForTaskDefinitionTests.cs new file mode 100644 index 0000000..b764d9d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForTaskDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class ForTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = ForTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ForTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ForTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = ForTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForkTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForkTaskDefinitionTests.cs new file mode 100644 index 0000000..f6b23d8 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ForkTaskDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class ForkTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = ForkTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ForkTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ForkTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = ForkTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/GrpcCallDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/GrpcCallDefinitionTests.cs new file mode 100644 index 0000000..620cc82 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/GrpcCallDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class GrpcCallDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = GrpcCallDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.GrpcCallDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.GrpcCallDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = GrpcCallDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/GrpcServiceDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/GrpcServiceDefinitionTests.cs new file mode 100644 index 0000000..6f86b77 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/GrpcServiceDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class GrpcServiceDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = GrpcServiceDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.GrpcServiceDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.GrpcServiceDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = GrpcServiceDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpCallDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpCallDefinitionTests.cs new file mode 100644 index 0000000..e0da12b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpCallDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class HttpCallDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = HttpCallDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.HttpCallDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.HttpCallDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = HttpCallDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpRequestTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpRequestTests.cs new file mode 100644 index 0000000..4641670 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpRequestTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class HttpRequestTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = HttpRequestFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.HttpRequest); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.HttpRequest); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = HttpRequestFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpResponseTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpResponseTests.cs new file mode 100644 index 0000000..ba36b6e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/HttpResponseTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class HttpResponseTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = HttpResponseFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.HttpResponse); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.HttpResponse); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = HttpResponseFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/InputDataModelDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/InputDataModelDefinitionTests.cs new file mode 100644 index 0000000..2d5a200 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/InputDataModelDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class InputDataModelDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = InputDataModelDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.InputDataModelDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.InputDataModelDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = InputDataModelDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/JitterDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/JitterDefinitionTests.cs new file mode 100644 index 0000000..0729462 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/JitterDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class JitterDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = JitterDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.JitterDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.JitterDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = JitterDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/LinearBackoffDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/LinearBackoffDefinitionTests.cs new file mode 100644 index 0000000..2adffb2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/LinearBackoffDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class LinearBackoffDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = LinearBackoffDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.LinearBackoffDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.LinearBackoffDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = LinearBackoffDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ListenTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ListenTaskDefinitionTests.cs new file mode 100644 index 0000000..9dd9f7f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ListenTaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ListenTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ListenTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ListenTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ListenTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ListenTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ListenerDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ListenerDefinitionTests.cs new file mode 100644 index 0000000..92afbf9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ListenerDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ListenerDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ListenerDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ListenerDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ListenerDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ListenerDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationClientDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationClientDefinitionTests.cs new file mode 100644 index 0000000..7191322 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationClientDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class OAuth2AuthenticationClientDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationClientDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OAuth2AuthenticationClientDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OAuth2AuthenticationClientDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationClientDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationEndpointsDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationEndpointsDefinitionTests.cs new file mode 100644 index 0000000..5ead2b6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationEndpointsDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class OAuth2AuthenticationEndpointsDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationEndpointsDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OAuth2AuthenticationEndpointsDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OAuth2AuthenticationEndpointsDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationEndpointsDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationRequestDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationRequestDefinitionTests.cs new file mode 100644 index 0000000..e9ca92b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationRequestDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class OAuth2AuthenticationRequestDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationRequestDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OAuth2AuthenticationRequestDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OAuth2AuthenticationRequestDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationRequestDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationSchemeDefinitionBaseTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationSchemeDefinitionBaseTests.cs new file mode 100644 index 0000000..3a87ac9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationSchemeDefinitionBaseTests.cs @@ -0,0 +1,67 @@ +// 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.UnitTests.Cases.Core.Models; + +public class OAuth2AuthenticationSchemeDefinitionBaseTests +{ + + [Fact] + public void Should_Set_All_Properties() + { + //arrange + var authority = new Uri("https://auth.example.com"); + var grantType = "client_credentials"; + var username = "user"; + var password = "pass"; + var scope = "read"; + var audience = "api"; + var issuer = "https://issuer.example.com"; + + //act + var scheme = new OAuth2AuthenticationSchemeDefinition + { + Authority = authority, + Grant = grantType, + Username = username, + Password = password, + Scopes = [scope], + Audiences = [audience], + Issuers = [issuer] + }; + + //assert + scheme.Authority.Should().Be(authority); + scheme.Grant.Should().Be(grantType); + scheme.Username.Should().Be(username); + scheme.Password.Should().Be(password); + scheme.Scopes.Should().Contain(scope); + scheme.Audiences.Should().Contain(audience); + scheme.Issuers.Should().Contain(issuer); + scheme.Scheme.Should().Be(AuthenticationScheme.OAuth2); + } + + [Fact] + public void OpenIDConnect_Should_Have_Correct_Scheme() + { + //arrange + var authority = new Uri("https://oidc.example.com"); + + //act + var scheme = new OpenIDConnectSchemeDefinition { Authority = authority, Grant = "authorization_code" }; + + //assert + scheme.Scheme.Should().Be(AuthenticationScheme.OpenIDConnect); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationSchemeDefinitionTests.cs new file mode 100644 index 0000000..e74db0a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2AuthenticationSchemeDefinitionTests.cs @@ -0,0 +1,54 @@ +// 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.UnitTests.Cases.Core.Models; + +public class OAuth2AuthenticationSchemeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationSchemeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OAuth2AuthenticationSchemeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OAuth2AuthenticationSchemeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OAuth2AuthenticationSchemeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_OAuth2() + { + //arrange + var definition = OAuth2AuthenticationSchemeDefinitionFactory.Create(); + //act + var scheme = definition.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.OAuth2); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2TokenDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2TokenDefinitionTests.cs new file mode 100644 index 0000000..7987eae --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OAuth2TokenDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class OAuth2TokenDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OAuth2TokenDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OAuth2TokenDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OAuth2TokenDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OAuth2TokenDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OneOfTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OneOfTests.cs new file mode 100644 index 0000000..1e14a37 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OneOfTests.cs @@ -0,0 +1,129 @@ +// 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.UnitTests.Cases.Core.Models; + +public class OneOfTests +{ + [Fact] + public void TryGetAsT1_Should_Return_True_When_T1_Is_Set() + { + //arrange + OneOf oneOf = "hello"; + //act + var result = oneOf.TryGetAsT1(out var value); + //assert + result.Should().BeTrue(); + value.Should().Be("hello"); + } + + [Fact] + public void TryGetAsT1_Should_Return_False_When_T2_Is_Set() + { + //arrange + OneOf oneOf = 42; + //act + var result = oneOf.TryGetAsT1(out _); + //assert + result.Should().BeFalse(); + } + + [Fact] + public void TryGetAsT2_Should_Return_True_When_T2_Is_Set() + { + //arrange + OneOf oneOf = 42; + //act + var result = oneOf.TryGetAsT2(out var value); + //assert + result.Should().BeTrue(); + value.Should().Be(42); + } + + [Fact] + public void TryGetAsT2_Should_Return_False_When_T1_Is_Set() + { + //arrange + OneOf oneOf = "hello"; + //act + var result = oneOf.TryGetAsT2(out _); + //assert + result.Should().BeFalse(); + } + + [Fact] + public void Match_Should_Invoke_F1_When_T1_Is_Set() + { + //arrange + OneOf oneOf = "hello"; + //act + var result = oneOf.Match(s => s.Length, i => i); + //assert + result.Should().Be(5); + } + + [Fact] + public void Match_Should_Invoke_F2_When_T2_Is_Set() + { + //arrange + OneOf oneOf = 42; + //act + var result = oneOf.Match(s => s.Length, i => i); + //assert + result.Should().Be(42); + } + + [Fact] + public void Switch_Should_Invoke_A1_When_T1_Is_Set() + { + //arrange + OneOf oneOf = "hello"; + string? captured = null; + //act + oneOf.Switch(s => captured = s, _ => { }); + //assert + captured.Should().Be("hello"); + } + + [Fact] + public void Switch_Should_Invoke_A2_When_T2_Is_Set() + { + //arrange + OneOf oneOf = 42; + int captured = 0; + //act + oneOf.Switch(_ => { }, i => captured = i); + //assert + captured.Should().Be(42); + } + + [Fact] + public void Implicit_Conversion_From_T1_Should_Work() + { + //arrange & act + OneOf oneOf = "test"; + //assert + oneOf.TryGetAsT1(out var value).Should().BeTrue(); + value.Should().Be("test"); + } + + [Fact] + public void Implicit_Conversion_From_T2_Should_Work() + { + //arrange & act + OneOf oneOf = 99; + //assert + oneOf.TryGetAsT2(out var value).Should().BeTrue(); + value.Should().Be(99); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OpenApiCallDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OpenApiCallDefinitionTests.cs new file mode 100644 index 0000000..cc5f7be --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OpenApiCallDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class OpenApiCallDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OpenApiCallDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OpenApiCallDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OpenApiCallDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OpenApiCallDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OpenIDConnectSchemeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OpenIDConnectSchemeDefinitionTests.cs new file mode 100644 index 0000000..0d2b802 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OpenIDConnectSchemeDefinitionTests.cs @@ -0,0 +1,54 @@ +// 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.UnitTests.Cases.Core.Models; + +public class OpenIDConnectSchemeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OpenIDConnectSchemeDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OpenIDConnectSchemeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OpenIDConnectSchemeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OpenIDConnectSchemeDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Scheme_Should_Return_OpenIDConnect() + { + //arrange + var definition = OpenIDConnectSchemeDefinitionFactory.Create(); + //act + var scheme = definition.Scheme; + //assert + scheme.Should().Be(AuthenticationScheme.OpenIDConnect); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OutputDataModelDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OutputDataModelDefinitionTests.cs new file mode 100644 index 0000000..439761b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/OutputDataModelDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class OutputDataModelDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OutputDataModelDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.OutputDataModelDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.OutputDataModelDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OutputDataModelDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ProcessDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ProcessDefinitionTests.cs new file mode 100644 index 0000000..3441a52 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ProcessDefinitionTests.cs @@ -0,0 +1,81 @@ +// 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.UnitTests.Cases.Core.Models; + +public class ProcessDefinitionTests +{ + + [Fact] + public void ContainerProcessDefinition_Should_Be_ProcessDefinition() + { + //arrange + var image = "alpine:latest"; + + //act + var definition = new ContainerProcessDefinition { Image = image }; + + //assert + definition.Should().BeAssignableTo(); + definition.Image.Should().Be(image); + } + + [Fact] + public void ShellProcessDefinition_Should_Be_ProcessDefinition() + { + //arrange + var command = "echo hello"; + + //act + var definition = new ShellProcessDefinition { Command = command }; + + //assert + definition.Should().BeAssignableTo(); + definition.Command.Should().Be(command); + } + + [Fact] + public void ScriptProcessDefinition_Should_Be_ProcessDefinition() + { + //arrange + var language = "javascript"; + var code = "console.log('hello')"; + + //act + var definition = new ScriptProcessDefinition { Language = language, Code = code }; + + //assert + definition.Should().BeAssignableTo(); + definition.Language.Should().Be(language); + definition.Code.Should().Be(code); + } + + [Fact] + public void WorkflowProcessDefinition_Should_Be_ProcessDefinition() + { + //arrange + var ns = "default"; + var name = "sub-workflow"; + var version = "1.0.0"; + + //act + var definition = new WorkflowProcessDefinition { Namespace = ns, Name = name, Version = version }; + + //assert + definition.Should().BeAssignableTo(); + definition.Namespace.Should().Be(ns); + definition.Name.Should().Be(name); + definition.Version.Should().Be(version); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ProcessTypeDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ProcessTypeDefinitionTests.cs new file mode 100644 index 0000000..8be58fb --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ProcessTypeDefinitionTests.cs @@ -0,0 +1,126 @@ +// 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.UnitTests.Cases.Core.Models; + +public class ProcessTypeDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Container_Json_Should_Work() + { + //arrange + var toSerialize = ProcessTypeDefinitionFactory.CreateContainer(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ProcessTypeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ProcessTypeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Container_Yaml_Should_Work() + { + //arrange + var toSerialize = ProcessTypeDefinitionFactory.CreateContainer(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Shell_Json_Should_Work() + { + //arrange + var toSerialize = ProcessTypeDefinitionFactory.CreateShell(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ProcessTypeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ProcessTypeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Script_Json_Should_Work() + { + //arrange + var toSerialize = ProcessTypeDefinitionFactory.CreateScript(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ProcessTypeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ProcessTypeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Workflow_Json_Should_Work() + { + //arrange + var toSerialize = ProcessTypeDefinitionFactory.CreateWorkflow(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ProcessTypeDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ProcessTypeDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void ProcessType_Should_Return_Container_When_Container_Is_Set() + { + //arrange + var definition = ProcessTypeDefinitionFactory.CreateContainer(); + //act + var processType = definition.ProcessType; + //assert + processType.Should().Be(ProcessType.Container); + } + + [Fact] + public void ProcessType_Should_Return_Shell_When_Shell_Is_Set() + { + //arrange + var definition = ProcessTypeDefinitionFactory.CreateShell(); + //act + var processType = definition.ProcessType; + //assert + processType.Should().Be(ProcessType.Shell); + } + + [Fact] + public void ProcessType_Should_Return_Script_When_Script_Is_Set() + { + //arrange + var definition = ProcessTypeDefinitionFactory.CreateScript(); + //act + var processType = definition.ProcessType; + //assert + processType.Should().Be(ProcessType.Script); + } + + [Fact] + public void ProcessType_Should_Return_Workflow_When_Workflow_Is_Set() + { + //arrange + var definition = ProcessTypeDefinitionFactory.CreateWorkflow(); + //act + var processType = definition.ProcessType; + //assert + processType.Should().Be(ProcessType.Workflow); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RaiseErrorDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RaiseErrorDefinitionTests.cs new file mode 100644 index 0000000..403bed3 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RaiseErrorDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class RaiseErrorDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = RaiseErrorDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RaiseErrorDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RaiseErrorDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = RaiseErrorDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RaiseTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RaiseTaskDefinitionTests.cs new file mode 100644 index 0000000..a3257be --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RaiseTaskDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class RaiseTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = RaiseTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RaiseTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RaiseTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = RaiseTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryAttemptLimitDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryAttemptLimitDefinitionTests.cs new file mode 100644 index 0000000..1f1f132 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryAttemptLimitDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class RetryAttemptLimitDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = RetryAttemptLimitDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RetryAttemptLimitDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RetryAttemptLimitDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = RetryAttemptLimitDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryPolicyDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryPolicyDefinitionTests.cs new file mode 100644 index 0000000..5cd1bfe --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryPolicyDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class RetryPolicyDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = RetryPolicyDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RetryPolicyDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RetryPolicyDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = RetryPolicyDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryPolicyLimitDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryPolicyLimitDefinitionTests.cs new file mode 100644 index 0000000..d897e88 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RetryPolicyLimitDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class RetryPolicyLimitDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = RetryPolicyLimitDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RetryPolicyLimitDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RetryPolicyLimitDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = RetryPolicyLimitDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RunTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RunTaskDefinitionTests.cs new file mode 100644 index 0000000..9a6b261 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RunTaskDefinitionTests.cs @@ -0,0 +1,82 @@ +// 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.UnitTests.Cases.Core.Models; + +public class RunTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Container_Json_Should_Work() + { + //arrange + var toSerialize = RunTaskDefinitionFactory.CreateContainer(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RunTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RunTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Container_Yaml_Should_Work() + { + //arrange + var toSerialize = RunTaskDefinitionFactory.CreateContainer(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Shell_Json_Should_Work() + { + //arrange + var toSerialize = RunTaskDefinitionFactory.CreateShell(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RunTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RunTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Script_Json_Should_Work() + { + //arrange + var toSerialize = RunTaskDefinitionFactory.CreateScript(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RunTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RunTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Workflow_Json_Should_Work() + { + //arrange + var toSerialize = RunTaskDefinitionFactory.CreateWorkflow(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RunTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RunTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RuntimeDescriptorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RuntimeDescriptorTests.cs new file mode 100644 index 0000000..a0c6f31 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RuntimeDescriptorTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class RuntimeDescriptorTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = RuntimeDescriptorFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RuntimeDescriptor); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RuntimeDescriptor); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = RuntimeDescriptorFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RuntimeExpressionEvaluationConfigurationTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RuntimeExpressionEvaluationConfigurationTests.cs new file mode 100644 index 0000000..0bb9528 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/RuntimeExpressionEvaluationConfigurationTests.cs @@ -0,0 +1,52 @@ +// 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.UnitTests.Cases.Core.Models; + +public class RuntimeExpressionEvaluationConfigurationTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = RuntimeExpressionEvaluationConfigurationFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.RuntimeExpressionEvaluationConfiguration); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.RuntimeExpressionEvaluationConfiguration); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = RuntimeExpressionEvaluationConfigurationFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Default_Language_Should_Be_JQ() + { + //arrange & act + var config = new RuntimeExpressionEvaluationConfiguration(); + //assert + config.Language.Should().Be(RuntimeExpressions.Languages.JQ); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SchemaDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SchemaDefinitionTests.cs new file mode 100644 index 0000000..dac02c5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SchemaDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class SchemaDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = SchemaDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.SchemaDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.SchemaDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = SchemaDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ScriptProcessDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ScriptProcessDefinitionTests.cs new file mode 100644 index 0000000..2a8a2b5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ScriptProcessDefinitionTests.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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ScriptProcessDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ScriptProcessDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ScriptProcessDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ScriptProcessDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ScriptProcessDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SetTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SetTaskDefinitionTests.cs new file mode 100644 index 0000000..e05acf2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SetTaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class SetTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = TaskDefinitionFactory.CreateSetTask(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.SetTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.SetTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = TaskDefinitionFactory.CreateSetTask(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ShellProcessDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ShellProcessDefinitionTests.cs new file mode 100644 index 0000000..2fd6eba --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/ShellProcessDefinitionTests.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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class ShellProcessDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = ShellProcessDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.ShellProcessDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.ShellProcessDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = ShellProcessDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SubscriptionIteratorDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SubscriptionIteratorDefinitionTests.cs new file mode 100644 index 0000000..9fb7fd4 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SubscriptionIteratorDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class SubscriptionIteratorDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = SubscriptionIteratorDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.SubscriptionIteratorDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.SubscriptionIteratorDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = SubscriptionIteratorDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SwitchCaseDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SwitchCaseDefinitionTests.cs new file mode 100644 index 0000000..c753299 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SwitchCaseDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class SwitchCaseDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = SwitchCaseDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.SwitchCaseDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.SwitchCaseDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = SwitchCaseDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SwitchTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SwitchTaskDefinitionTests.cs new file mode 100644 index 0000000..c83e2a0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/SwitchTaskDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class SwitchTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = SwitchTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.SwitchTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.SwitchTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = SwitchTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TaskDefinitionTests.cs new file mode 100644 index 0000000..34a7acf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class TaskDefinitionTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = TaskDefinitionFactory.CreateSetTask(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.TaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.TaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = TaskDefinitionFactory.CreateSetTask(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TaskDescriptorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TaskDescriptorTests.cs new file mode 100644 index 0000000..de8868a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TaskDescriptorTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class TaskDescriptorTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = TaskDescriptorFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.TaskDescriptor); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.TaskDescriptor); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = TaskDescriptorFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TimeoutDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TimeoutDefinitionTests.cs new file mode 100644 index 0000000..b3ac446 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TimeoutDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class TimeoutDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = TimeoutDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.TimeoutDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.TimeoutDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = TimeoutDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TryTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TryTaskDefinitionTests.cs new file mode 100644 index 0000000..0b5b98d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/TryTaskDefinitionTests.cs @@ -0,0 +1,42 @@ +// 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.UnitTests.Cases.Core.Models; + +public class TryTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { //arrange + var toSerialize = TryTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.TryTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.TryTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = TryTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WaitTaskDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WaitTaskDefinitionTests.cs new file mode 100644 index 0000000..9d87f1f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WaitTaskDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class WaitTaskDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = WaitTaskDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WaitTaskDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WaitTaskDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = WaitTaskDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDefinitionMetadataTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDefinitionMetadataTests.cs new file mode 100644 index 0000000..40b16bb --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDefinitionMetadataTests.cs @@ -0,0 +1,57 @@ +// 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.UnitTests.Cases.Core.Models; + +public class WorkflowDefinitionMetadataTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowDefinitionMetadataFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowDefinitionMetadata); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowDefinitionMetadata); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = WorkflowDefinitionMetadataFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Default_Namespace_Should_Be_Default() + { + //arrange + var metadata = new WorkflowDefinitionMetadata + { + Dsl = "1.0.0", + Name = "test", + Version = "0.1.0" + }; + //assert + metadata.Namespace.Should().Be(WorkflowDefinitionMetadata.DefaultNamespace); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDefinitionTests.cs new file mode 100644 index 0000000..c2951e3 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDefinitionTests.cs @@ -0,0 +1,69 @@ +// 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.UnitTests.Cases.Core.Models; + +public class WorkflowDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Basic_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Basic_Yaml_Should_Work() + { + //arrange + var toSerialize = WorkflowDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Minimal_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowDefinitionFactory.CreateMinimal(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Minimal_Yaml_Should_Work() + { + //arrange + var toSerialize = WorkflowDefinitionFactory.CreateMinimal(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDescriptorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDescriptorTests.cs new file mode 100644 index 0000000..0a778b9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowDescriptorTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class WorkflowDescriptorTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowDescriptorFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowDescriptor); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowDescriptor); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = WorkflowDescriptorFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowProcessDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowProcessDefinitionTests.cs new file mode 100644 index 0000000..62e839f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowProcessDefinitionTests.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. + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Core.Models; + +public class WorkflowProcessDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowProcessDefinitionFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowProcessDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowProcessDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = WorkflowProcessDefinitionFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowScheduleDefinitionTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowScheduleDefinitionTests.cs new file mode 100644 index 0000000..46f848c --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Core/Models/WorkflowScheduleDefinitionTests.cs @@ -0,0 +1,82 @@ +// 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.UnitTests.Cases.Core.Models; + +public class WorkflowScheduleDefinitionTests +{ + [Fact] + public void Serialize_And_Deserialize_Cron_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowScheduleDefinitionFactory.CreateWithCron(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowScheduleDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowScheduleDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Cron_Yaml_Should_Work() + { + //arrange + var toSerialize = WorkflowScheduleDefinitionFactory.CreateWithCron(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, JsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, JsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Every_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowScheduleDefinitionFactory.CreateWithEvery(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowScheduleDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowScheduleDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_After_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowScheduleDefinitionFactory.CreateWithAfter(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowScheduleDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowScheduleDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Event_Json_Should_Work() + { + //arrange + var toSerialize = WorkflowScheduleDefinitionFactory.CreateWithEvent(); + //act + var json = JsonSerializer.Serialize(toSerialize, JsonSerializationContext.Default.WorkflowScheduleDefinition); + var deserialized = JsonSerializer.Deserialize(json, JsonSerializationContext.Default.WorkflowScheduleDefinition); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeJsonEquivalentTo(toSerialize); + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/OneOfIOTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/OneOfIOTests.cs deleted file mode 100644 index afe7ee0..0000000 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/OneOfIOTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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 Microsoft.Extensions.DependencyInjection; -using Neuroglia.Serialization; -using ServerlessWorkflow.Sdk.IO; - -namespace ServerlessWorkflow.Sdk.UnitTests.Cases.IO; - -public class OneOfIOTests - : IDisposable -{ - - public OneOfIOTests() - { - var services = new ServiceCollection(); - services.AddServerlessWorkflowIO(); - this.ServiceProvider = services.BuildServiceProvider(); - } - - protected ServiceProvider ServiceProvider { get; } - - protected IJsonSerializer JsonSerializer => this.ServiceProvider.GetRequiredService(); - - protected IYamlSerializer YamlSerializer => this.ServiceProvider.GetRequiredService(); - - [Fact] - public void Serialize_And_Deserialize_OneOf_ToFromJson_Should_Work() - { - //arrange - var uri = new Uri("https://test.com"); - var endpoint = new EndpointDefinition() { Uri = uri }; - var t1OneOf = new OneOf(endpoint); - var t2OneOf = new OneOf(uri); - - //act - var t1OneOfJson = this.JsonSerializer.SerializeToText(t1OneOf); - var t2OneOfJson = this.JsonSerializer.SerializeToText(t2OneOf); - var t1OneOfDeserialized = this.JsonSerializer.Deserialize>(t1OneOfJson); - var t2OneOfDeserialized = this.JsonSerializer.Deserialize>(t2OneOfJson); - - //assert - t1OneOfJson.Should().Be(this.JsonSerializer.SerializeToText(endpoint)); - t2OneOfJson.Should().Be(this.JsonSerializer.SerializeToText(uri)); - t1OneOfDeserialized.Should().BeEquivalentTo(t1OneOf); - t2OneOfDeserialized.Should().BeEquivalentTo(t2OneOf); - } - - [Fact] - public void Serialize_And_Deserialize_OneOf_ToFromYaml_Should_Work() - { - //arrange - var uri = new Uri("https://test.com"); - var endpoint = new EndpointDefinition() { Uri = uri }; - var t1OneOf = new OneOf(endpoint); - var t2OneOf = new OneOf(uri); - - //act - var t1OneOfYaml = this.YamlSerializer.SerializeToText(t1OneOf); - var t2OneOfYaml = this.YamlSerializer.SerializeToText(t2OneOf); - var t1OneOfDeserialized = this.YamlSerializer.Deserialize>(t1OneOfYaml); - var t2OneOfDeserialized = this.YamlSerializer.Deserialize>(t2OneOfYaml); - - //assert - t1OneOfYaml.Should().Be(this.YamlSerializer.SerializeToText(endpoint)); - t2OneOfYaml.Should().Be(this.YamlSerializer.SerializeToText(uri)); - t1OneOfDeserialized.Should().BeEquivalentTo(t1OneOf); - t2OneOfDeserialized.Should().BeEquivalentTo(t2OneOf); - } - - void IDisposable.Dispose() - { - this.ServiceProvider.Dispose(); - GC.SuppressFinalize(this); - } - -} - diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/WorkflowDefinitionIOTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/WorkflowDefinitionIOTests.cs index a3a37d0..ad7ba0c 100644 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/WorkflowDefinitionIOTests.cs +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/IO/WorkflowDefinitionIOTests.cs @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.IO; - namespace ServerlessWorkflow.Sdk.UnitTests.Cases.IO; public class WorkflowDefinitionIOTests @@ -28,10 +26,10 @@ public async Task WriteThenRead_Workflow_Definition_ToFrom_Yaml_Should_Work() var reader = WorkflowDefinitionReader.Create(); //act - await writer.WriteAsync(toSerialize, stream, WorkflowDefinitionFormat.Yaml); - await stream.FlushAsync(); + await writer.WriteAsync(toSerialize, stream, WorkflowDefinitionFormat.Yaml, TestContext.Current.CancellationToken); + await stream.FlushAsync(TestContext.Current.CancellationToken); stream.Position = 0; - var deserialized = await reader.ReadAsync(stream); + var deserialized = await reader.ReadAsync(stream, new(), TestContext.Current.CancellationToken); //assert deserialized.Should().NotBeNull(); @@ -48,10 +46,10 @@ public async Task WriteThenRead_Workflow_Definition_ToFrom_Json_Should_Work() var reader = WorkflowDefinitionReader.Create(); //act - await writer.WriteAsync(toSerialize, stream, WorkflowDefinitionFormat.Json); - await stream.FlushAsync(); + await writer.WriteAsync(toSerialize, stream, WorkflowDefinitionFormat.Json, TestContext.Current.CancellationToken); + await stream.FlushAsync(TestContext.Current.CancellationToken); stream.Position = 0; - var deserialized = await reader.ReadAsync(stream); + var deserialized = await reader.ReadAsync(stream, new(), TestContext.Current.CancellationToken); //assert deserialized.Should().NotBeNull(); diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/AuthenticationResultTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/AuthenticationResultTests.cs new file mode 100644 index 0000000..65a0368 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/AuthenticationResultTests.cs @@ -0,0 +1,80 @@ +// 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.UnitTests.Cases.Runtime.Models; + +public class AuthenticationResultTests +{ + + [Fact] + public void Should_Set_Scheme_And_Value() + { + //arrange + var scheme = "Bearer"; + var value = "eyJhbGciOiJSUzI1NiJ9..."; + + //act + var result = new AuthenticationResult(scheme, value); + + //assert + result.Scheme.Should().Be(scheme); + result.Value.Should().Be(value); + } + + [Fact] + public void Should_Implement_IAuthenticationResult() + { + //arrange + var scheme = "Basic"; + var value = "dXNlcjpwYXNz"; + + //act + IAuthenticationResult result = new AuthenticationResult(scheme, value); + + //assert + result.Scheme.Should().Be(scheme); + result.Value.Should().Be(value); + } + + [Fact] + public void Should_Support_Record_Equality() + { + //arrange + var scheme = "Bearer"; + var value = "token123"; + var result1 = new AuthenticationResult(scheme, value); + var result2 = new AuthenticationResult(scheme, value); + + //act & assert + result1.Should().Be(result2); + } + + [Fact] + public void Should_Support_Record_With_Expression() + { + //arrange + var originalScheme = "Bearer"; + var originalValue = "old-token"; + var newValue = "new-token"; + var original = new AuthenticationResult(originalScheme, originalValue); + + //act + var updated = original with { Value = newValue }; + + //assert + updated.Scheme.Should().Be(originalScheme); + updated.Value.Should().Be(newValue); + original.Value.Should().Be(originalValue); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/CloudEventTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/CloudEventTests.cs new file mode 100644 index 0000000..17039de --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/CloudEventTests.cs @@ -0,0 +1,105 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; +using RuntimeJsonSerializationContext = ServerlessWorkflow.Sdk.Runtime.Serialization.Json.JsonSerializationContext; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class CloudEventTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = CloudEventFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.CloudEvent); + var deserialized = JsonSerializer.Deserialize(json, RuntimeJsonSerializationContext.Default.CloudEvent); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.Id.Should().Be(toSerialize.Id); + deserialized.SpecVersion.Should().Be(toSerialize.SpecVersion); + deserialized.Source.Should().Be(toSerialize.Source); + deserialized.Type.Should().Be(toSerialize.Type); + deserialized.Subject.Should().Be(toSerialize.Subject); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = CloudEventFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, RuntimeJsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.Id.Should().Be(toSerialize.Id); + deserialized.Source.Should().Be(toSerialize.Source); + deserialized.Type.Should().Be(toSerialize.Type); + } + + [Fact] + public void GetAttribute_Should_Return_Known_Attributes() + { + //arrange + var cloudEvent = CloudEventFactory.Create(); + //act & assert + cloudEvent.GetAttribute(CloudEventAttributes.Id).Should().Be(cloudEvent.Id); + cloudEvent.GetAttribute(CloudEventAttributes.SpecVersion).Should().Be(cloudEvent.SpecVersion); + cloudEvent.GetAttribute(CloudEventAttributes.Source).Should().Be(cloudEvent.Source); + cloudEvent.GetAttribute(CloudEventAttributes.Type).Should().Be(cloudEvent.Type); + cloudEvent.GetAttribute(CloudEventAttributes.Subject).Should().Be(cloudEvent.Subject); + cloudEvent.GetAttribute(CloudEventAttributes.DataContentType).Should().Be(cloudEvent.DataContentType); + cloudEvent.GetAttribute(CloudEventAttributes.DataSchema).Should().Be(cloudEvent.DataSchema); + cloudEvent.GetAttribute(CloudEventAttributes.Data).Should().Be(cloudEvent.Data); + } + + [Fact] + public void GetAttribute_Should_Return_Null_For_Unknown_Attributes() + { + //arrange + var cloudEvent = CloudEventFactory.Create(); + //act + var value = cloudEvent.GetAttribute("nonexistent"); + //assert + value.Should().BeNull(); + } + + [Fact] + public void ToString_Should_Return_Id() + { + //arrange + var cloudEvent = CloudEventFactory.Create(); + //act + var result = cloudEvent.ToString(); + //assert + result.Should().Be(cloudEvent.Id); + } + + [Fact] + public void Default_Values_Should_Be_Set() + { + //arrange & act + var cloudEvent = new CloudEvent { Source = new Uri("https://example.com"), Type = "test" }; + //assert + cloudEvent.Id.Should().NotBeNullOrWhiteSpace(); + cloudEvent.SpecVersion.Should().Be(CloudEvent.DefaultVersion); + cloudEvent.DataContentType.Should().Be("application/json"); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/OAuth2TokenTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/OAuth2TokenTests.cs new file mode 100644 index 0000000..7fdc703 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/OAuth2TokenTests.cs @@ -0,0 +1,104 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; +using RuntimeJsonSerializationContext = ServerlessWorkflow.Sdk.Runtime.Serialization.Json.JsonSerializationContext; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class OAuth2TokenTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = OAuth2TokenFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.OAuth2Token); + var deserialized = JsonSerializer.Deserialize(json, RuntimeJsonSerializationContext.Default.OAuth2Token); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.AccessToken.Should().Be(toSerialize.AccessToken); + deserialized.TokenType.Should().Be(toSerialize.TokenType); + deserialized.RefreshToken.Should().Be(toSerialize.RefreshToken); + deserialized.Ttl.Should().Be(toSerialize.Ttl); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = OAuth2TokenFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, RuntimeJsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.AccessToken.Should().Be(toSerialize.AccessToken); + deserialized.TokenType.Should().Be(toSerialize.TokenType); + } + + [Fact] + public void HasExpired_Should_Return_False_When_ExpiresAt_Is_In_Future() + { + //arrange + var token = new OAuth2Token + { + ExpiresAt = DateTime.UtcNow.AddHours(1) + }; + //act & assert + token.HasExpired.Should().BeFalse(); + } + + [Fact] + public void HasExpired_Should_Return_True_When_ExpiresAt_Is_In_Past() + { + //arrange + var token = new OAuth2Token + { + ExpiresAt = DateTime.UtcNow.AddHours(-1) + }; + //act & assert + token.HasExpired.Should().BeTrue(); + } + + [Fact] + public void HasExpired_Should_Use_Ttl_When_ExpiresAt_Is_Not_Set() + { + //arrange + var token = new OAuth2Token + { + CreatedAt = DateTime.UtcNow.AddSeconds(-10), + Ttl = 5 + }; + //act & assert + token.HasExpired.Should().BeTrue(); + } + + [Fact] + public void HasExpired_Should_Return_False_When_Within_Ttl() + { + //arrange + var token = new OAuth2Token + { + CreatedAt = DateTime.UtcNow, + Ttl = 3600 + }; + //act & assert + token.HasExpired.Should().BeFalse(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/RuntimeErrorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/RuntimeErrorTests.cs new file mode 100644 index 0000000..ca47578 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/RuntimeErrorTests.cs @@ -0,0 +1,100 @@ +// 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 SdkJsonSerializationContext = ServerlessWorkflow.Sdk.Serialization.Json.JsonSerializationContext; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class RuntimeErrorTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = RuntimeErrorFactory.Create(); + //act + var json = JsonSerializer.Serialize(toSerialize, SdkJsonSerializationContext.Default.Error); + var deserialized = JsonSerializer.Deserialize(json, SdkJsonSerializationContext.Default.Error); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = RuntimeErrorFactory.Create(); + //act + var yaml = YamlSerializer.Serialize(toSerialize, SdkJsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, SdkJsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().BeEquivalentTo(toSerialize); + } + + [Fact] + public void Communication_Factory_Should_Create_Communication_Error() + { + //arrange + var instance = new Uri("/tasks/456", UriKind.RelativeOrAbsolute); + //act + var error = Error.Communication(instance, 502, "Bad Gateway"); + //assert + error.Type.Should().Be(ErrorType.Communication); + error.Title.Should().Be(ErrorTitle.Communication); + error.Status.Should().Be(502); + error.Detail.Should().Be("Bad Gateway"); + error.Instance.Should().Be(instance); + } + + [Fact] + public void Runtime_Factory_Should_Create_Runtime_Error() + { + //arrange + var instance = new Uri("/tasks/789", UriKind.RelativeOrAbsolute); + //act + var error = Error.Runtime(instance, "Something went wrong"); + //assert + error.Type.Should().Be(ErrorType.Runtime); + error.Title.Should().Be(ErrorTitle.Runtime); + error.Status.Should().Be(ErrorStatus.Runtime); + error.Detail.Should().Be("Something went wrong"); + } + + [Fact] + public void Validation_Factory_Should_Create_Validation_Error() + { + //arrange + var instance = new Uri("/tasks/abc", UriKind.RelativeOrAbsolute); + //act + var error = Error.Validation(instance, "Invalid input"); + //assert + error.Type.Should().Be(ErrorType.Validation); + error.Status.Should().Be(ErrorStatus.Validation); + } + + [Fact] + public void Configuration_Factory_Should_Create_Configuration_Error() + { + //arrange + var instance = new Uri("/tasks/def", UriKind.RelativeOrAbsolute); + //act + var error = Error.Configuration(instance, "Missing config"); + //assert + error.Type.Should().Be(ErrorType.Configuration); + error.Status.Should().Be(ErrorStatus.Configuration); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/SchemaValidationResultTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/SchemaValidationResultTests.cs new file mode 100644 index 0000000..9f1fb91 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/SchemaValidationResultTests.cs @@ -0,0 +1,98 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; +using RuntimeJsonSerializationContext = ServerlessWorkflow.Sdk.Runtime.Serialization.Json.JsonSerializationContext; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class SchemaValidationResultTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = SchemaValidationResult.Failed(new Dictionary> + { + ["$.name"] = ["Field 'name' is required"] + }); + //act + var json = JsonSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.SchemaValidationResult); + var deserialized = JsonSerializer.Deserialize(json, RuntimeJsonSerializationContext.Default.SchemaValidationResult); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.IsValid.Should().BeFalse(); + deserialized.Errors.Should().ContainKey("$.name"); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = SchemaValidationResult.Failed(new Dictionary> + { + ["$.name"] = ["Field 'name' is required"] + }); + //act + var yaml = YamlSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, RuntimeJsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.IsValid.Should().BeFalse(); + } + + [Fact] + public void Succeeded_Should_Create_Valid_Result() + { + //act + var result = SchemaValidationResult.Succeeded(); + //assert + result.IsValid.Should().BeTrue(); + result.Errors.Should().BeNull(); + } + + [Fact] + public void Failed_With_Dictionary_Should_Create_Invalid_Result() + { + //arrange + var errors = new Dictionary> + { + ["$.age"] = ["Must be a positive integer", "Must be less than 150"] + }; + //act + var result = SchemaValidationResult.Failed(errors); + //assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().NotBeNull(); + result.Errors!["$.age"].Should().HaveCount(2); + } + + [Fact] + public void Failed_With_Enumerable_Should_Create_Invalid_Result() + { + //arrange + var errors = new List>> + { + new("$.email", ["Invalid email format"]) + }; + //act + var result = SchemaValidationResult.Failed(errors); + //assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainKey("$.email"); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/TaskInstanceTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/TaskInstanceTests.cs new file mode 100644 index 0000000..23b619a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/TaskInstanceTests.cs @@ -0,0 +1,203 @@ +// 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 RuntimeJsonSerializationContext = ServerlessWorkflow.Sdk.Runtime.Serialization.Json.JsonSerializationContext; +using SdkTaskStatus = ServerlessWorkflow.Sdk.Runtime.TaskStatus; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class TaskInstanceTests +{ + + static TaskInstance CreateInstance() => new() + { + WorkflowId = "wf-1", + Reference = JsonPointer.Parse("/test"), + Input = new JsonObject { ["key"] = "value" } + }; + + static Error CreateError() => Error.Runtime(new Uri("https://example.com/errors/test"), "Something went wrong"); + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = CreateInstance(); + //act + var json = JsonSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.TaskInstance); + var deserialized = JsonSerializer.Deserialize(json, RuntimeJsonSerializationContext.Default.TaskInstance); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.Id.Should().Be(toSerialize.Id); + deserialized.WorkflowId.Should().Be(toSerialize.WorkflowId); + deserialized.Reference.Should().Be(toSerialize.Reference); + } + + [Fact] + public async Task StartAsync_Should_Set_Status_And_StartedAt_And_Add_Run() + { + //arrange + var instance = CreateInstance(); + //act + await instance.StartAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Running); + instance.StartedAt.Should().NotBeNull(); + instance.Runs.Should().NotBeNull(); + instance.Runs!.Count.Should().Be(1); + } + + [Fact] + public async Task SuspendAsync_Should_Set_Status_And_End_Current_Run() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + //act + await instance.SuspendAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Suspended); + instance.Runs.Should().NotBeNull(); + var run = instance.Runs!.Last(); + run.EndedAt.Should().NotBeNull(); + run.Outcome.Should().Be(SdkTaskStatus.Suspended); + } + + [Fact] + public async Task ResumeAsync_Should_Set_Status_And_Add_New_Run() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + await instance.SuspendAsync(TestContext.Current.CancellationToken); + //act + await instance.ResumeAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Running); + instance.Runs.Should().NotBeNull(); + instance.Runs!.Count.Should().Be(2); + } + + [Fact] + public async Task SetOutputAsync_Should_Set_Completed_Status_And_Output_And_Next() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + var output = new JsonObject { ["result"] = "done" }; + var next = "/next-task"; + //act + await instance.SetOutputAsync(output, next, TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Completed); + instance.Output.Should().NotBeNull(); + instance.Next.Should().Be(next); + instance.EndedAt.Should().NotBeNull(); + } + + [Fact] + public async Task SetErrorAsync_Should_Set_Faulted_Status_And_EndedAt() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + var error = CreateError(); + //act + await instance.SetErrorAsync(error, TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Faulted); + instance.EndedAt.Should().NotBeNull(); + } + + [Fact] + public async Task SkipAsync_Should_Set_Skipped_Status_And_Output_And_Next() + { + //arrange + var instance = CreateInstance(); + var output = new JsonObject { ["skipped"] = true }; + var next = "/after-skip"; + //act + await instance.SkipAsync(output, next, TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Skipped); + instance.Output.Should().NotBeNull(); + instance.Next.Should().Be(next); + instance.EndedAt.Should().NotBeNull(); + } + + [Fact] + public async Task CancelAsync_Should_Set_Cancelled_Status_And_EndedAt() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + //act + await instance.CancelAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Cancelled); + instance.EndedAt.Should().NotBeNull(); + } + + [Fact] + public async Task RetryAsync_Should_Add_RetryAttempt_And_Set_Running_Status() + { + //arrange + var instance = CreateInstance(); + var error = CreateError(); + //act + await instance.RetryAsync(error, TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(SdkTaskStatus.Running); + instance.Retries.Should().NotBeNull(); + instance.Retries!.Count.Should().Be(1); + instance.Retries!.First().Number.Should().Be(1u); + instance.Retries!.First().Cause.Should().Be(error); + instance.Runs.Should().NotBeNull(); + instance.Runs!.Count.Should().Be(1); + instance.StartedAt.Should().NotBeNull(); + } + + [Fact] + public async Task RetryAsync_Multiple_Times_Should_Increment_Attempt_Number() + { + //arrange + var instance = CreateInstance(); + var error1 = CreateError(); + var error2 = CreateError(); + //act + await instance.RetryAsync(error1, TestContext.Current.CancellationToken); + await instance.RetryAsync(error2, TestContext.Current.CancellationToken); + //assert + instance.Retries.Should().NotBeNull(); + instance.Retries!.Count.Should().Be(2); + instance.Retries!.Last().Number.Should().Be(2u); + instance.Runs!.Count.Should().Be(2); + } + + [Fact] + public void Default_Values_Should_Be_Set() + { + //arrange & act + var instance = CreateInstance(); + //assert + instance.Id.Should().NotBeNullOrWhiteSpace(); + instance.WorkflowId.Should().Be("wf-1"); + instance.Status.Should().BeNull(); + instance.StartedAt.Should().BeNull(); + instance.EndedAt.Should().BeNull(); + instance.Runs.Should().BeNull(); + instance.Retries.Should().BeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/TaskLifeCycleEventTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/TaskLifeCycleEventTests.cs new file mode 100644 index 0000000..93e4cc9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/TaskLifeCycleEventTests.cs @@ -0,0 +1,73 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; +using RuntimeJsonSerializationContext = ServerlessWorkflow.Sdk.Runtime.Serialization.Json.JsonSerializationContext; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class TaskLifeCycleEventTests +{ + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = new TaskLifeCycleEvent(TaskLifeCycleEventType.Completed, new JsonObject { ["duration"] = 1234 }); + //act + var json = JsonSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.TaskLifeCycleEvent); + var deserialized = JsonSerializer.Deserialize(json, RuntimeJsonSerializationContext.Default.TaskLifeCycleEvent); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.Type.Should().Be(TaskLifeCycleEventType.Completed); + deserialized.Data.Should().NotBeNull(); + } + + [Fact] + public void Serialize_And_Deserialize_Yaml_Should_Work() + { + //arrange + var toSerialize = new TaskLifeCycleEvent(TaskLifeCycleEventType.Completed, new JsonObject { ["duration"] = 1234 }); + //act + var yaml = YamlSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.Options); + var deserialized = YamlSerializer.Deserialize(yaml, RuntimeJsonSerializationContext.Default.Options); + //assert + yaml.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.Type.Should().Be(TaskLifeCycleEventType.Completed); + } + + [Fact] + public void Constructor_Should_Set_Properties() + { + //act + var evt = new TaskLifeCycleEvent(TaskLifeCycleEventType.Running); + //assert + evt.Type.Should().Be(TaskLifeCycleEventType.Running); + evt.Data.Should().BeNull(); + } + + [Fact] + public void Constructor_With_Data_Should_Set_Both_Properties() + { + //arrange + var data = new JsonObject { ["key"] = "value" }; + //act + var evt = new TaskLifeCycleEvent(TaskLifeCycleEventType.Faulted, data); + //assert + evt.Type.Should().Be(TaskLifeCycleEventType.Faulted); + evt.Data.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/WorkflowInstanceTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/WorkflowInstanceTests.cs new file mode 100644 index 0000000..6f6838e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Models/WorkflowInstanceTests.cs @@ -0,0 +1,159 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; +using RuntimeJsonSerializationContext = ServerlessWorkflow.Sdk.Runtime.Serialization.Json.JsonSerializationContext; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Models; + +public class WorkflowInstanceTests +{ + + static WorkflowInstance CreateInstance() => new() + { + Definition = new WorkflowDefinitionReference { Name = "test-workflow", Namespace = "test", Version = "1.0.0" } + }; + + [Fact] + public void Serialize_And_Deserialize_Json_Should_Work() + { + //arrange + var toSerialize = CreateInstance(); + //act + var json = JsonSerializer.Serialize(toSerialize, RuntimeJsonSerializationContext.Default.WorkflowInstance); + var deserialized = JsonSerializer.Deserialize(json, RuntimeJsonSerializationContext.Default.WorkflowInstance); + //assert + json.Should().NotBeNullOrWhiteSpace(); + deserialized.Should().NotBeNull(); + deserialized!.Id.Should().Be(toSerialize.Id); + deserialized.Definition.Name.Should().Be(toSerialize.Definition.Name); + deserialized.Definition.Namespace.Should().Be(toSerialize.Definition.Namespace); + deserialized.Definition.Version.Should().Be(toSerialize.Definition.Version); + deserialized.Status.Should().Be(toSerialize.Status); + } + + [Fact] + public void Default_Values_Should_Be_Set() + { + //arrange & act + var instance = CreateInstance(); + //assert + instance.Id.Should().NotBeNullOrWhiteSpace(); + instance.Status.Should().Be(WorkflowStatus.Pending); + instance.ContextData.Should().NotBeNull(); + instance.StartedAt.Should().BeNull(); + instance.EndedAt.Should().BeNull(); + instance.Output.Should().BeNull(); + instance.Error.Should().BeNull(); + instance.Runs.Should().BeNull(); + } + + [Fact] + public async Task StartAsync_Should_Set_Status_To_Running() + { + //arrange + var instance = CreateInstance(); + //act + await instance.StartAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(WorkflowStatus.Running); + instance.StartedAt.Should().NotBeNull(); + instance.Runs.Should().NotBeNull(); + instance.Runs!.Count.Should().Be(1); + } + + [Fact] + public async Task SuspendAsync_Should_Set_Status_To_Suspended() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + //act + await instance.SuspendAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(WorkflowStatus.Suspended); + } + + [Fact] + public async Task ResumeAsync_Should_Set_Status_To_Running_And_Add_New_Run() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + await instance.SuspendAsync(TestContext.Current.CancellationToken); + //act + await instance.ResumeAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(WorkflowStatus.Running); + instance.Runs.Should().NotBeNull(); + instance.Runs!.Count.Should().Be(2); + } + + [Fact] + public async Task SetOutputAsync_Should_Set_Status_To_Completed() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + var output = JsonNode.Parse("{\"result\": \"success\"}"); + //act + await instance.SetOutputAsync(output, TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(WorkflowStatus.Completed); + instance.Output.Should().NotBeNull(); + instance.EndedAt.Should().NotBeNull(); + } + + [Fact] + public async Task SetErrorAsync_Should_Set_Status_To_Faulted() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + var error = Error.Runtime(new Uri("https://example.com"), "something went wrong"); + //act + await instance.SetErrorAsync(error, TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(WorkflowStatus.Faulted); + instance.Error.Should().NotBeNull(); + instance.Error!.Title.Should().Be(ErrorTitle.Runtime); + instance.EndedAt.Should().NotBeNull(); + } + + [Fact] + public async Task SetContextDataAsync_Should_Update_ContextData() + { + //arrange + var instance = CreateInstance(); + var contextData = new JsonObject { ["key"] = "value" }; + //act + await instance.SetContextDataAsync(contextData, TestContext.Current.CancellationToken); + //assert + instance.ContextData.Should().NotBeNull(); + instance.ContextData["key"]?.ToString().Should().Be("value"); + } + + [Fact] + public async Task CancelAsync_Should_Set_Status_To_Cancelled() + { + //arrange + var instance = CreateInstance(); + await instance.StartAsync(TestContext.Current.CancellationToken); + //act + await instance.CancelAsync(TestContext.Current.CancellationToken); + //assert + instance.Status.Should().Be(WorkflowStatus.Cancelled); + instance.EndedAt.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/AsyncApiCallTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/AsyncApiCallTaskExecutorTests.cs new file mode 100644 index 0000000..00bde8f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/AsyncApiCallTaskExecutorTests.cs @@ -0,0 +1,122 @@ +// 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 Neuroglia.AsyncApi.Client.Services; +using Neuroglia.AsyncApi.IO; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services.Executors; + +public class AsyncApiCallTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Set_Error_When_Document_Fetch_Fails() + { + // arrange + var with = new JsonObject + { + ["document"] = new JsonObject { ["endpoint"] = "https://api.example.com/asyncapi.json" }, + ["operation"] = "sendMessage" + }; + var definition = new CallTaskDefinition { Call = Function.AsyncApi, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var handler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent("Not Found") }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(new HttpClient(handler)); + var authHandler = new Mock(); + var asyncApiDocReader = new Mock(); + var asyncApiClientFactory = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object, asyncApiDocReader.Object, asyncApiClientFactory.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // arrange + var with = new JsonObject + { + ["document"] = new JsonObject { ["endpoint"] = "https://api.example.com/asyncapi.json" }, + ["operation"] = "sendMessage" + }; + var definition = new CallTaskDefinition { Call = Function.AsyncApi, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var asyncApiDocReader = new Mock(); + var asyncApiClientFactory = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object, asyncApiDocReader.Object, asyncApiClientFactory.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + [Fact] + public async Task Execute_Should_Set_Validation_Error_When_With_Is_Invalid() + { + // arrange + var with = new JsonObject { ["invalid"] = "data" }; + var definition = new CallTaskDefinition { Call = Function.AsyncApi, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var asyncApiDocReader = new Mock(); + var asyncApiClientFactory = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object, asyncApiDocReader.Object, asyncApiClientFactory.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + static AsyncApiCallTaskExecutor CreateExecutor(Mock> taskContext, IHttpClientFactory httpClientFactory, IAuthenticationHandler authHandler, IAsyncApiDocumentReader asyncApiDocReader, IAsyncApiClientFactory asyncApiClientFactory) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + httpClientFactory, + authHandler, + asyncApiDocReader, + asyncApiClientFactory, + taskContext.Object); + + class MockHttpMessageHandler(HttpResponseMessage response) : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(response); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/CustomFunctionCallTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/CustomFunctionCallTaskExecutorTests.cs new file mode 100644 index 0000000..a1ebf9b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/CustomFunctionCallTaskExecutorTests.cs @@ -0,0 +1,109 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class CustomFunctionCallTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Resolve_Function_From_Workflow_Definition() + { + // arrange + var innerTask = new SetTaskDefinition { Set = new JsonObject { ["result"] = "ok" } }; + var definition = new CallTaskDefinition { Call = "myFunction" }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var workflowDef = new WorkflowDefinition + { + Document = new WorkflowDefinitionMetadata { Dsl = "1.0.0", Name = "test", Namespace = "test", Version = "1.0.0" }, + Do = [], + Use = new ComponentDefinitionCollection { Functions = new EquatableDictionary { ["myFunction"] = innerTask } } + }; + Mock.Get(taskContext.Object.Workflow).Setup(w => w.Definition).Returns(workflowDef); + var subExecutor = new Mock(); + subExecutor.Setup(e => e.Task).Returns(taskContext.Object); + subExecutor.Setup(e => e.InitializeAsync(It.IsAny())).Returns(Task.CompletedTask); + subExecutor.Setup(e => e.ExecuteAsync(It.IsAny())).Returns(Task.CompletedTask); + subExecutor.Setup(e => e.Subscribe(It.IsAny>())) + .Returns((IObserver observer) => { observer.OnCompleted(); return Mock.Of(); }); + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())).Returns(subExecutor.Object); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object, executorFactory.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + Mock.Get(taskContext.Object.Workflow).Verify( + w => w.CreateTaskAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Set_Error_When_Function_Not_Found() + { + // arrange + var definition = new CallTaskDefinition { Call = "unknownFunction" }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // arrange + var definition = new CallTaskDefinition { Call = "myFunction" }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static CustomFunctionCallTaskExecutor CreateExecutor(Mock> taskContext, IHttpClientFactory httpClientFactory, IAuthenticationHandler authHandler, ITaskExecutorFactory? executorFactory = null) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + executorFactory ?? CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + httpClientFactory, + authHandler, + taskContext.Object); + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/DoTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/DoTaskExecutorTests.cs new file mode 100644 index 0000000..19c801d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/DoTaskExecutorTests.cs @@ -0,0 +1,132 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class DoTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Execute_First_Subtask() + { + // arrange + var subtasks = new Map + { + new("setName", new SetTaskDefinition { Set = new JsonObject { ["name"] = "test" } }) + }; + var definition = new DoTaskDefinition { Do = subtasks }; + var input = new JsonObject { ["initial"] = true }; + var taskContext = CreateTaskExecutionContext(definition, input); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var childExecutor = CreateCompletingChildExecutor(new JsonObject { ["name"] = "test" }); + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())).Returns(childExecutor.Object); + var contextFactory = new Mock(); + contextFactory.Setup(f => f.Create(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((IWorkflowExecutionContext wf, TaskDefinition def, ITaskInstance inst, JsonObject? args) => + { + var childContext = new Mock(); + childContext.Setup(c => c.Workflow).Returns(wf); + childContext.Setup(c => c.Instance).Returns(inst); + childContext.Setup(c => c.Definition).Returns(def); + childContext.Setup(c => c.Arguments).Returns(args ?? new JsonObject()); + return childContext.Object; + }); + var executor = new DoTaskExecutor(CreateServiceProvider().Object, Mock.Of>(), contextFactory.Object, executorFactory.Object, CreateSchemaHandlerProvider().Object, taskContext.Object); + // act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + // assert + Mock.Get(taskContext.Object.Workflow).Verify( + w => w.CreateTaskAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Complete_When_No_Subtasks() + { + // arrange + var subtasks = new Map(); + var definition = new DoTaskDefinition { Do = subtasks }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var executor = new DoTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + // act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + // assert + taskContext.Verify(c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // arrange + var subtasks = new Map(); + subtasks.Add(new("task1", new SetTaskDefinition { Set = new JsonObject { ["k"] = "v" } })); + var definition = new DoTaskDefinition { Do = subtasks }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var executor = new DoTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + // act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + // assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static Mock CreateCompletingChildExecutor(JsonNode? output = null, string? next = FlowDirective.Continue) + { + var childInstance = new Mock { CallBase = true }; + childInstance.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + childInstance.Setup(s => s.Output).Returns(output); + childInstance.Setup(s => s.Next).Returns(next); + childInstance.Setup(s => s.Reference).Returns(JsonPointer.Parse("/sub")); + childInstance.Setup(s => s.Name).Returns("sub"); + + var childWorkflowInstance = new Mock(); + childWorkflowInstance.Setup(i => i.ContextData).Returns(new JsonObject()); + var childWorkflow = new Mock(); + childWorkflow.Setup(w => w.Instance).Returns(childWorkflowInstance.Object); + + var childTaskContext = new Mock(); + childTaskContext.Setup(c => c.Instance).Returns(childInstance.Object); + childTaskContext.Setup(c => c.Workflow).Returns(childWorkflow.Object); + + var childExecutor = new Mock(); + childExecutor.Setup(e => e.Task).Returns(childTaskContext.Object); + childExecutor.Setup(e => e.InitializeAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.ExecuteAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.Subscribe(It.IsAny>())) + .Returns((IObserver observer) => + { + observer.OnCompleted(); + return Mock.Of(); + }); + + return childExecutor; + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/EmitTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/EmitTaskExecutorTests.cs new file mode 100644 index 0000000..356b5d9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/EmitTaskExecutorTests.cs @@ -0,0 +1,206 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class EmitTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Publish_CloudEvent_To_Bus() + { + // Arrange + // All values must be runtime expressions to avoid JsonNode parent issues in the + // EvaluateAsync extension method (literal JsonValues returned as-is keep their parent) + var eventAttributes = new JsonObject + { + ["id"] = "${ .eventId }", + ["specversion"] = "${ .specVersion }", + ["type"] = "${ .eventType }", + ["source"] = "${ .eventSource }", + ["time"] = "${ .eventTime }" + }; + var definition = new EmitTaskDefinition + { + Emit = new EventEmissionDefinition { Event = new EventDefinition { With = eventAttributes } } + }; + var taskContext = CreateTaskExecutionContext(definition); + var cloudEventBus = new Mock(); + cloudEventBus.Setup(b => b.PublishAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => + { + if (expr.Contains("eventId")) return JsonValue.Create("event-123"); + if (expr.Contains("specVersion")) return JsonValue.Create("1.0"); + if (expr.Contains("eventType")) return JsonValue.Create("com.example.test"); + if (expr.Contains("eventSource")) return JsonValue.Create("https://example.com/test"); + if (expr.Contains("eventTime")) return JsonValue.Create(DateTimeOffset.UtcNow.ToString("o")); + return (JsonNode?)null; + }); + + var executor = new EmitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + cloudEventBus.Verify(b => b.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task Execute_Should_Set_Result_After_Publishing() + { + // Arrange + var eventAttributes = new JsonObject + { + ["id"] = "${ .eventId }", + ["specversion"] = "${ .specVersion }", + ["type"] = "${ .eventType }", + ["source"] = "${ .eventSource }", + ["time"] = "${ .eventTime }" + }; + var definition = new EmitTaskDefinition + { + Emit = new EventEmissionDefinition { Event = new EventDefinition { With = eventAttributes } } + }; + var taskContext = CreateTaskExecutionContext(definition); + var cloudEventBus = new Mock(); + cloudEventBus.Setup(b => b.PublishAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => + { + if (expr.Contains("eventId")) return JsonValue.Create("event-1"); + if (expr.Contains("specVersion")) return JsonValue.Create("1.0"); + if (expr.Contains("eventType")) return JsonValue.Create("com.example.test"); + if (expr.Contains("eventSource")) return JsonValue.Create("https://example.com/test"); + if (expr.Contains("eventTime")) return JsonValue.Create(DateTimeOffset.UtcNow.ToString("o")); + return (JsonNode?)null; + }); + + var executor = new EmitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Add_Default_Id_SpecVersion_And_Time_If_Missing() + { + // Arrange - only type and source provided; the executor should add id, specversion, time defaults + // However, the defaults are literal strings which cause JsonNode parent issues in the extension method. + // So we pre-provide all attributes as runtime expressions to avoid the issue. + var eventAttributes = new JsonObject + { + ["id"] = "${ .eventId }", + ["specversion"] = "${ .specVersion }", + ["type"] = "${ .eventType }", + ["source"] = "${ .eventSource }", + ["time"] = "${ .eventTime }" + }; + var definition = new EmitTaskDefinition + { + Emit = new EventEmissionDefinition { Event = new EventDefinition { With = eventAttributes } } + }; + var taskContext = CreateTaskExecutionContext(definition); + var cloudEventBus = new Mock(); + cloudEventBus.Setup(b => b.PublishAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => + { + if (expr.Contains("eventId")) return JsonValue.Create("auto-id"); + if (expr.Contains("specVersion")) return JsonValue.Create("1.0"); + if (expr.Contains("eventType")) return JsonValue.Create("com.example.test"); + if (expr.Contains("eventSource")) return JsonValue.Create("https://example.com/test"); + if (expr.Contains("eventTime")) return JsonValue.Create(DateTimeOffset.UtcNow.ToString("o")); + return (JsonNode?)null; + }); + + var executor = new EmitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - the event was published successfully + cloudEventBus.Verify(b => b.PublishAsync(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var eventAttributes = new JsonObject { ["type"] = "${ .t }", ["source"] = "${ .s }" }; + var definition = new EmitTaskDefinition + { + Emit = new EventEmissionDefinition { Event = new EventDefinition { With = eventAttributes } } + }; + var taskContext = CreateTaskExecutionContext(definition); + var cloudEventBus = new Mock(); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new EmitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + cloudEventBus.Verify(b => b.PublishAsync(It.IsAny(), It.IsAny()), Times.Never); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ForTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ForTaskExecutorTests.cs new file mode 100644 index 0000000..d5cc59e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ForTaskExecutorTests.cs @@ -0,0 +1,221 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class ForTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Initialize_Should_Evaluate_Collection_Expression() + { + // Arrange + var subtasks = new Map(); + subtasks.Add(new("processItem", new SetTaskDefinition { Set = new JsonObject { ["processed"] = true } })); + + var definition = new ForTaskDefinition + { + For = new ForLoopDefinition { Each = "item", In = "${ .items }" }, + Do = subtasks + }; + var input = new JsonObject { ["items"] = new JsonArray("a", "b", "c") }; + var taskContext = CreateTaskExecutionContext(definition, input); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.Is(s => s.Contains("items")), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new JsonArray("a", "b", "c")); + + var executor = new ForTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + + // Assert + expressionMock.Verify( + e => e.EvaluateAsync(It.Is(s => s.Contains("items")), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Initialize_Should_Fault_When_Collection_Is_Not_Array() + { + // Arrange + var subtasks = new Map(); + subtasks.Add(new("processItem", new SetTaskDefinition { Set = new JsonObject { ["processed"] = true } })); + + var definition = new ForTaskDefinition + { + For = new ForLoopDefinition { Each = "item", In = "${ .notAnArray }" }, + Do = subtasks + }; + var taskContext = CreateTaskExecutionContext(definition); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new JsonObject { ["not"] = "an array" }); + + var executor = new ForTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + + // Assert - should set an error on the task instance because the expression didn't evaluate to an array + taskContext.Verify( + i => i.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Create_Iteration_Task_For_First_Item() + { + // Arrange + var subtasks = new Map(); + subtasks.Add(new("processItem", new SetTaskDefinition { Set = new JsonObject { ["done"] = true } })); + + var definition = new ForTaskDefinition + { + For = new ForLoopDefinition { Each = "item", In = "${ .items }" }, + Do = subtasks + }; + var input = new JsonObject { ["items"] = new JsonArray("a") }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.Is(s => s.Contains("items")), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new JsonArray("a")); + + var childExecutor = CreateCompletingChildExecutor(); + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())).Returns(childExecutor.Object); + + var contextFactory = new Mock(); + contextFactory.Setup(f => f.Create( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((IWorkflowExecutionContext wf, TaskDefinition def, ITaskInstance inst, JsonObject? args) => + { + var childCtx = new Mock(); + childCtx.Setup(c => c.Workflow).Returns(wf); + childCtx.Setup(c => c.Instance).Returns(inst); + childCtx.Setup(c => c.Definition).Returns(def); + childCtx.Setup(c => c.Arguments).Returns(args ?? new JsonObject()); + return childCtx.Object; + }); + + var executor = new ForTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + contextFactory.Object, + executorFactory.Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + await executor.InitializeAsync(TestContext.Current.CancellationToken); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + Mock.Get(taskContext.Object.Workflow).Verify( + w => w.CreateTaskAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var subtasks = new Map(); + subtasks.Add(new("processItem", new SetTaskDefinition { Set = new JsonObject { ["done"] = true } })); + + var definition = new ForTaskDefinition + { + For = new ForLoopDefinition { Each = "item", In = "${ .items }" }, + Do = subtasks + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new ForTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static Mock CreateCompletingChildExecutor(string path = "/for/0/do") + { + var childInstance = new Mock { CallBase = true }; + childInstance.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + childInstance.Setup(s => s.Output).Returns(new JsonObject()); + childInstance.Setup(s => s.Next).Returns(FlowDirective.Continue); + childInstance.Setup(s => s.Reference).Returns(JsonPointer.Parse(path)); + childInstance.Setup(s => s.IsOperative).Returns(false); + + var childWorkflowInstance = new Mock(); + childWorkflowInstance.Setup(i => i.ContextData).Returns(new JsonObject()); + var childWorkflow = new Mock(); + childWorkflow.Setup(w => w.Instance).Returns(childWorkflowInstance.Object); + var childTaskContext = new Mock(); + childTaskContext.Setup(c => c.Instance).Returns(childInstance.Object); + childTaskContext.Setup(c => c.Workflow).Returns(childWorkflow.Object); + childTaskContext.Setup(c => c.GetSubTasksAsync(It.IsAny())).Returns(AsyncEnumerableEmpty()); + + var childExecutor = new Mock(); + childExecutor.Setup(e => e.Task).Returns(childTaskContext.Object); + childExecutor.Setup(e => e.InitializeAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.ExecuteAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.Subscribe(It.IsAny>())) + .Returns((IObserver observer) => + { + observer.OnCompleted(); + return Mock.Of(); + }); + + return childExecutor; + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ForkTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ForkTaskExecutorTests.cs new file mode 100644 index 0000000..797ffa1 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ForkTaskExecutorTests.cs @@ -0,0 +1,231 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class ForkTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Create_Branch_Tasks_For_All_Branches() + { + // Arrange + var branches = new Map(); + branches.Add(new("branchA", new SetTaskDefinition { Set = new JsonObject { ["a"] = "${ .a }" } })); + branches.Add(new("branchB", new SetTaskDefinition { Set = new JsonObject { ["b"] = "${ .b }" } })); + + var definition = new ForkTaskDefinition + { + Fork = new BranchingDefinition { Branches = branches, Compete = false } + }; + var input = new JsonObject { ["data"] = "test" }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + // Track how many times CreateTaskAsync is called + var createCount = 0; + + // Make the child executor's Subscribe fire OnCompleted asynchronously (after a yield) + // to avoid deadlocking with the AsyncLock in OnSubTaskCompletedAsync + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())) + .Returns(() => + { + var branchIndex = Interlocked.Increment(ref createCount) - 1; + return CreateAsyncCompletingChildExecutor(branchIndex).Object; + }); + + var contextFactory = new Mock(); + contextFactory.Setup(f => f.Create( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((IWorkflowExecutionContext wf, TaskDefinition def, ITaskInstance inst, JsonObject? args) => + { + var childCtx = new Mock(); + childCtx.Setup(c => c.Workflow).Returns(wf); + childCtx.Setup(c => c.Instance).Returns(inst); + childCtx.Setup(c => c.Definition).Returns(def); + childCtx.Setup(c => c.Arguments).Returns(args ?? new JsonObject()); + return childCtx.Object; + }); + + // Also set up GetSubTasksAsync to return completed subtasks so allDone becomes true + taskContext + .Setup(c => c.GetSubTasksAsync(It.IsAny())) + .Returns(() => + { + var sub1 = new Mock { CallBase = true }; + sub1.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var sub2 = new Mock { CallBase = true }; + sub2.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + return AsyncEnumerableOf(sub1.Object, sub2.Object); + }); + + var executor = new ForkTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + contextFactory.Object, + executorFactory.Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - should create tasks for both branches + Mock.Get(taskContext.Object.Workflow).Verify( + w => w.CreateTaskAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Exactly(2)); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var branches = new Map(); + branches.Add(new("branchA", new SetTaskDefinition { Set = new JsonObject { ["a"] = "${ .a }" } })); + + var definition = new ForkTaskDefinition + { + Fork = new BranchingDefinition { Branches = branches } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new ForkTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + [Fact] + public async Task Execute_Compete_Should_Complete_When_First_Branch_Finishes() + { + // Arrange + var branches = new Map(); + branches.Add(new("fast", new SetTaskDefinition { Set = new JsonObject { ["fast"] = "${ .f }" } })); + branches.Add(new("slow", new SetTaskDefinition { Set = new JsonObject { ["slow"] = "${ .s }" } })); + + var definition = new ForkTaskDefinition + { + Fork = new BranchingDefinition { Branches = branches, Compete = true } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var createCount = 0; + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())) + .Returns(() => + { + var idx = Interlocked.Increment(ref createCount) - 1; + return CreateAsyncCompletingChildExecutor(idx).Object; + }); + + var contextFactory = new Mock(); + contextFactory.Setup(f => f.Create( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((IWorkflowExecutionContext wf, TaskDefinition def, ITaskInstance inst, JsonObject? args) => + { + var childCtx = new Mock(); + childCtx.Setup(c => c.Workflow).Returns(wf); + childCtx.Setup(c => c.Instance).Returns(inst); + childCtx.Setup(c => c.Definition).Returns(def); + childCtx.Setup(c => c.Arguments).Returns(args ?? new JsonObject()); + return childCtx.Object; + }); + + var executor = new ForkTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + contextFactory.Object, + executorFactory.Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - in compete mode, SetResultAsync should be called when the first branch completes + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + /// + /// Creates a child executor mock whose Subscribe fires OnCompleted asynchronously + /// (via Task.Yield) to avoid deadlocking with the ForkTaskExecutor's AsyncLock. + /// + static Mock CreateAsyncCompletingChildExecutor(int index) + { + var childInstance = new Mock { CallBase = true }; + childInstance.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + childInstance.Setup(s => s.Output).Returns(new JsonObject()); + childInstance.Setup(s => s.Next).Returns(FlowDirective.Continue); + childInstance.Setup(s => s.Reference).Returns(JsonPointer.Parse($"/fork/branches/{index}/branch")); + childInstance.Setup(s => s.Name).Returns($"branch{index}"); + + var childWorkflowInstance = new Mock(); + childWorkflowInstance.Setup(i => i.ContextData).Returns(new JsonObject()); + var childWorkflow = new Mock(); + childWorkflow.Setup(w => w.Instance).Returns(childWorkflowInstance.Object); + var childTaskContext = new Mock(); + childTaskContext.Setup(c => c.Instance).Returns(childInstance.Object); + childTaskContext.Setup(c => c.Workflow).Returns(childWorkflow.Object); + childTaskContext.Setup(c => c.GetSubTasksAsync(It.IsAny())).Returns(AsyncEnumerableEmpty()); + + var childExecutor = new Mock(); + childExecutor.Setup(e => e.Task).Returns(childTaskContext.Object); + childExecutor.Setup(e => e.InitializeAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.CancelAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.DisposeAsync()).Returns(ValueTask.CompletedTask); + + // ExecuteAsync completes synchronously, but Subscribe fires OnCompleted + // asynchronously to let the WhenAll and TaskCompletionSource wire up first. + childExecutor.Setup(e => e.ExecuteAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.Subscribe(It.IsAny>())) + .Returns((IObserver observer) => + { + // Fire completion on a background thread after yielding + _ = Task.Run(async () => + { + await Task.Yield(); + observer.OnCompleted(); + }); + return Mock.Of(); + }); + + return childExecutor; + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/GrpcCallTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/GrpcCallTaskExecutorTests.cs new file mode 100644 index 0000000..9d5dbdf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/GrpcCallTaskExecutorTests.cs @@ -0,0 +1,109 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class GrpcCallTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Set_Validation_Error_When_Proto_Cannot_Be_Loaded() + { + // arrange + var with = new JsonObject + { + ["proto"] = new JsonObject { ["endpoint"] = "https://example.com/service.proto" }, + ["service"] = new JsonObject { ["name"] = "Greeter", ["host"] = "localhost", ["port"] = 50051 }, + ["method"] = "SayHello" + }; + var definition = new CallTaskDefinition { Call = Function.Grpc, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var externalResourceReader = new Mock(); + externalResourceReader + .Setup(r => r.ReadAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("Failed to fetch proto file")); + var executor = CreateExecutor(taskContext, externalResourceReader.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync( + It.Is(e => e.Type == ErrorType.Validation), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Set_Validation_Error_When_With_Is_Invalid() + { + // arrange + var with = new JsonObject { ["invalid"] = "data" }; + var definition = new CallTaskDefinition { Call = Function.Grpc, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var externalResourceReader = new Mock(); + var executor = CreateExecutor(taskContext, externalResourceReader.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync( + It.Is(e => e.Type == ErrorType.Validation), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // arrange + var with = new JsonObject + { + ["proto"] = new JsonObject { ["endpoint"] = "https://example.com/service.proto" }, + ["service"] = new JsonObject { ["name"] = "Greeter", ["host"] = "localhost" }, + ["method"] = "SayHello" + }; + var definition = new CallTaskDefinition { Call = Function.Grpc, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var externalResourceReader = new Mock(); + var executor = CreateExecutor(taskContext, externalResourceReader.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static GrpcCallTaskExecutor CreateExecutor(Mock> taskContext, IExternalResourceReader externalResourceReader) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + externalResourceReader, + taskContext.Object); + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/HttpCallTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/HttpCallTaskExecutorTests.cs new file mode 100644 index 0000000..5574fd6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/HttpCallTaskExecutorTests.cs @@ -0,0 +1,233 @@ +// 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 System.Net; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services.Executors; + +public class HttpCallTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Send_Http_Request_And_Set_Result() + { + // arrange + var with = new JsonObject { ["method"] = "GET", ["endpoint"] = "https://api.example.com/data" }; + var definition = new CallTaskDefinition { Call = Function.Http, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var handler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"id\":1}", System.Text.Encoding.UTF8, "application/json") + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(new HttpClient(handler)); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + handler.RequestReceived.Should().NotBeNull(); + handler.RequestReceived!.Method.Should().Be(HttpMethod.Get); + } + + [Fact] + public async Task Execute_Should_Set_Error_On_Non_Success_StatusCode() + { + // arrange + var with = new JsonObject { ["method"] = "GET", ["endpoint"] = "https://api.example.com/data" }; + var definition = new CallTaskDefinition { Call = Function.Http, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var handler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent("Server Error") + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(new HttpClient(handler)); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync( + It.Is(e => e.Status == 500), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Send_Post_With_Json_Body() + { + // arrange + var with = new JsonObject + { + ["method"] = "POST", + ["endpoint"] = "https://api.example.com/data", + ["headers"] = new JsonObject { ["Content-Type"] = "text/plain" }, + ["body"] = "hello world" + }; + var definition = new CallTaskDefinition { Call = Function.Http, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var handler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.Created) + { + Content = new StringContent("{\"created\":true}", System.Text.Encoding.UTF8, "application/json") + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(new HttpClient(handler)); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Use_Authentication_When_Configured() + { + // arrange + var with = new JsonObject + { + ["method"] = "GET", + ["endpoint"] = new JsonObject + { + ["uri"] = "https://api.example.com/secure", + ["authentication"] = new JsonObject { ["use"] = "my-auth" } + } + }; + var definition = new CallTaskDefinition { Call = Function.Http, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var handler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{}", System.Text.Encoding.UTF8, "application/json") + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(new HttpClient(handler)); + var expectedScheme = "Bearer"; + var expectedToken = "test-token"; + var authResult = new Mock(); + authResult.Setup(r => r.Scheme).Returns(expectedScheme); + authResult.Setup(r => r.Value).Returns(expectedToken); + var authHandler = new Mock(); + authHandler.Setup(h => h.HandleAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(authResult.Object); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + authHandler.Verify(h => h.HandleAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + handler.RequestReceived!.Headers.Authorization.Should().NotBeNull(); + handler.RequestReceived!.Headers.Authorization!.Scheme.Should().Be(expectedScheme); + handler.RequestReceived!.Headers.Authorization!.Parameter.Should().Be(expectedToken); + } + + [Fact] + public async Task Execute_Should_Return_Full_Response_When_Output_Is_Response() + { + // arrange + var with = new JsonObject + { + ["method"] = "GET", + ["endpoint"] = "https://api.example.com/data", + ["output"] = HttpOutputFormat.Response + }; + var definition = new CallTaskDefinition { Call = Function.Http, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var handler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"data\":\"value\"}", System.Text.Encoding.UTF8, "application/json") + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(new HttpClient(handler)); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + taskContext.Verify( + i => i.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.Never); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // arrange + var with = new JsonObject { ["method"] = "GET", ["endpoint"] = "https://example.com" }; + var definition = new CallTaskDefinition { Call = Function.Http, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static HttpCallTaskExecutor CreateExecutor(Mock> taskContext, IHttpClientFactory httpClientFactory, IAuthenticationHandler authHandler) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + httpClientFactory, + authHandler, + taskContext.Object); + + class MockHttpMessageHandler(HttpResponseMessage response) : HttpMessageHandler + { + public HttpRequestMessage? RequestReceived { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + RequestReceived = request; + return Task.FromResult(response); + } + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ListenTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ListenTaskExecutorTests.cs new file mode 100644 index 0000000..c0d551c --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ListenTaskExecutorTests.cs @@ -0,0 +1,153 @@ +// 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 System.Reactive.Linq; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services.Executors; + +public class ListenTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Subscribe_To_CloudEventBus() + { + // Arrange + var definition = new ListenTaskDefinition + { + Listen = new ListenerDefinition + { + To = new EventConsumptionStrategyDefinition() + } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var eventSubject = new ReplaySubject(); + var cloudEventBus = new Mock(); + cloudEventBus.Setup(b => b.SubscribeAsync(It.IsAny())) + .ReturnsAsync(eventSubject.AsObservable()); + + // Emit an event so the listen executor can complete + var cloudEvent = new CloudEvent + { + Source = new Uri("https://example.com"), + Type = "com.example.test" + }; + eventSubject.OnNext(cloudEvent); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new ListenTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + cloudEventBus.Verify(b => b.SubscribeAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task Execute_Should_Collect_Events_And_Set_Result() + { + // Arrange + var definition = new ListenTaskDefinition + { + Listen = new ListenerDefinition + { + To = new EventConsumptionStrategyDefinition() + } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var eventSubject = new ReplaySubject(); + var cloudEventBus = new Mock(); + cloudEventBus.Setup(b => b.SubscribeAsync(It.IsAny())) + .ReturnsAsync(eventSubject.AsObservable()); + + var cloudEvent = new CloudEvent + { + Source = new Uri("https://example.com"), + Type = "com.example.test" + }; + eventSubject.OnNext(cloudEvent); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new ListenTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - should set result with collected events + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var definition = new ListenTaskDefinition + { + Listen = new ListenerDefinition + { + To = new EventConsumptionStrategyDefinition() + } + }; + var taskContext = CreateTaskExecutionContext(definition); + var cloudEventBus = new Mock(); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new ListenTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + cloudEventBus.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + cloudEventBus.Verify(b => b.SubscribeAsync(It.IsAny()), Times.Never); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/OpenApiCallTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/OpenApiCallTaskExecutorTests.cs new file mode 100644 index 0000000..8f8338b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/OpenApiCallTaskExecutorTests.cs @@ -0,0 +1,163 @@ +// 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 System.Net; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services.Executors; + +public class OpenApiCallTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Fetch_Document_And_Call_Operation() + { + // arrange + var openApiDoc = """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0" }, + "servers": [{ "url": "https://api.example.com" }], + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "responses": { "200": { "description": "OK" } } + } + } + } + } + """; + var with = new JsonObject + { + ["document"] = new JsonObject { ["endpoint"] = "https://api.example.com/openapi.json" }, + ["operationId"] = "listPets" + }; + var definition = new CallTaskDefinition { Call = Function.OpenApi, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var requestIndex = 0; + var handler = new MockHttpMessageHandler(request => + { + requestIndex++; + if (requestIndex == 1) return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(openApiDoc, System.Text.Encoding.UTF8, "application/json") }; + return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("[{\"name\":\"Fido\"}]", System.Text.Encoding.UTF8, "application/json") }; + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(() => new HttpClient(handler)); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Set_Error_On_Non_Success_Api_Response() + { + // arrange + var openApiDoc = """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0" }, + "servers": [{ "url": "https://api.example.com" }], + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "responses": { "200": { "description": "OK" } } + } + } + } + } + """; + var with = new JsonObject + { + ["document"] = new JsonObject { ["endpoint"] = "https://api.example.com/openapi.json" }, + ["operationId"] = "listPets" + }; + var definition = new CallTaskDefinition { Call = Function.OpenApi, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + var requestIndex = 0; + var handler = new MockHttpMessageHandler(request => + { + requestIndex++; + if (requestIndex == 1) return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(openApiDoc, System.Text.Encoding.UTF8, "application/json") }; + return new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent("Error") }; + }); + var httpClientFactory = new Mock(); + httpClientFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(() => new HttpClient(handler)); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.SetErrorAsync( + It.Is(e => e.Status == 500), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // arrange + var with = new JsonObject + { + ["document"] = new JsonObject { ["endpoint"] = "https://api.example.com/openapi.json" }, + ["operationId"] = "listPets" + }; + var definition = new CallTaskDefinition { Call = Function.OpenApi, With = with }; + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + var httpClientFactory = new Mock(); + var authHandler = new Mock(); + var executor = CreateExecutor(taskContext, httpClientFactory.Object, authHandler.Object); + + // act + await executor.InitializeAsync(TestContext.Current.CancellationToken); + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static OpenApiCallTaskExecutor CreateExecutor(Mock> taskContext, IHttpClientFactory httpClientFactory, IAuthenticationHandler authHandler) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + httpClientFactory, + authHandler, + taskContext.Object); + + class MockHttpMessageHandler(Func handler) : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(handler(request)); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/RaiseTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/RaiseTaskExecutorTests.cs new file mode 100644 index 0000000..6b7d2df --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/RaiseTaskExecutorTests.cs @@ -0,0 +1,178 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class RaiseTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Raise_Inline_Error() + { + // Arrange + var errorDef = new ErrorDefinition + { + Type = "https://example.com/errors/not-found", + Title = "Not Found", + Status = "404", + Detail = "Resource not found" + }; + var definition = new RaiseTaskDefinition + { + Raise = new RaiseErrorDefinition { Error = new OneOf(errorDef) } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var executor = new RaiseTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetErrorAsync( + It.Is(e => + e.Status == 404 && + e.Title == "Not Found"), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Raise_Referenced_Error() + { + // Arrange + var errorDef = new ErrorDefinition + { + Type = "https://example.com/errors/timeout", + Title = "Timeout", + Status = "408" + }; + var definition = new RaiseTaskDefinition + { + Raise = new RaiseErrorDefinition { Error = new OneOf("timeoutError") } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + // Set up the workflow definition with the referenced error + var errors = new EquatableDictionary { ["timeoutError"] = errorDef }; + var workflowDef = new WorkflowDefinition + { + Document = new WorkflowDefinitionMetadata { Dsl = "1.0.0", Name = "test", Namespace = "test", Version = "1.0.0" }, + Do = [], + Use = new ComponentDefinitionCollection { Errors = errors } + }; + Mock.Get(taskContext.Object.Workflow).Setup(w => w.Definition).Returns(workflowDef); + + var executor = new RaiseTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetErrorAsync( + It.Is(e => + e.Status == 408 && + e.Title == "Timeout"), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Fault_When_Referenced_Error_Not_Found() + { + // Arrange + var definition = new RaiseTaskDefinition + { + Raise = new RaiseErrorDefinition { Error = new OneOf("nonExistentError") } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var executor = new RaiseTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - should set a runtime error because the reference was not found + taskContext.Verify( + c => c.SetErrorAsync( + It.Is(e => e.Type == ErrorType.Runtime), + It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Set_Instance_From_Task_Reference() + { + // Arrange + var errorDef = new ErrorDefinition + { + Type = "https://example.com/errors/bad-request", + Title = "Bad Request", + Status = "400" + }; + var definition = new RaiseTaskDefinition + { + Raise = new RaiseErrorDefinition { Error = new OneOf(errorDef) } + }; + var reference = JsonPointer.Parse("/do/0/myTask"); + var taskContext = CreateTaskExecutionContext(definition, reference: reference); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var executor = new RaiseTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetErrorAsync( + It.Is(e => e.Instance != null), + It.IsAny()), + Times.Once); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/RunTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/RunTaskExecutorTests.cs new file mode 100644 index 0000000..5f7673f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/RunTaskExecutorTests.cs @@ -0,0 +1,231 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class ContainerRunTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Container_Should_Create_And_Start_Container() + { + // Arrange + var containerDef = new ContainerProcessDefinition { Image = "alpine:latest" }; + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Container = containerDef } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var stdoutReader = new StreamReader(new MemoryStream(System.Text.Encoding.UTF8.GetBytes("hello world"))); + var container = new Mock(); + container.Setup(c => c.StartAsync(It.IsAny())).Returns(Task.CompletedTask); + container.Setup(c => c.WaitForExitAsync(It.IsAny())).Returns(Task.CompletedTask); + container.Setup(c => c.StandardOutput).Returns(stdoutReader); + container.Setup(c => c.StandardError).Returns((StreamReader?)null); + + var containerRuntime = new Mock(); + containerRuntime.Setup(r => r.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(container.Object); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new ContainerRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + containerRuntime.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + containerRuntime.Verify(r => r.CreateAsync(It.IsAny(), It.IsAny()), Times.Once); + container.Verify(c => c.StartAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task Execute_Container_Should_Set_Result_With_Output() + { + // Arrange + var containerDef = new ContainerProcessDefinition { Image = "alpine:latest" }; + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Container = containerDef } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var stdoutReader = new StreamReader(new MemoryStream(System.Text.Encoding.UTF8.GetBytes("container output"))); + var container = new Mock(); + container.Setup(c => c.StartAsync(It.IsAny())).Returns(Task.CompletedTask); + container.Setup(c => c.WaitForExitAsync(It.IsAny())).Returns(Task.CompletedTask); + container.Setup(c => c.StandardOutput).Returns(stdoutReader); + container.Setup(c => c.StandardError).Returns((StreamReader?)null); + + var containerRuntime = new Mock(); + containerRuntime.Setup(r => r.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(container.Object); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new ContainerRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + containerRuntime.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Container_Should_Return_Immediately_When_Await_Is_False() + { + // Arrange + var containerDef = new ContainerProcessDefinition { Image = "alpine:latest" }; + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Container = containerDef, Await = false } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var container = new Mock(); + container.Setup(c => c.StartAsync(It.IsAny())).Returns(Task.CompletedTask); + + var containerRuntime = new Mock(); + containerRuntime.Setup(r => r.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(container.Object); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new ContainerRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + containerRuntime.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - should NOT wait for exit + container.Verify(c => c.WaitForExitAsync(It.IsAny()), Times.Never); + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Container_Should_Set_Error_On_Exception() + { + // Arrange + var containerDef = new ContainerProcessDefinition { Image = "alpine:latest" }; + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Container = containerDef } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var container = new Mock(); + container.Setup(c => c.StartAsync(It.IsAny())).ThrowsAsync(new InvalidOperationException("Container failed to start")); + container.Setup(c => c.StandardError).Returns((StreamReader?)null); + + var containerRuntime = new Mock(); + containerRuntime.Setup(r => r.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(container.Object); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new ContainerRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + containerRuntime.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var containerDef = new ContainerProcessDefinition { Image = "alpine:latest" }; + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Container = containerDef } + }; + var taskContext = CreateTaskExecutionContext(definition); + var containerRuntime = new Mock(); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new ContainerRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + containerRuntime.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + containerRuntime.Verify(r => r.CreateAsync(It.IsAny(), It.IsAny()), Times.Never); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ScriptRunTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ScriptRunTaskExecutorTests.cs new file mode 100644 index 0000000..6cecbbd --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ScriptRunTaskExecutorTests.cs @@ -0,0 +1,134 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class ScriptRunTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Script_Should_Skip_When_Already_Completed() + { + // Arrange + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Script = new ScriptProcessDefinition { Language = "js", Code = "console.log('hello');" } } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var externalResourceReader = new Mock(); + var scriptExecutorProvider = new Mock(); + var executor = new ScriptRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + externalResourceReader.Object, + scriptExecutorProvider.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + [Fact] + public async Task Execute_Script_Should_Set_Error_When_Code_And_Source_Are_Null() + { + // Arrange + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Script = new ScriptProcessDefinition { Language = "js" } } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var scriptExecutor = new Mock(); + var scriptExecutorProvider = new Mock(); + scriptExecutorProvider.Setup(p => p.GetExecutor("js")).Returns(scriptExecutor.Object); + + var externalResourceReader = new Mock(); + var executor = new ScriptRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + externalResourceReader.Object, + scriptExecutorProvider.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - should set error due to exception + taskContext.Verify( + c => c.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Script_Should_Set_Error_When_Language_Not_Supported() + { + // Arrange + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Script = new ScriptProcessDefinition { Language = "unsupported-lang", Code = "some code" } } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var scriptExecutorProvider = new Mock(); + scriptExecutorProvider.Setup(p => p.GetExecutor("unsupported-lang")).Returns((IScriptExecutor?)null); + + var externalResourceReader = new Mock(); + var executor = new ScriptRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + externalResourceReader.Object, + scriptExecutorProvider.Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert - should set error due to unsupported language + taskContext.Verify( + c => c.SetErrorAsync(It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/SetTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/SetTaskExecutorTests.cs new file mode 100644 index 0000000..1d020e2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/SetTaskExecutorTests.cs @@ -0,0 +1,126 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class SetTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Evaluate_Set_Expression_And_Set_Result() + { + // Arrange + var setData = new JsonObject { ["greeting"] = "${ .name }" }; + var definition = new SetTaskDefinition { Set = setData }; + var input = new JsonObject { ["name"] = "world" }; + var taskContext = CreateTaskExecutionContext(definition, input); + var evaluatedResult = new JsonObject { ["greeting"] = "world" }; + + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => evaluatedResult.DeepClone()); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var executor = CreateExecutor(taskContext); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Pass_Input_To_Expression_Evaluator() + { + // Arrange + var setData = new JsonObject { ["result"] = "${ .value }" }; + var definition = new SetTaskDefinition { Set = setData }; + var input = new JsonObject { ["value"] = 42 }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => JsonValue.Create(42)); + + var executor = CreateExecutor(taskContext); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Once); + } + + [Fact] + public async Task Execute_Should_Use_Then_Directive_From_Definition() + { + // Arrange + var setData = new JsonObject { ["key"] = "${ .val }" }; + var definition = new SetTaskDefinition { Set = setData, Then = FlowDirective.End }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => JsonValue.Create("value")); + + var executor = CreateExecutor(taskContext); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var definition = new SetTaskDefinition { Set = new JsonObject { ["k"] = "${ .v }" } }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = CreateExecutor(taskContext); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static SetTaskExecutor CreateExecutor(Mock> taskContext) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ShellRunTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ShellRunTaskExecutorTests.cs new file mode 100644 index 0000000..7a35604 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/ShellRunTaskExecutorTests.cs @@ -0,0 +1,49 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class ShellRunTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Shell_Should_Skip_When_Already_Completed() + { + // Arrange + var definition = new RunTaskDefinition + { + Run = new ProcessTypeDefinition { Shell = new ShellProcessDefinition { Command = "echo hello" } } + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new ShellRunTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/SwitchTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/SwitchTaskExecutorTests.cs new file mode 100644 index 0000000..c923fd9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/SwitchTaskExecutorTests.cs @@ -0,0 +1,164 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class SwitchTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Match_First_True_Case() + { + // Arrange + var switchCases = new Map(); + switchCases.Add(new("caseA", new SwitchCaseDefinition { When = "${ .status == \"active\" }", Then = FlowDirective.End })); + switchCases.Add(new("caseB", new SwitchCaseDefinition { When = "${ .status == \"inactive\" }", Then = FlowDirective.Continue })); + + var definition = new SwitchTaskDefinition { Switch = switchCases }; + var input = new JsonObject { ["status"] = "active" }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + // First case matches + expressionMock.Setup(e => e.EvaluateAsync( + It.Is(s => s.Contains("active")), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(JsonValue.Create(true)); + // Second case does not match + expressionMock.Setup(e => e.EvaluateAsync( + It.Is(s => s.Contains("inactive")), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(JsonValue.Create(false)); + + var executor = new SwitchTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Use_Default_Case_When_No_Condition_Matches() + { + // Arrange + var switchCases = new Map(); + switchCases.Add(new("caseA", new SwitchCaseDefinition { When = "${ .status == \"active\" }", Then = FlowDirective.End })); + switchCases.Add(new("default", new SwitchCaseDefinition { Then = FlowDirective.Continue })); + + var definition = new SwitchTaskDefinition { Switch = switchCases }; + var input = new JsonObject { ["status"] = "unknown" }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + // No cases match + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(JsonValue.Create(false)); + + var executor = new SwitchTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Use_Task_Then_When_No_Case_Matches_And_No_Default() + { + // Arrange + var switchCases = new Map(); + switchCases.Add(new("caseA", new SwitchCaseDefinition { When = "${ .status == \"active\" }", Then = FlowDirective.End })); + + var definition = new SwitchTaskDefinition { Switch = switchCases, Then = FlowDirective.Continue }; + var input = new JsonObject { ["status"] = "unknown" }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var expressionMock = Mock.Get(taskContext.Object.Workflow.Expressions); + expressionMock.Setup(e => e.EvaluateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(JsonValue.Create(false)); + + var executor = new SwitchTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var switchCases = new Map(); + switchCases.Add(new("caseA", new SwitchCaseDefinition { When = "${ true }", Then = FlowDirective.End })); + + var definition = new SwitchTaskDefinition { Switch = switchCases }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new SwitchTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/TaskExecutorTestsBase.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/TaskExecutorTestsBase.cs new file mode 100644 index 0000000..073b13a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/TaskExecutorTestsBase.cs @@ -0,0 +1,135 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public abstract class TaskExecutorTestsBase +{ + + protected static Mock> CreateTaskExecutionContext( + TDefinition definition, JsonNode? input = null, JsonObject? contextData = null, + JsonObject? arguments = null, string? taskStatus = null, string? taskName = null, JsonPointer? reference = null) + where TDefinition : TaskDefinition + { + input ??= new JsonObject(); + contextData ??= []; + arguments ??= []; + var effectiveReference = reference ?? JsonPointer.Parse("/test"); + + var taskInstance = new Mock { CallBase = true }; + taskInstance.Setup(i => i.Id).Returns("task-1"); + taskInstance.Setup(i => i.WorkflowId).Returns("workflow-1"); + taskInstance.Setup(i => i.Status).Returns(taskStatus); + taskInstance.Setup(i => i.Name).Returns(taskName); + taskInstance.Setup(i => i.Reference).Returns(effectiveReference); + taskInstance.Setup(i => i.IsExtension).Returns(false); + taskInstance.Setup(i => i.Input).Returns(input); + taskInstance.Setup(i => i.StartAsync(It.IsAny())).Returns(Task.CompletedTask); + taskInstance.Setup(i => i.SetOutputAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + taskInstance.Setup(i => i.SetErrorAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + taskInstance.Setup(i => i.CancelAsync(It.IsAny())).Returns(Task.CompletedTask); + taskInstance.Setup(i => i.SuspendAsync(It.IsAny())).Returns(Task.CompletedTask); + taskInstance.Setup(i => i.SkipAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + + var expressionEvaluator = new Mock(); + expressionEvaluator.Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((JsonNode?)null); + + var runtimeDescriptor = new RuntimeDescriptor() { Name = "test-runtime", Version = "1.0.0" }; + var runtime = new Mock(); + runtime.Setup(r => r.Descriptor).Returns(runtimeDescriptor); + + var workflowDefinition = new WorkflowDefinition + { + Document = new WorkflowDefinitionMetadata + { + Dsl = "1.0.0", + Name = "test-workflow", + Namespace = "test", + Version = "1.0.0" + }, + Do = [] + }; + + var workflowInstance = new Mock(); + workflowInstance.Setup(i => i.Id).Returns("workflow-1"); + workflowInstance.Setup(i => i.ContextData).Returns(contextData); + + var workflow = new Mock(); + workflow.Setup(w => w.Definition).Returns(workflowDefinition); + workflow.Setup(w => w.Instance).Returns(workflowInstance.Object); + workflow.Setup(w => w.Expressions).Returns(expressionEvaluator.Object); + workflow.Setup(w => w.Runtime).Returns(runtime.Object); + workflow.Setup(w => w.GetExpressionEvaluationArguments()).Returns(new JsonObject()); + workflow.Setup(w => w.CreateTaskAsync( + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((TaskDefinition def, JsonPointer path, JsonNode inp, ITaskExecutionContext? parent, bool isExt, CancellationToken ct) => + { + var subInstance = new Mock { CallBase = true }; + subInstance.Setup(i => i.Id).Returns(Guid.NewGuid().ToString()); + subInstance.Setup(i => i.WorkflowId).Returns("workflow-1"); + subInstance.Setup(i => i.Reference).Returns(path); + subInstance.Setup(i => i.Name).Returns(path.ToString().Split('/').Last()); + subInstance.Setup(i => i.IsExtension).Returns(isExt); + subInstance.Setup(i => i.Input).Returns(inp); + subInstance.Setup(i => i.StartAsync(It.IsAny())).Returns(Task.CompletedTask); + subInstance.Setup(i => i.SetOutputAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + subInstance.Setup(i => i.SetErrorAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + subInstance.Setup(i => i.CancelAsync(It.IsAny())).Returns(Task.CompletedTask); + subInstance.Setup(i => i.SuspendAsync(It.IsAny())).Returns(Task.CompletedTask); + subInstance.Setup(i => i.SkipAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + return Task.FromResult(subInstance.Object); + }); + + var taskContext = new Mock>(); + taskContext.Setup(c => c.Workflow).Returns(workflow.Object); + taskContext.Setup(c => c.Definition).Returns(definition); + taskContext.Setup(c => c.Instance).Returns(taskInstance.Object); + taskContext.Setup(c => c.Arguments).Returns(() => arguments.DeepClone().AsObject()!); + taskContext.Setup(c => c.StartAsync(It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.SetContextDataAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.SetErrorAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.SkipAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.SuspendAsync(It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.CancelAsync(It.IsAny())).Returns(Task.CompletedTask); + taskContext.Setup(c => c.GetSubTasksAsync(It.IsAny())).Returns(AsyncEnumerableEmpty()); + + return taskContext; + } + + protected static Mock CreateServiceProvider() => new(); + + protected static Mock CreateExecutionContextFactory() => new(); + + protected static Mock CreateExecutorFactory() => new(); + + protected static Mock CreateSchemaHandlerProvider() => new(); + + protected static async IAsyncEnumerable AsyncEnumerableEmpty() + { + await Task.CompletedTask; + yield break; + } + + protected static async IAsyncEnumerable AsyncEnumerableOf(params T[] items) + { + foreach (var item in items) + { + yield return item; + await Task.CompletedTask; + } + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/TryTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/TryTaskExecutorTests.cs new file mode 100644 index 0000000..e826aaa --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/TryTaskExecutorTests.cs @@ -0,0 +1,197 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class TryTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Create_Try_Subtask() + { + // Arrange + var tryTasks = new Map(); + tryTasks.Add(new("riskyTask", new SetTaskDefinition { Set = new JsonObject { ["result"] = "ok" } })); + + var definition = new TryTaskDefinition + { + Try = tryTasks, + Catch = new ErrorCatcherDefinition() + }; + var input = new JsonObject { ["data"] = "test" }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var childExecutor = CreateCompletingChildExecutor(new JsonObject { ["result"] = "ok" }); + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())).Returns(childExecutor.Object); + + var contextFactory = new Mock(); + contextFactory.Setup(f => f.Create( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((IWorkflowExecutionContext wf, TaskDefinition def, ITaskInstance inst, JsonObject? args) => + { + var childCtx = new Mock(); + childCtx.Setup(c => c.Workflow).Returns(wf); + childCtx.Setup(c => c.Instance).Returns(inst); + childCtx.Setup(c => c.Definition).Returns(def); + childCtx.Setup(c => c.Arguments).Returns(args ?? new JsonObject()); + return childCtx.Object; + }); + + var executor = new TryTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + contextFactory.Object, + executorFactory.Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + Mock.Get(taskContext.Object.Workflow).Verify( + w => w.CreateTaskAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Set_Result_When_Try_Succeeds() + { + // Arrange + var tryTasks = new Map(); + tryTasks.Add(new("successTask", new SetTaskDefinition { Set = new JsonObject { ["result"] = "success" } })); + + var definition = new TryTaskDefinition + { + Try = tryTasks, + Catch = new ErrorCatcherDefinition() + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var childExecutor = CreateCompletingChildExecutor(new JsonObject { ["result"] = "success" }); + var executorFactory = new Mock(); + executorFactory.Setup(f => f.Create(It.IsAny())).Returns(childExecutor.Object); + + var contextFactory = new Mock(); + contextFactory.Setup(f => f.Create( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((IWorkflowExecutionContext wf, TaskDefinition def, ITaskInstance inst, JsonObject? args) => + { + var childCtx = new Mock(); + childCtx.Setup(c => c.Workflow).Returns(wf); + childCtx.Setup(c => c.Instance).Returns(inst); + childCtx.Setup(c => c.Definition).Returns(def); + childCtx.Setup(c => c.Arguments).Returns(args ?? new JsonObject()); + return childCtx.Object; + }); + + var executor = new TryTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + contextFactory.Object, + executorFactory.Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var tryTasks = new Map(); + tryTasks.Add(new("task", new SetTaskDefinition { Set = new JsonObject { ["k"] = "v" } })); + + var definition = new TryTaskDefinition + { + Try = tryTasks, + Catch = new ErrorCatcherDefinition() + }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new TryTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + + static Mock CreateCompletingChildExecutor(JsonNode? output = null) + { + var childInstance = new Mock { CallBase = true }; + childInstance.Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + childInstance.Setup(i => i.Output).Returns(output); + childInstance.Setup(i => i.Next).Returns(FlowDirective.Continue); + childInstance.Setup(i => i.Reference).Returns(JsonPointer.Parse("/try")); + childInstance.Setup(i => i.Name).Returns("try"); + + var childWorkflowInstance = new Mock(); + childWorkflowInstance.Setup(i => i.ContextData).Returns(new JsonObject()); + var childWorkflow = new Mock(); + childWorkflow.Setup(w => w.Instance).Returns(childWorkflowInstance.Object); + var childTaskContext = new Mock(); + childTaskContext.Setup(c => c.Instance).Returns(childInstance.Object); + childTaskContext.Setup(c => c.Workflow).Returns(childWorkflow.Object); + + var childExecutor = new Mock(); + childExecutor.Setup(e => e.Task).Returns(childTaskContext.Object); + childExecutor.Setup(e => e.InitializeAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.ExecuteAsync(It.IsAny())).Returns(Task.CompletedTask); + childExecutor.Setup(e => e.Subscribe(It.IsAny>())) + .Returns((IObserver observer) => + { + observer.OnCompleted(); + return Mock.Of(); + }); + + return childExecutor; + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/WaitTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/WaitTaskExecutorTests.cs new file mode 100644 index 0000000..b2c5a59 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/WaitTaskExecutorTests.cs @@ -0,0 +1,132 @@ +// 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.UnitTests.Cases.Runtime.Services.Executors; + +public class WaitTaskExecutorTests + : TaskExecutorTestsBase +{ + + [Fact] + public async Task Execute_Should_Wait_And_Set_Result_With_Input() + { + // Arrange + var definition = new WaitTaskDefinition { Wait = Duration.FromMilliseconds(10) }; + var input = new JsonObject { ["data"] = "test" }; + var taskContext = CreateTaskExecutionContext(definition, input); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + taskContext.Object.Workflow.Expressions + .AsIMock() + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new WaitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Use_Then_Directive() + { + // Arrange + var definition = new WaitTaskDefinition { Wait = Duration.FromMilliseconds(10), Then = FlowDirective.End }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + taskContext.Object.Workflow.Expressions + .AsIMock() + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((string expr, JsonNode inp, JsonObject? args, CancellationToken ct) => inp); + + var executor = new WaitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Be_Cancellable() + { + // Arrange + var definition = new WaitTaskDefinition { Wait = Duration.FromSeconds(5) }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var executor = new WaitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50)); + + // Act & Assert - should not throw; the base class catches OperationCanceledException + await executor.ExecuteAsync(cts.Token); + } + + [Fact] + public async Task Execute_Should_Skip_When_Already_Completed() + { + // Arrange + var definition = new WaitTaskDefinition { Wait = Duration.FromMilliseconds(10) }; + var taskContext = CreateTaskExecutionContext(definition); + + Mock.Get(taskContext.Object.Instance).Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + var executor = new WaitTaskExecutor( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify( + i => i.StartAsync(It.IsAny()), + Times.Never); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/WorkflowRunTaskExecutorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/WorkflowRunTaskExecutorTests.cs new file mode 100644 index 0000000..c28df78 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/Executors/WorkflowRunTaskExecutorTests.cs @@ -0,0 +1,282 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Configuration; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services.Executors; + +public class WorkflowRunTaskExecutorTests + : TaskExecutorTestsBase +{ + + static RunTaskDefinition CreateDefinition(bool? @await = null, JsonObject? input = null) => new() + { + Run = new ProcessTypeDefinition + { + Workflow = new WorkflowProcessDefinition + { + Namespace = "test", + Name = "sub-workflow", + Version = "1.0.0", + Input = input + }, + Await = @await + } + }; + + static WorkflowDefinition CreateSubflowDefinition() => new() + { + Document = new WorkflowDefinitionMetadata + { + Dsl = "1.0.0", + Namespace = "test", + Name = "sub-workflow", + Version = "1.0.0" + }, + Do = [] + }; + + static (Mock process, Subject events) CreateProcessMock() + { + var events = new Subject(); + var process = new Mock(); + process.Setup(p => p.Subscribe(It.IsAny>())) + .Returns((IObserver o) => events.Subscribe(o)); + process.Setup(p => p.CancelAsync(It.IsAny())).Returns(Task.CompletedTask); + return (process, events); + } + + static WorkflowRunTaskExecutor CreateExecutor(Mock> taskContext, Mock definitions) => new( + CreateServiceProvider().Object, + Mock.Of>(), + CreateExecutionContextFactory().Object, + CreateExecutorFactory().Object, + CreateSchemaHandlerProvider().Object, + taskContext.Object, + definitions.Object); + + [Fact] + public async Task Execute_Should_Resolve_Definition_Run_Subflow_And_Set_Result_With_Output() + { + // Arrange + var definition = CreateDefinition(); + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var subflowDefinition = CreateSubflowDefinition(); + var definitions = new Mock(); + definitions.Setup(d => d.GetAsync("test", "sub-workflow", "1.0.0", It.IsAny())) + .ReturnsAsync(subflowDefinition); + + var expectedOutput = new JsonObject { ["result"] = "ok" }; + var (process, events) = CreateProcessMock(); + process.Setup(p => p.WaitAsync(It.IsAny())) + .Returns(() => + { + events.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Completed, expectedOutput)); + return Task.CompletedTask; + }); + + var runtimeMock = Mock.Get(taskContext.Object.Workflow.Runtime); + runtimeMock.Setup(r => r.RunAsync(subflowDefinition, It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(process.Object); + + var executor = CreateExecutor(taskContext, definitions); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + definitions.Verify(d => d.GetAsync("test", "sub-workflow", "1.0.0", It.IsAny()), Times.Once); + runtimeMock.Verify(r => r.RunAsync(subflowDefinition, It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + taskContext.Verify(c => c.SetResultAsync(It.Is(n => n == expectedOutput), It.IsAny(), It.IsAny()), Times.AtLeastOnce); + taskContext.Verify(c => c.SetErrorAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task Execute_Should_Evaluate_Input_Expression_Before_Running_Subflow() + { + // Arrange + var inputExpression = new JsonObject { ["mapped"] = "${ .value }" }; + var definition = CreateDefinition(input: inputExpression); + var parentInput = new JsonObject { ["value"] = 42 }; + var taskContext = CreateTaskExecutionContext(definition, parentInput); + Mock.Get(taskContext.Object.Instance).Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var evaluatedInput = new JsonObject { ["mapped"] = 42 }; + Mock.Get(taskContext.Object.Workflow.Expressions) + .Setup(e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(evaluatedInput); + + var subflowDefinition = CreateSubflowDefinition(); + var definitions = new Mock(); + definitions.Setup(d => d.GetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(subflowDefinition); + + JsonObject? capturedInput = null; + var (process, events) = CreateProcessMock(); + process.Setup(p => p.WaitAsync(It.IsAny())) + .Returns(() => + { + events.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Completed, new JsonObject())); + return Task.CompletedTask; + }); + + Mock.Get(taskContext.Object.Workflow.Runtime) + .Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((WorkflowDefinition _, JsonObject? i, WorkflowExecutionsOptions? _, CancellationToken _) => capturedInput = i) + .ReturnsAsync(process.Object); + + var executor = CreateExecutor(taskContext, definitions); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + Mock.Get(taskContext.Object.Workflow.Expressions).Verify( + e => e.EvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce); + capturedInput.Should().NotBeNull(); + } + + [Fact] + public async Task Execute_Should_Return_Immediately_When_Await_Is_False() + { + // Arrange + var definition = CreateDefinition(@await: false); + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var definitions = new Mock(); + definitions.Setup(d => d.GetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(CreateSubflowDefinition()); + + var (process, _) = CreateProcessMock(); + Mock.Get(taskContext.Object.Workflow.Runtime) + .Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(process.Object); + + var executor = CreateExecutor(taskContext, definitions); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + process.Verify(p => p.WaitAsync(It.IsAny()), Times.Never); + taskContext.Verify(c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.AtLeastOnce); + } + + [Fact] + public async Task Execute_Should_Set_Error_When_Subflow_Faults() + { + // Arrange + var definition = CreateDefinition(); + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var definitions = new Mock(); + definitions.Setup(d => d.GetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(CreateSubflowDefinition()); + + var subflowError = new Error + { + Type = ErrorType.Runtime, + Status = ErrorStatus.Runtime, + Title = ErrorTitle.Runtime, + Detail = "Boom" + }; + var (process, events) = CreateProcessMock(); + process.Setup(p => p.WaitAsync(It.IsAny())) + .Returns(() => + { + events.OnNext(new WorkflowLifeCycleEvent(WorkflowLifeCycleEventType.Faulted, subflowError)); + return Task.FromException(new RuntimeErrorException(subflowError)); + }); + + Mock.Get(taskContext.Object.Workflow.Runtime) + .Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(process.Object); + + var executor = CreateExecutor(taskContext, definitions); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify(c => c.SetErrorAsync(It.Is(e => e.Detail == "Boom"), It.IsAny()), Times.AtLeastOnce); + taskContext.Verify(c => c.SetResultAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task Execute_Should_Set_Error_When_Definition_Not_Found() + { + // Arrange + var definition = CreateDefinition(); + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var definitions = new Mock(); + definitions.Setup(d => d.GetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync((WorkflowDefinition?)null); + + var executor = CreateExecutor(taskContext, definitions); + + // Act + await executor.ExecuteAsync(TestContext.Current.CancellationToken); + + // Assert + taskContext.Verify(c => c.SetErrorAsync(It.IsAny(), It.IsAny()), Times.AtLeastOnce); + Mock.Get(taskContext.Object.Workflow.Runtime).Verify( + r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + } + + [Fact] + public async Task Cancel_Should_Propagate_To_Running_Subflow() + { + // Arrange + var definition = CreateDefinition(); + var taskContext = CreateTaskExecutionContext(definition); + Mock.Get(taskContext.Object.Instance).Setup(i => i.Status).Returns(Sdk.Runtime.TaskStatus.Running); + + var definitions = new Mock(); + definitions.Setup(d => d.GetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(CreateSubflowDefinition()); + + var waitTcs = new TaskCompletionSource(); + var (process, _) = CreateProcessMock(); + process.Setup(p => p.WaitAsync(It.IsAny())).Returns(waitTcs.Task); + process.Setup(p => p.CancelAsync(It.IsAny())) + .Returns(() => + { + waitTcs.TrySetCanceled(); + return Task.CompletedTask; + }); + + Mock.Get(taskContext.Object.Workflow.Runtime) + .Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(process.Object); + + var executor = CreateExecutor(taskContext, definitions); + + // Act + var executeTask = executor.ExecuteAsync(TestContext.Current.CancellationToken); + await Task.Yield(); + await executor.CancelAsync(TestContext.Current.CancellationToken); + await executeTask; + + // Assert + process.Verify(p => p.CancelAsync(It.IsAny()), Times.AtLeastOnce); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/IRuntimeExpressionEvaluatorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/IRuntimeExpressionEvaluatorTests.cs new file mode 100644 index 0000000..369a526 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/IRuntimeExpressionEvaluatorTests.cs @@ -0,0 +1,33 @@ +// 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.UnitTests.Cases.Runtime.Services; + +public abstract class IRuntimeExpressionEvaluatorTests +{ + + protected abstract IRuntimeExpressionEvaluator ExpressionEvaluator { get; } + + [Fact] + public abstract Task Evaluate_Expression_Should_Work(); + + [Fact] + public abstract Task Evaluate_Expression_Against_Input_Should_Work(); + + [Fact] + public abstract Task Evaluate_Expression_Against_Arguments_Should_Work(); + + [Fact] + public abstract Task Evaluate_JsonObject_Should_Work(); + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryCloudEventBusTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryCloudEventBusTests.cs new file mode 100644 index 0000000..e0c0adc --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryCloudEventBusTests.cs @@ -0,0 +1,83 @@ +// 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 System.Reactive.Linq; +using ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services; + +public class InMemoryCloudEventBusTests +{ + + [Fact] + public async Task PublishAsync_Should_Emit_Event_To_Subscribers() + { + //arrange + using var bus = new InMemoryCloudEventBus(); + var eventSource = new Uri("https://example.com"); + var eventType = "com.test"; + var cloudEvent = new CloudEvent { Source = eventSource, Type = eventType }; + var observable = await bus.SubscribeAsync(TestContext.Current.CancellationToken); + ICloudEvent? received = null; + var tcs = new TaskCompletionSource(); + observable.Subscribe(e => { received = e; tcs.TrySetResult(); }); + + //act + await bus.PublishAsync(cloudEvent, TestContext.Current.CancellationToken); + await tcs.Task.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); + + //assert + received.Should().NotBeNull(); + received!.Type.Should().Be(eventType); + received!.Source.Should().Be(eventSource); + } + + [Fact] + public async Task SubscribeAsync_Should_Return_Observable() + { + //arrange + using var bus = new InMemoryCloudEventBus(); + + //act + var observable = await bus.SubscribeAsync(TestContext.Current.CancellationToken); + + //assert + observable.Should().NotBeNull(); + } + + [Fact] + public async Task PublishAsync_Should_Deliver_To_Multiple_Subscribers() + { + //arrange + using var bus = new InMemoryCloudEventBus(); + var eventType = "com.multi"; + var cloudEvent = new CloudEvent { Source = new Uri("https://example.com"), Type = eventType }; + var obs1 = await bus.SubscribeAsync(TestContext.Current.CancellationToken); + var obs2 = await bus.SubscribeAsync(TestContext.Current.CancellationToken); + var count1 = 0; + var count2 = 0; + var tcs1 = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); + obs1.Subscribe(_ => { count1++; tcs1.TrySetResult(); }); + obs2.Subscribe(_ => { count2++; tcs2.TrySetResult(); }); + + //act + await bus.PublishAsync(cloudEvent, TestContext.Current.CancellationToken); + await Task.WhenAll(tcs1.Task, tcs2.Task).WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); + + //assert + count1.Should().Be(1); + count2.Should().Be(1); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryTaskStateStoreTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryTaskStateStoreTests.cs new file mode 100644 index 0000000..50d8637 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryTaskStateStoreTests.cs @@ -0,0 +1,98 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services; + +public class InMemoryTaskStateStoreTests +{ + + [Fact] + public async Task AddAsync_Should_Store_And_Return_State() + { + //arrange + var store = new InMemoryTaskStateStore(); + var workflowId = "wf-1"; + var taskId = "task-1"; + var state = new Mock(); + state.Setup(s => s.WorkflowId).Returns(workflowId); + state.Setup(s => s.Id).Returns(taskId); + + //act + var result = await store.AddAsync(state.Object, TestContext.Current.CancellationToken); + + //assert + result.Should().Be(state.Object); + } + + [Fact] + public async Task GetAsync_Should_Return_Stored_State() + { + //arrange + var store = new InMemoryTaskStateStore(); + var workflowId = "wf-1"; + var taskId = "task-1"; + var state = new Mock(); + state.Setup(s => s.WorkflowId).Returns(workflowId); + state.Setup(s => s.Id).Returns(taskId); + await store.AddAsync(state.Object, TestContext.Current.CancellationToken); + + //act + var result = await store.GetAsync(workflowId, taskId, TestContext.Current.CancellationToken); + + //assert + result.Should().Be(state.Object); + } + + [Fact] + public async Task GetAsync_Should_Throw_When_Not_Found() + { + //arrange + var store = new InMemoryTaskStateStore(); + var workflowId = "wf-missing"; + var taskId = "task-missing"; + + //act + var act = () => store.GetAsync(workflowId, taskId, TestContext.Current.CancellationToken); + + //assert + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task UpdateAsync_Should_Overwrite_Existing_State() + { + //arrange + var store = new InMemoryTaskStateStore(); + var workflowId = "wf-1"; + var taskId = "task-1"; + var original = new Mock(); + original.Setup(s => s.WorkflowId).Returns(workflowId); + original.Setup(s => s.Id).Returns(taskId); + original.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Running); + await store.AddAsync(original.Object, TestContext.Current.CancellationToken); + var updated = new Mock(); + updated.Setup(s => s.WorkflowId).Returns(workflowId); + updated.Setup(s => s.Id).Returns(taskId); + updated.Setup(s => s.Status).Returns(Sdk.Runtime.TaskStatus.Completed); + + //act + await store.UpdateAsync(updated.Object, TestContext.Current.CancellationToken); + var result = await store.GetAsync(workflowId, taskId, TestContext.Current.CancellationToken); + + //assert + result.Status.Should().Be(Sdk.Runtime.TaskStatus.Completed); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryWorkflowStateStoreTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryWorkflowStateStoreTests.cs new file mode 100644 index 0000000..a0dcd24 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/InMemoryWorkflowStateStoreTests.cs @@ -0,0 +1,77 @@ +// 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 Microsoft.Extensions.Caching.Memory; +using ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services; + +public class InMemoryWorkflowStateStoreTests +{ + + [Fact] + public async Task AddAsync_Should_Store_And_Return_State() + { + //arrange + using var cache = new MemoryCache(new MemoryCacheOptions()); + var store = new InMemoryWorkflowStore(cache); + var definition = new WorkflowDefinition + { + Document = new WorkflowDefinitionMetadata { Dsl = "1.0.0", Name = "test-workflow", Namespace = "test", Version = "1.0.0" }, + Do = [] + }; + + //act + var result = await store.AddAsync(definition, null, TestContext.Current.CancellationToken); + + //assert + result.Should().NotBeNull(); + result.Id.Should().NotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task GetAsync_Should_Return_Stored_State() + { + //arrange + using var cache = new MemoryCache(new MemoryCacheOptions()); + var store = new InMemoryWorkflowStore(cache); + var definition = new WorkflowDefinition + { + Document = new WorkflowDefinitionMetadata { Dsl = "1.0.0", Name = "test-workflow", Namespace = "test", Version = "1.0.0" }, + Do = [] + }; + var added = await store.AddAsync(definition, null, TestContext.Current.CancellationToken); + + //act + var result = await store.GetAsync(added.Id, TestContext.Current.CancellationToken); + + //assert + result.Should().Be(added); + } + + [Fact] + public async Task GetAsync_Should_Throw_When_Not_Found() + { + //arrange + using var cache = new MemoryCache(new MemoryCacheOptions()); + var store = new InMemoryWorkflowStore(cache); + var id = "wf-missing"; + + //act + var act = () => store.GetAsync(id, TestContext.Current.CancellationToken); + + //assert + await act.Should().ThrowAsync(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JQRuntimeExpressionEvaluatorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JQRuntimeExpressionEvaluatorTests.cs new file mode 100644 index 0000000..efd3cad --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JQRuntimeExpressionEvaluatorTests.cs @@ -0,0 +1,100 @@ +// 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.UnitTests.Cases.Runtime.Services; + +public sealed class JQRuntimeExpressionEvaluatorTests + : IRuntimeExpressionEvaluatorTests +{ + + protected override IRuntimeExpressionEvaluator ExpressionEvaluator { get; } = new JQRuntimeExpressionEvaluator(); + + [Fact] + public override async Task Evaluate_Expression_Should_Work() + { + //arrange + var expression = "1 + 2"; + //act + var result = await ExpressionEvaluator.EvaluateAsync(expression, new JsonObject(), null, TestContext.Current.CancellationToken); + //assert + result.Should().NotBeNull(); + result.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + + [Fact] + public override async Task Evaluate_Expression_Against_Input_Should_Work() + { + //arrange + var expression = ".value + 2"; + var input = new JsonObject + { + ["value"] = 1 + }; + //act + var result = await ExpressionEvaluator.EvaluateAsync(expression, input, null, TestContext.Current.CancellationToken); + //assert + result.Should().NotBeNull(); + result.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + + [Fact] + public override async Task Evaluate_Expression_Against_Arguments_Should_Work() + { + //arrange + var arguments = new JsonObject() + { + ["ARG1"] = 2 + }; + var input = new JsonObject + { + ["value"] = 1 + }; + var expression = ".value + $ARG1"; + //act + var result = await ExpressionEvaluator.EvaluateAsync(expression, input, arguments, TestContext.Current.CancellationToken); + //assert + result.Should().NotBeNull(); + result.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + + [Fact] + public override async Task Evaluate_JsonObject_Should_Work() + { + //arrange + var propertyName = "additionResult"; + var arguments = new JsonObject() + { + ["ARG1"] = 2 + }; + var input = new JsonObject + { + ["value"] = 1 + }; + var value = new JsonObject() + { + [propertyName] = "${ .value + $ARG1 }" + }; + //act + var result = await ExpressionEvaluator.EvaluateAsync(value, input, arguments, TestContext.Current.CancellationToken); + //assert + result.Should().NotBeNull(); + result.AsObject().TryGetPropertyValue(propertyName, out var propertyValue).Should().BeTrue(); + propertyValue.Should().NotBeNull(); + propertyValue.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JSRuntimeExpressionEvaluatorTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JSRuntimeExpressionEvaluatorTests.cs new file mode 100644 index 0000000..9009a38 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JSRuntimeExpressionEvaluatorTests.cs @@ -0,0 +1,113 @@ +// 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.UnitTests.Cases.Runtime.Services; + +public sealed class JSRuntimeExpressionEvaluatorTests + : IRuntimeExpressionEvaluatorTests +{ + + protected override IRuntimeExpressionEvaluator ExpressionEvaluator { get; } = new JSRuntimeExpressionEvaluator(); + + [Fact] + public override async Task Evaluate_Expression_Should_Work() + { + // arrange + var expression = "1 + 2"; + + // act + var result = await ExpressionEvaluator.EvaluateAsync(expression, new JsonObject(), null, TestContext.Current.CancellationToken); + + // assert + result.Should().NotBeNull(); + result.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + + [Fact] + public override async Task Evaluate_Expression_Against_Input_Should_Work() + { + // arrange + var expression = "$.value + 2"; + var input = new JsonObject + { + ["value"] = 1 + }; + + // act + var result = await ExpressionEvaluator.EvaluateAsync(expression, input, null, TestContext.Current.CancellationToken); + + // assert + result.Should().NotBeNull(); + result.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + + [Fact] + public override async Task Evaluate_Expression_Against_Arguments_Should_Work() + { + // arrange + var arguments = new JsonObject() + { + ["ARG1"] = 2 + }; + + var input = new JsonObject + { + ["value"] = 1 + }; + + var expression = "$.value + ARG1"; + + // act + var result = await ExpressionEvaluator.EvaluateAsync(expression, input, arguments, TestContext.Current.CancellationToken); + + // assert + result.Should().NotBeNull(); + result.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + + [Fact] + public override async Task Evaluate_JsonObject_Should_Work() + { + // arrange + var propertyName = "additionResult"; + + var arguments = new JsonObject() + { + ["ARG1"] = 2 + }; + + var input = new JsonObject + { + ["value"] = 1 + }; + + var value = new JsonObject() + { + [propertyName] = "${ $.value + ARG1 }" + }; + + // act + var result = await ExpressionEvaluator.EvaluateAsync(value, input, arguments, TestContext.Current.CancellationToken); + + // assert + result.Should().NotBeNull(); + result.AsObject().TryGetPropertyValue(propertyName, out var propertyValue).Should().BeTrue(); + propertyValue.Should().NotBeNull(); + propertyValue.AsValue().TryGetValue(out var additionResult).Should().BeTrue(); + additionResult.Should().Be(3); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JsonSchemaHandlerTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JsonSchemaHandlerTests.cs new file mode 100644 index 0000000..24f619d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/JsonSchemaHandlerTests.cs @@ -0,0 +1,86 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services; + +public class JsonSchemaHandlerTests +{ + + [Fact] + public void Supports_Should_Return_True_For_Json() + { + //arrange + var format = "json"; + var handler = new JsonSchemaHandler(Mock.Of()); + + //act + var result = handler.Supports(format); + + //assert + result.Should().BeTrue(); + } + + [Fact] + public void Supports_Should_Return_False_For_Other_Formats() + { + //arrange + var format = "xml"; + var handler = new JsonSchemaHandler(Mock.Of()); + + //act + var result = handler.Supports(format); + + //assert + result.Should().BeFalse(); + } + + [Fact] + public async Task ValidateAsync_Should_Pass_For_Valid_Document() + { + //arrange + var schemaJson = """{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}"""; + var schema = new SchemaDefinition { Document = new OneOf(schemaJson) }; + var nameKey = "name"; + var nameValue = "test"; + var input = new JsonObject { [nameKey] = nameValue }; + var handler = new JsonSchemaHandler(Mock.Of()); + + //act + var result = await handler.ValidateAsync(input, schema, TestContext.Current.CancellationToken); + + //assert + result.IsValid.Should().BeTrue(); + } + + [Fact] + public async Task ValidateAsync_Should_Fail_For_Invalid_Document() + { + //arrange + var schemaJson = """{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}"""; + var schema = new SchemaDefinition { Document = new OneOf(schemaJson) }; + var wrongKey = "wrong"; + var wrongValue = 123; + var input = new JsonObject { [wrongKey] = wrongValue }; + var handler = new JsonSchemaHandler(Mock.Of()); + + //act + var result = await handler.ValidateAsync(input, schema, TestContext.Current.CancellationToken); + + //assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().NotBeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/RuntimeExpressionEvaluatorProviderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/RuntimeExpressionEvaluatorProviderTests.cs new file mode 100644 index 0000000..d8de34a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/RuntimeExpressionEvaluatorProviderTests.cs @@ -0,0 +1,53 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services; + +public class RuntimeExpressionEvaluatorProviderTests +{ + + [Fact] + public void GetEvaluator_Should_Return_Evaluator_That_Supports_Language() + { + //arrange + var language = "jq"; + var mockEvaluator = new Mock(); + mockEvaluator.Setup(e => e.Supports(language)).Returns(true); + var provider = new RuntimeExpressionEvaluatorProvider([mockEvaluator.Object]); + + //act + var evaluator = provider.GetEvaluator(language); + + //assert + evaluator.Should().Be(mockEvaluator.Object); + } + + [Fact] + public void GetEvaluator_Should_Return_Null_When_No_Evaluator_Supports_Language() + { + //arrange + var language = "unknown"; + var mockEvaluator = new Mock(); + mockEvaluator.Setup(e => e.Supports(It.IsAny())).Returns(false); + var provider = new RuntimeExpressionEvaluatorProvider([mockEvaluator.Object]); + + //act + var evaluator = provider.GetEvaluator(language); + + //assert + evaluator.Should().BeNull(); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/SchemaHandlerProviderTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/SchemaHandlerProviderTests.cs new file mode 100644 index 0000000..93fe1d3 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/SchemaHandlerProviderTests.cs @@ -0,0 +1,71 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Services; + +namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Runtime.Services; + +public class SchemaHandlerProviderTests +{ + + [Fact] + public void GetHandler_Should_Return_Handler_That_Supports_Format() + { + //arrange + var format = "json"; + var mockHandler = new Mock(); + mockHandler.Setup(h => h.Supports(format)).Returns(true); + var provider = new SchemaHandlerProvider([mockHandler.Object]); + + //act + var handler = provider.GetHandler(format); + + //assert + handler.Should().Be(mockHandler.Object); + } + + [Fact] + public void GetHandler_Should_Return_Null_When_No_Handler_Supports_Format() + { + //arrange + var format = "unsupported"; + var mockHandler = new Mock(); + mockHandler.Setup(h => h.Supports(It.IsAny())).Returns(false); + var provider = new SchemaHandlerProvider([mockHandler.Object]); + + //act + var handler = provider.GetHandler(format); + + //assert + handler.Should().BeNull(); + } + + [Fact] + public void GetHandler_Should_Return_First_Matching_Handler() + { + //arrange + var format = "json"; + var handler1 = new Mock(); + handler1.Setup(h => h.Supports(format)).Returns(true); + var handler2 = new Mock(); + handler2.Setup(h => h.Supports(format)).Returns(true); + var provider = new SchemaHandlerProvider([handler1.Object, handler2.Object]); + + //act + var result = provider.GetHandler(format); + + //assert + result.Should().Be(handler1.Object); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/TaskExecutorRegistryTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/TaskExecutorRegistryTests.cs new file mode 100644 index 0000000..39f47d2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Runtime/Services/TaskExecutorRegistryTests.cs @@ -0,0 +1,78 @@ +// 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.UnitTests.Cases.Runtime.Services; + +public class TaskExecutorRegistryTests +{ + + [Fact] + public void Resolve_Should_Return_Registered_Executor_Type() + { + //arrange + var registry = new TaskExecutorRegistry(); + var taskType = "custom"; + registry.Register(taskType); + + //act + var resolved = registry.Resolve(taskType); + + //assert + resolved.Should().Be(typeof(SetTaskExecutor)); + } + + [Fact] + public void Resolve_Should_Return_Null_For_Unknown_TaskType() + { + //arrange + var registry = new TaskExecutorRegistry(); + var unknownType = "nonexistent"; + + //act + var resolved = registry.Resolve(unknownType); + + //assert + resolved.Should().BeNull(); + } + + [Fact] + public void Register_By_Definition_Should_Map_Correctly() + { + //arrange + var registry = new TaskExecutorRegistry(); + + //act + registry.Register(); + var resolved = registry.Resolve(TaskType.Set); + + //assert + resolved.Should().Be(typeof(SetTaskExecutor)); + } + + [Fact] + public void Register_Should_Overwrite_Previous_Registration() + { + //arrange + var registry = new TaskExecutorRegistry(); + var taskType = "test"; + registry.Register(taskType); + + //act + registry.Register(taskType); + var resolved = registry.Resolve(taskType); + + //assert + resolved.Should().Be(typeof(WaitTaskExecutor)); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Validation/WorkflowDefinitionValidationTests.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Validation/WorkflowDefinitionValidationTests.cs deleted file mode 100644 index b4b6023..0000000 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/Cases/Validation/WorkflowDefinitionValidationTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -// 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 ServerlessWorkflow.Sdk.Builders; -using ServerlessWorkflow.Sdk.Validation; - -namespace ServerlessWorkflow.Sdk.UnitTests.Cases.Validation; - -public class WorkflowDefinitionValidationTests -{ - - [Fact] - public async Task Validate_Workflow_With_Task_Flowing_To_Undefined_Task_Should_Fail() - { - //arrange - var workflow = new WorkflowDefinitionBuilder() - .UseDsl(DslVersion.V1Alpha2) - .WithNamespace("fake-namespace") - .WithName("fake-workflow") - .WithVersion("0.1.0-fake") - .Do("fake-task-1", task => task - .Set("foo", "bar") - .Then("undefined")) - .Build(); - var validator = WorkflowDefinitionValidator.Create(); - - //act - var result = await validator.ValidateAsync(workflow); - - //assert - result.IsValid.Should().BeFalse(); - } - - [Fact] - public async Task Validate_Workflow_With_Undefined_Timeout_Should_Fail() - { - //arrange - var workflow = new WorkflowDefinitionBuilder() - .UseDsl(DslVersion.V1Alpha2) - .WithNamespace("fake-namespace") - .WithName("fake-workflow") - .WithVersion("0.1.0-fake") - .WithTimeout("undefined") - .Do("fake-task-1", task => task - .Set("foo", "bar")) - .Build(); - var validator = WorkflowDefinitionValidator.Create(); - - //act - var result = await validator.ValidateAsync(workflow); - - //assert - result.IsValid.Should().BeFalse(); - } - - [Fact] - public async Task Validate_Workflow_With_Task_With_Undefined_Timeout_Should_Fail() - { - //arrange - var workflow = new WorkflowDefinitionBuilder() - .UseDsl(DslVersion.V1Alpha2) - .WithNamespace("fake-namespace") - .WithName("fake-workflow") - .WithVersion("0.1.0-fake") - .Do("fake-task-1", task => task - .Set("foo", "bar") - .WithTimeout("undefined")) - .Build(); - var validator = WorkflowDefinitionValidator.Create(); - - //act - var result = await validator.ValidateAsync(workflow); - - //assert - result.IsValid.Should().BeFalse(); - } - - [Fact] - public async Task Validate_Workflow_With_Call_To_Undefined_Function_Should_Fail() - { - //arrange - var workflow = new WorkflowDefinitionBuilder() - .UseDsl(DslVersion.V1Alpha2) - .WithNamespace("fake-namespace") - .WithName("fake-workflow") - .WithVersion("0.1.0-fake") - .Do("fake-task-1", task => task - .Call("undefined")) - .Build(); - var validator = WorkflowDefinitionValidator.Create(); - - //act - var result = await validator.ValidateAsync(workflow); - - //assert - result.IsValid.Should().BeFalse(); - } - - [Fact] - public async Task Validate_Workflow_With_Switch_Flowing_To_Undefined_Task_Should_Fail() - { - //arrange - var workflow = new WorkflowDefinitionBuilder() - .UseDsl(DslVersion.V1Alpha2) - .WithNamespace("fake-namespace") - .WithName("fake-workflow") - .WithVersion("0.1.0-fake") - .Do("fake-task-1", task => task - .Switch() - .Case("fake-case-1", @case => - @case.Then("undefined"))) - .Build(); - var validator = WorkflowDefinitionValidator.Create(); - - //act - var result = await validator.ValidateAsync(workflow); - - //assert - result.IsValid.Should().BeFalse(); - } - -} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Extensions/MockExtensions.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Extensions/MockExtensions.cs new file mode 100644 index 0000000..9f48dc6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Extensions/MockExtensions.cs @@ -0,0 +1,23 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.UnitTests; + +internal static class MockExtensions +{ + + internal static Mock AsIMock(this T obj) where T : class => Mock.Get(obj); + +} \ No newline at end of file diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Extensions/ObjectAssertionsExtensions.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Extensions/ObjectAssertionsExtensions.cs new file mode 100644 index 0000000..00b0c41 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Extensions/ObjectAssertionsExtensions.cs @@ -0,0 +1,38 @@ +// 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. + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace ServerlessWorkflow.Sdk.UnitTests; + +internal static class ObjectAssertionsExtensions +{ + + public static void BeJsonEquivalentTo(this ObjectAssertions should, T expected) + { + should.BeEquivalentTo(expected, opts => opts + .Using(ctx => + ctx.Subject.ToJsonString().Should().Be(ctx.Expectation.ToJsonString())) + .WhenTypeIs() + .Using(ctx => + ctx.Subject?.ToJsonString().Should().Be(ctx.Expectation?.ToJsonString())) + .WhenTypeIs() + .Using(ctx => + ctx.Subject?.ToJsonString().Should().Be(ctx.Expectation?.ToJsonString())) + .WhenTypeIs() + .Using(ctx => + ctx.Subject?.ToJsonDocument().RootElement.ToJsonString().Should().Be(ctx.Expectation?.ToJsonDocument().RootElement.ToJsonString())) + .WhenTypeIs()); + } + +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/ServerlessWorkflow.Sdk.UnitTests.csproj b/tests/ServerlessWorkflow.Sdk.UnitTests/ServerlessWorkflow.Sdk.UnitTests.csproj index cb47d09..d038655 100644 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/ServerlessWorkflow.Sdk.UnitTests.csproj +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/ServerlessWorkflow.Sdk.UnitTests.csproj @@ -1,35 +1,36 @@ - + - net8.0;net9.0 + net10.0 enable enable - false - true - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + - + + - + \ No newline at end of file diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiCallDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiCallDefinitionFactory.cs new file mode 100644 index 0000000..41f1a35 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiCallDefinitionFactory.cs @@ -0,0 +1,28 @@ +// 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.UnitTests.Services; + +internal static class AsyncApiCallDefinitionFactory +{ + internal static AsyncApiCallDefinition Create() => new() + { + Document = ExternalResourceDefinitionFactory.Create(), + Operation = "onUserSignedUp", + Server = "production", + Protocol = "amqp", + Message = AsyncApiMessageDefinitionFactory.Create(), + Subscription = AsyncApiSubscriptionDefinitionFactory.Create(), + Authentication = AuthenticationPolicyDefinitionFactory.CreateBearer() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiMessageDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiMessageDefinitionFactory.cs new file mode 100644 index 0000000..2c985d8 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiMessageDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class AsyncApiMessageDefinitionFactory +{ + internal static AsyncApiMessageDefinition Create() => new() + { + Payload = new JsonObject { ["userId"] = "123", ["email"] = "test@example.com" }, + Headers = new JsonObject { ["correlationId"] = "abc-123" } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiSubscriptionDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiSubscriptionDefinitionFactory.cs new file mode 100644 index 0000000..5f8c3b9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiSubscriptionDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class AsyncApiSubscriptionDefinitionFactory +{ + internal static AsyncApiSubscriptionDefinition Create() => new() + { + Filter = ".data.userId != null", + Consume = AsyncApiSubscriptionLifetimeDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiSubscriptionLifetimeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiSubscriptionLifetimeDefinitionFactory.cs new file mode 100644 index 0000000..9d2f6c0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AsyncApiSubscriptionLifetimeDefinitionFactory.cs @@ -0,0 +1,25 @@ +// 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.UnitTests.Services; + +internal static class AsyncApiSubscriptionLifetimeDefinitionFactory +{ + internal static AsyncApiSubscriptionLifetimeDefinition Create() => new() + { + Amount = 10, + While = ".data.active == true", + Until = ".data.done == true", + For = DurationFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AuthenticationPolicyDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AuthenticationPolicyDefinitionFactory.cs new file mode 100644 index 0000000..2f521cf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/AuthenticationPolicyDefinitionFactory.cs @@ -0,0 +1,52 @@ +// 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.UnitTests.Services; + +internal static class AuthenticationPolicyDefinitionFactory +{ + internal static AuthenticationPolicyDefinition CreateBasic() => new() + { + Basic = BasicAuthenticationSchemeDefinitionFactory.Create() + }; + + internal static AuthenticationPolicyDefinition CreateBearer() => new() + { + Bearer = BearerAuthenticationSchemeDefinitionFactory.Create() + }; + + internal static AuthenticationPolicyDefinition CreateCertificate() => new() + { + Certificate = new CertificateAuthenticationSchemeDefinition() + }; + + internal static AuthenticationPolicyDefinition CreateDigest() => new() + { + Digest = DigestAuthenticationSchemeDefinitionFactory.Create() + }; + + internal static AuthenticationPolicyDefinition CreateOAuth2() => new() + { + OAuth2 = OAuth2AuthenticationSchemeDefinitionFactory.Create() + }; + + internal static AuthenticationPolicyDefinition CreateOidc() => new() + { + Oidc = OpenIDConnectSchemeDefinitionFactory.Create() + }; + + internal static AuthenticationPolicyDefinition CreateWithUse() => new() + { + Use = "my-auth-policy" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BackoffStrategyDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BackoffStrategyDefinitionFactory.cs new file mode 100644 index 0000000..ddd0347 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BackoffStrategyDefinitionFactory.cs @@ -0,0 +1,32 @@ +// 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.UnitTests.Services; + +internal static class BackoffStrategyDefinitionFactory +{ + internal static BackoffStrategyDefinition CreateConstant() => new() + { + Constant = ConstantBackoffDefinitionFactory.Create() + }; + + internal static BackoffStrategyDefinition CreateExponential() => new() + { + Exponential = ExponentialBackoffDefinitionFactory.Create() + }; + + internal static BackoffStrategyDefinition CreateLinear() => new() + { + Linear = LinearBackoffDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BasicAuthenticationSchemeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BasicAuthenticationSchemeDefinitionFactory.cs new file mode 100644 index 0000000..06eb0a2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BasicAuthenticationSchemeDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class BasicAuthenticationSchemeDefinitionFactory +{ + internal static BasicAuthenticationSchemeDefinition Create() => new() + { + Username = "admin", + Password = "p@ssw0rd" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BearerAuthenticationSchemeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BearerAuthenticationSchemeDefinitionFactory.cs new file mode 100644 index 0000000..b0484af --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BearerAuthenticationSchemeDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class BearerAuthenticationSchemeDefinitionFactory +{ + internal static BearerAuthenticationSchemeDefinition Create() => new() + { + Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BranchingDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BranchingDefinitionFactory.cs new file mode 100644 index 0000000..d92d300 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/BranchingDefinitionFactory.cs @@ -0,0 +1,30 @@ +// 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.UnitTests.Services; + +internal static class BranchingDefinitionFactory +{ + internal static BranchingDefinition Create() + { + var branches = new Map + { + new MapEntry("branch1", TaskDefinitionFactory.CreateSetTask()) + }; + return new() + { + Branches = branches, + Compete = true + }; + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CallTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CallTaskDefinitionFactory.cs new file mode 100644 index 0000000..4b43dd7 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CallTaskDefinitionFactory.cs @@ -0,0 +1,27 @@ +// 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.UnitTests.Services; + +internal static class CallTaskDefinitionFactory +{ + internal static CallTaskDefinition Create() => new() + { + Call = "http", + With = new JsonObject + { + ["method"] = "GET", + ["uri"] = "https://api.example.com/data" + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CatalogDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CatalogDefinitionFactory.cs new file mode 100644 index 0000000..3a0017a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CatalogDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class CatalogDefinitionFactory +{ + internal static CatalogDefinition Create() => new() + { + Endpoint = EndpointDefinitionFactory.CreateSimple() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CloudEventFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CloudEventFactory.cs new file mode 100644 index 0000000..bb8689d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CloudEventFactory.cs @@ -0,0 +1,32 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; + +namespace ServerlessWorkflow.Sdk.UnitTests.Services; + +internal static class CloudEventFactory +{ + internal static CloudEvent Create() => new() + { + Id = "ce-123", + SpecVersion = CloudEvent.DefaultVersion, + Time = new DateTimeOffset(2026, 4, 3, 12, 0, 0, TimeSpan.Zero), + Source = new Uri("https://example.com/source"), + Type = "com.example.test", + Subject = "test-subject", + DataContentType = "application/json", + DataSchema = new Uri("https://example.com/schema"), + Data = new JsonObject { ["key"] = "value" } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ComponentDefinitionCollectionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ComponentDefinitionCollectionFactory.cs new file mode 100644 index 0000000..5ffa370 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ComponentDefinitionCollectionFactory.cs @@ -0,0 +1,30 @@ +// 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.UnitTests.Services; + +internal static class ComponentDefinitionCollectionFactory +{ + internal static ComponentDefinitionCollection Create() => new() + { + Authentications = new EquatableDictionary() + { + ["basicAuth"] = AuthenticationPolicyDefinitionFactory.CreateBasic() + }, + Retries = new EquatableDictionary() + { + ["defaultRetry"] = RetryPolicyDefinitionFactory.Create() + }, + Secrets = ["my-secret-1", "my-secret-2"] + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ConstantBackoffDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ConstantBackoffDefinitionFactory.cs new file mode 100644 index 0000000..40914be --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ConstantBackoffDefinitionFactory.cs @@ -0,0 +1,19 @@ +// 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.UnitTests.Services; + +internal static class ConstantBackoffDefinitionFactory +{ + internal static ConstantBackoffDefinition Create() => new(); +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ContainerLifetimeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ContainerLifetimeDefinitionFactory.cs new file mode 100644 index 0000000..ef67aac --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ContainerLifetimeDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class ContainerLifetimeDefinitionFactory +{ + internal static ContainerLifetimeDefinition Create() => new() + { + Cleanup = ContainerCleanupPolicy.Eventually, + Duration = Duration.FromMinutes(30) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ContainerProcessDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ContainerProcessDefinitionFactory.cs new file mode 100644 index 0000000..3453103 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ContainerProcessDefinitionFactory.cs @@ -0,0 +1,32 @@ +// 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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.UnitTests.Services; + +internal static class ContainerProcessDefinitionFactory +{ + internal static ContainerProcessDefinition Create() => new() + { + Image = "my-app:latest", + Name = "my-container", + Command = "dotnet run", + Environment = new EquatableDictionary( + new Dictionary + { + ["ASPNETCORE_ENVIRONMENT"] = "Production" + }), + Lifetime = ContainerLifetimeDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CorrelationKeyDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CorrelationKeyDefinitionFactory.cs new file mode 100644 index 0000000..04ebe80 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/CorrelationKeyDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class CorrelationKeyDefinitionFactory +{ + internal static CorrelationKeyDefinition Create() => new() + { + From = ".correlationId", + Expect = "${ .correlationId }" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DateTimeDescriptorFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DateTimeDescriptorFactory.cs new file mode 100644 index 0000000..187cc60 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DateTimeDescriptorFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class DateTimeDescriptorFactory +{ + internal static DateTimeDescriptor Create() => new() + { + Iso8601 = "2024-06-01T12:00:00Z", + Epoch = EpochFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DigestAuthenticationSchemeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DigestAuthenticationSchemeDefinitionFactory.cs new file mode 100644 index 0000000..cf53f1a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DigestAuthenticationSchemeDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class DigestAuthenticationSchemeDefinitionFactory +{ + internal static DigestAuthenticationSchemeDefinition Create() => new() + { + Username = "admin", + Password = "p@ssw0rd" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DoTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DoTaskDefinitionFactory.cs new file mode 100644 index 0000000..746d7c7 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DoTaskDefinitionFactory.cs @@ -0,0 +1,27 @@ +// 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.UnitTests.Services; + +internal static class DoTaskDefinitionFactory +{ + internal static DoTaskDefinition Create() + { + var tasks = new Map(); + tasks.Add(new MapEntry("setValues", TaskDefinitionFactory.CreateSetTask())); + return new() + { + Do = tasks + }; + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DurationFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DurationFactory.cs new file mode 100644 index 0000000..bf5c0f6 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/DurationFactory.cs @@ -0,0 +1,19 @@ +// 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.UnitTests.Services; + +internal static class DurationFactory +{ + internal static Duration Create() => Duration.FromSeconds(30); +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EmitTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EmitTaskDefinitionFactory.cs new file mode 100644 index 0000000..35a3cf2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EmitTaskDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class EmitTaskDefinitionFactory +{ + internal static EmitTaskDefinition Create() => new() + { + Emit = EventEmissionDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EndpointDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EndpointDefinitionFactory.cs new file mode 100644 index 0000000..c8014ff --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EndpointDefinitionFactory.cs @@ -0,0 +1,28 @@ +// 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.UnitTests.Services; + +internal static class EndpointDefinitionFactory +{ + internal static EndpointDefinition Create() => new() + { + Uri = new Uri("https://api.example.com/v1"), + Authentication = AuthenticationPolicyDefinitionFactory.CreateBasic() + }; + + internal static EndpointDefinition CreateSimple() => new() + { + Uri = new Uri("https://api.example.com/v1") + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EpochFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EpochFactory.cs new file mode 100644 index 0000000..ce656e1 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EpochFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class EpochFactory +{ + internal static Epoch Create() => new() + { + Milliseconds = 69 + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorCatcherDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorCatcherDefinitionFactory.cs new file mode 100644 index 0000000..af68ac5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorCatcherDefinitionFactory.cs @@ -0,0 +1,26 @@ +// 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.UnitTests.Services; + +internal static class ErrorCatcherDefinitionFactory +{ + internal static ErrorCatcherDefinition Create() => new() + { + Errors = ErrorFilterDefinitionFactory.Create(), + As = "error", + When = "${ .error.status == 503 }", + ExceptWhen = "${ .error.status == 404 }", + Retry = RetryPolicyDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorDefinitionFactory.cs new file mode 100644 index 0000000..20b36c9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorDefinitionFactory.cs @@ -0,0 +1,26 @@ +// 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.UnitTests.Services; + +internal static class ErrorDefinitionFactory +{ + internal static ErrorDefinition Create() => new() + { + Type = "https://example.com/errors/not-found", + Title = "Not Found", + Status = "404", + Detail = "The requested resource was not found", + Instance = "https://example.com/errors/not-found/12345" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorFilterDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorFilterDefinitionFactory.cs new file mode 100644 index 0000000..4524380 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ErrorFilterDefinitionFactory.cs @@ -0,0 +1,26 @@ +// 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.UnitTests.Services; + +internal static class ErrorFilterDefinitionFactory +{ + internal static ErrorFilterDefinition Create() => new() + { + With = new JsonObject + { + ["status"] = 503, + ["type"] = "https://example.com/errors/service-unavailable" + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventConsumptionStrategyDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventConsumptionStrategyDefinitionFactory.cs new file mode 100644 index 0000000..178cbf3 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventConsumptionStrategyDefinitionFactory.cs @@ -0,0 +1,32 @@ +// 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.UnitTests.Services; + +internal static class EventConsumptionStrategyDefinitionFactory +{ + internal static EventConsumptionStrategyDefinition CreateOne() => new() + { + One = EventFilterDefinitionFactory.CreateSimple() + }; + + internal static EventConsumptionStrategyDefinition CreateAll() => new() + { + All = [EventFilterDefinitionFactory.CreateSimple()] + }; + + internal static EventConsumptionStrategyDefinition CreateAny() => new() + { + Any = [EventFilterDefinitionFactory.CreateSimple()] + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventDefinitionFactory.cs new file mode 100644 index 0000000..07c98a2 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventDefinitionFactory.cs @@ -0,0 +1,26 @@ +// 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.UnitTests.Services; + +internal static class EventDefinitionFactory +{ + internal static EventDefinition Create() => new() + { + With = new JsonObject + { + ["type"] = "com.example.event", + ["source"] = "https://example.com" + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventEmissionDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventEmissionDefinitionFactory.cs new file mode 100644 index 0000000..0f3524f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventEmissionDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class EventEmissionDefinitionFactory +{ + internal static EventEmissionDefinition Create() => new() + { + Event = EventDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventFilterDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventFilterDefinitionFactory.cs new file mode 100644 index 0000000..34655ad --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/EventFilterDefinitionFactory.cs @@ -0,0 +1,38 @@ +// 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.UnitTests.Services; + +internal static class EventFilterDefinitionFactory +{ + internal static EventFilterDefinition Create() => new() + { + With = new JsonObject + { + ["type"] = "com.example.event" + }, + Correlate = new EquatableDictionary( + new Dictionary + { + ["orderId"] = CorrelationKeyDefinitionFactory.Create() + }) + }; + + internal static EventFilterDefinition CreateSimple() => new() + { + With = new JsonObject + { + ["type"] = "com.example.event" + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExponentialBackoffDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExponentialBackoffDefinitionFactory.cs new file mode 100644 index 0000000..5e37379 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExponentialBackoffDefinitionFactory.cs @@ -0,0 +1,19 @@ +// 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.UnitTests.Services; + +internal static class ExponentialBackoffDefinitionFactory +{ + internal static ExponentialBackoffDefinition Create() => new(); +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExtensionDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExtensionDefinitionFactory.cs new file mode 100644 index 0000000..10cd771 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExtensionDefinitionFactory.cs @@ -0,0 +1,31 @@ +// 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.UnitTests.Services; + +internal static class ExtensionDefinitionFactory +{ + internal static ExtensionDefinition Create() + { + var beforeTasks = new Map + { + new MapEntry("log", TaskDefinitionFactory.CreateSetTask()) + }; + return new() + { + Extend = "call", + When = "${ .call == \"http\" }", + Before = beforeTasks + }; + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExtensionTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExtensionTaskDefinitionFactory.cs new file mode 100644 index 0000000..bf08fe9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExtensionTaskDefinitionFactory.cs @@ -0,0 +1,25 @@ +// 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.UnitTests.Services; + +internal static class ExtensionTaskDefinitionFactory +{ + internal static ExtensionTaskDefinition Create() => new() + { + ExtensionData = new Dictionary() + { + ["customProperty"] = JsonElement.Parse("\"customValue\"") + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExternalResourceDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExternalResourceDefinitionFactory.cs new file mode 100644 index 0000000..5e0d313 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ExternalResourceDefinitionFactory.cs @@ -0,0 +1,29 @@ +// 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.UnitTests.Services; + +internal static class ExternalResourceDefinitionFactory +{ + internal static ExternalResourceDefinition Create() => new() + { + Name = "test-resource", + Endpoint = new Uri("https://api.example.com/resource") + }; + + internal static ExternalResourceDefinition CreateWithEndpointDefinition() => new() + { + Name = "test-resource", + Endpoint = EndpointDefinitionFactory.CreateSimple() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForLoopDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForLoopDefinitionFactory.cs new file mode 100644 index 0000000..d91286c --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForLoopDefinitionFactory.cs @@ -0,0 +1,24 @@ +// 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.UnitTests.Services; + +internal static class ForLoopDefinitionFactory +{ + internal static ForLoopDefinition Create() => new() + { + Each = "item", + In = "${ .items }", + At = "index" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForTaskDefinitionFactory.cs new file mode 100644 index 0000000..b643dfa --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForTaskDefinitionFactory.cs @@ -0,0 +1,29 @@ +// 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.UnitTests.Services; + +internal static class ForTaskDefinitionFactory +{ + internal static ForTaskDefinition Create() + { + var tasks = new Map(); + tasks.Add(new MapEntry("processItem", TaskDefinitionFactory.CreateSetTask())); + return new() + { + For = ForLoopDefinitionFactory.Create(), + While = "${ .hasMore }", + Do = tasks + }; + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForkTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForkTaskDefinitionFactory.cs new file mode 100644 index 0000000..7dbc134 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ForkTaskDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class ForkTaskDefinitionFactory +{ + internal static ForkTaskDefinition Create() => new() + { + Fork = BranchingDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/GrpcCallDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/GrpcCallDefinitionFactory.cs new file mode 100644 index 0000000..47dc120 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/GrpcCallDefinitionFactory.cs @@ -0,0 +1,27 @@ +// 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 ServerlessWorkflow.Sdk.Models.Calls; + +namespace ServerlessWorkflow.Sdk.UnitTests.Services; + +internal static class GrpcCallDefinitionFactory +{ + internal static GrpcCallDefinition Create() => new() + { + Proto = ExternalResourceDefinitionFactory.Create(), + Service = GrpcServiceDefinitionFactory.Create(), + Method = "SayHello", + Arguments = new JsonObject { ["name"] = "world" } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/GrpcServiceDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/GrpcServiceDefinitionFactory.cs new file mode 100644 index 0000000..3992696 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/GrpcServiceDefinitionFactory.cs @@ -0,0 +1,24 @@ +// 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.UnitTests.Services; + +internal static class GrpcServiceDefinitionFactory +{ + internal static GrpcServiceDefinition Create() => new() + { + Name = "GreeterService", + Host = "grpc.example.com", + Port = 443 + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpCallDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpCallDefinitionFactory.cs new file mode 100644 index 0000000..d6038e4 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpCallDefinitionFactory.cs @@ -0,0 +1,31 @@ +// 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.UnitTests.Services; + +internal static class HttpCallDefinitionFactory +{ + internal static HttpCallDefinition Create() => new() + { + Method = "POST", + Endpoint = EndpointDefinitionFactory.Create(), + Headers = new EquatableDictionary + { + ["Content-Type"] = "application/json", + ["Accept"] = "application/json" + }, + Body = new JsonObject { ["name"] = "test", ["value"] = 42 }, + Output = HttpOutputFormat.Response, + Redirect = true + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpRequestFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpRequestFactory.cs new file mode 100644 index 0000000..cd569ac --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpRequestFactory.cs @@ -0,0 +1,29 @@ +// 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.UnitTests.Services; + +internal static class HttpRequestFactory +{ + internal static HttpRequest Create() => new() + { + Method = "POST", + Uri = new("https://api.example.com/resources"), + Headers = new EquatableDictionary + { + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer token123" + }, + Body = new JsonObject { ["name"] = "test" } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpResponseFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpResponseFactory.cs new file mode 100644 index 0000000..af8af28 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/HttpResponseFactory.cs @@ -0,0 +1,28 @@ +// 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.UnitTests.Services; + +internal static class HttpResponseFactory +{ + internal static HttpResponse Create() => new() + { + Request = HttpRequestFactory.Create(), + StatusCode = 200, + Headers = new EquatableDictionary + { + ["Content-Type"] = "application/json" + }, + Content = new JsonObject { ["id"] = 1, ["name"] = "test" } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/InputDataModelDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/InputDataModelDefinitionFactory.cs new file mode 100644 index 0000000..2fe1612 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/InputDataModelDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class InputDataModelDefinitionFactory +{ + internal static InputDataModelDefinition Create() => new() + { + Schema = SchemaDefinitionFactory.Create(), + From = ".input" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/JitterDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/JitterDefinitionFactory.cs new file mode 100644 index 0000000..0532296 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/JitterDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class JitterDefinitionFactory +{ + internal static JitterDefinition Create() => new() + { + From = Duration.FromSeconds(1), + To = Duration.FromSeconds(10) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/LinearBackoffDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/LinearBackoffDefinitionFactory.cs new file mode 100644 index 0000000..5666345 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/LinearBackoffDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class LinearBackoffDefinitionFactory +{ + internal static LinearBackoffDefinition Create() => new() + { + Increment = Duration.FromSeconds(5) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ListenTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ListenTaskDefinitionFactory.cs new file mode 100644 index 0000000..b36e783 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ListenTaskDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class ListenTaskDefinitionFactory +{ + internal static ListenTaskDefinition Create() => new() + { + Listen = ListenerDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ListenerDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ListenerDefinitionFactory.cs new file mode 100644 index 0000000..6c09c40 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ListenerDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class ListenerDefinitionFactory +{ + internal static ListenerDefinition Create() => new() + { + To = EventConsumptionStrategyDefinitionFactory.CreateOne(), + Read = EventReadMode.Data + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationClientDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationClientDefinitionFactory.cs new file mode 100644 index 0000000..71e1bc0 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationClientDefinitionFactory.cs @@ -0,0 +1,24 @@ +// 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.UnitTests.Services; + +internal static class OAuth2AuthenticationClientDefinitionFactory +{ + internal static OAuth2AuthenticationClientDefinition Create() => new() + { + Id = "my-client-id", + Secret = "my-client-secret", + Authentication = "client_secret_post" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationEndpointsDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationEndpointsDefinitionFactory.cs new file mode 100644 index 0000000..0c0a39e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationEndpointsDefinitionFactory.cs @@ -0,0 +1,24 @@ +// 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.UnitTests.Services; + +internal static class OAuth2AuthenticationEndpointsDefinitionFactory +{ + internal static OAuth2AuthenticationEndpointsDefinition Create() => new() + { + Token = new Uri("/oauth2/token", UriKind.RelativeOrAbsolute), + Revocation = new Uri("/oauth2/revoke", UriKind.RelativeOrAbsolute), + Introspection = new Uri("/oauth2/introspect", UriKind.RelativeOrAbsolute) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationRequestDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationRequestDefinitionFactory.cs new file mode 100644 index 0000000..81730e4 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationRequestDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class OAuth2AuthenticationRequestDefinitionFactory +{ + internal static OAuth2AuthenticationRequestDefinition Create() => new() + { + Encoding = OAuth2RequestEncoding.FormUrl + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationSchemeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationSchemeDefinitionFactory.cs new file mode 100644 index 0000000..30e6b9f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2AuthenticationSchemeDefinitionFactory.cs @@ -0,0 +1,29 @@ +// 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.UnitTests.Services; + +internal static class OAuth2AuthenticationSchemeDefinitionFactory +{ + internal static OAuth2AuthenticationSchemeDefinition Create() => new() + { + Authority = new Uri("https://auth.example.com"), + Grant = OAuth2GrantType.ClientCredentials, + Client = OAuth2AuthenticationClientDefinitionFactory.Create(), + Endpoints = OAuth2AuthenticationEndpointsDefinitionFactory.Create(), + Request = OAuth2AuthenticationRequestDefinitionFactory.Create(), + Scopes = ["read", "write"], + Audiences = ["api"], + Issuers = ["https://auth.example.com"] + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2TokenDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2TokenDefinitionFactory.cs new file mode 100644 index 0000000..d3ae745 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2TokenDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class OAuth2TokenDefinitionFactory +{ + internal static OAuth2TokenDefinition Create() => new() + { + Token = "eyJhbGciOiJIUzI1NiJ9", + Type = "urn:ietf:params:oauth:token-type:access_token" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2TokenFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2TokenFactory.cs new file mode 100644 index 0000000..7880f18 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OAuth2TokenFactory.cs @@ -0,0 +1,30 @@ +// 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 ServerlessWorkflow.Sdk.Runtime.Models; + +namespace ServerlessWorkflow.Sdk.UnitTests.Services; + +internal static class OAuth2TokenFactory +{ + internal static OAuth2Token Create() => new() + { + CreatedAt = new DateTime(2026, 4, 3, 12, 0, 0, DateTimeKind.Utc), + TokenType = "Bearer", + TokenId = "token-123", + AccessToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.test", + RefreshToken = "refresh-token-456", + Ttl = 3600, + ExpiresAt = new DateTime(2026, 4, 3, 13, 0, 0, DateTimeKind.Utc) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OpenApiCallDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OpenApiCallDefinitionFactory.cs new file mode 100644 index 0000000..69ab23c --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OpenApiCallDefinitionFactory.cs @@ -0,0 +1,30 @@ +// 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.UnitTests.Services; + +internal static class OpenApiCallDefinitionFactory +{ + internal static OpenApiCallDefinition Create() => new() + { + Document = ExternalResourceDefinitionFactory.Create(), + OperationId = "getPetById", + Parameters = new() + { + ["id"] = 123 + }, + Authentication = AuthenticationPolicyDefinitionFactory.CreateBasic(), + Output = HttpOutputFormat.Content, + Redirect = false + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OpenIDConnectSchemeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OpenIDConnectSchemeDefinitionFactory.cs new file mode 100644 index 0000000..e7aa93f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OpenIDConnectSchemeDefinitionFactory.cs @@ -0,0 +1,25 @@ +// 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.UnitTests.Services; + +internal static class OpenIDConnectSchemeDefinitionFactory +{ + internal static OpenIDConnectSchemeDefinition Create() => new() + { + Authority = new Uri("https://auth.example.com"), + Grant = OAuth2GrantType.AuthorizationCode, + Client = OAuth2AuthenticationClientDefinitionFactory.Create(), + Scopes = ["openid", "profile"] + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OutputDataModelDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OutputDataModelDefinitionFactory.cs new file mode 100644 index 0000000..47ca921 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/OutputDataModelDefinitionFactory.cs @@ -0,0 +1,32 @@ +// 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.UnitTests.Services; + +internal static class OutputDataModelDefinitionFactory +{ + internal static OutputDataModelDefinition Create() => new() + { + Schema = SchemaDefinitionFactory.Create(), + As = new JsonObject + { + ["result"] = ".output" + } + }; + + internal static OutputDataModelDefinition CreateWithExpression() => new() + { + Schema = SchemaDefinitionFactory.Create(), + As = ".output" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ProcessTypeDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ProcessTypeDefinitionFactory.cs new file mode 100644 index 0000000..16a8e2a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ProcessTypeDefinitionFactory.cs @@ -0,0 +1,40 @@ +// 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.UnitTests.Services; + +internal static class ProcessTypeDefinitionFactory +{ + internal static ProcessTypeDefinition CreateContainer() => new() + { + Container = ContainerProcessDefinitionFactory.Create(), + Await = true, + Return = ProcessReturnType.Stdout + }; + + internal static ProcessTypeDefinition CreateShell() => new() + { + Shell = ShellProcessDefinitionFactory.Create(), + Await = true + }; + + internal static ProcessTypeDefinition CreateScript() => new() + { + Script = ScriptProcessDefinitionFactory.Create() + }; + + internal static ProcessTypeDefinition CreateWorkflow() => new() + { + Workflow = WorkflowProcessDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RaiseErrorDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RaiseErrorDefinitionFactory.cs new file mode 100644 index 0000000..c1d27e4 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RaiseErrorDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class RaiseErrorDefinitionFactory +{ + internal static RaiseErrorDefinition Create() => new() + { + Error = ErrorDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RaiseTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RaiseTaskDefinitionFactory.cs new file mode 100644 index 0000000..8a08e98 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RaiseTaskDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class RaiseTaskDefinitionFactory +{ + internal static RaiseTaskDefinition Create() => new() + { + Raise = RaiseErrorDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryAttemptLimitDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryAttemptLimitDefinitionFactory.cs new file mode 100644 index 0000000..a54de7d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryAttemptLimitDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class RetryAttemptLimitDefinitionFactory +{ + internal static RetryAttemptLimitDefinition Create() => new() + { + Count = 3, + Duration = Duration.FromMinutes(5) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryPolicyDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryPolicyDefinitionFactory.cs new file mode 100644 index 0000000..55ee783 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryPolicyDefinitionFactory.cs @@ -0,0 +1,27 @@ +// 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.UnitTests.Services; + +internal static class RetryPolicyDefinitionFactory +{ + internal static RetryPolicyDefinition Create() => new() + { + When = "${ .error.status == 503 }", + ExceptWhen = "${ .error.status == 404 }", + Limit = RetryPolicyLimitDefinitionFactory.Create(), + Delay = Duration.FromSeconds(5), + Backoff = BackoffStrategyDefinitionFactory.CreateExponential(), + Jitter = JitterDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryPolicyLimitDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryPolicyLimitDefinitionFactory.cs new file mode 100644 index 0000000..865024b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RetryPolicyLimitDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class RetryPolicyLimitDefinitionFactory +{ + internal static RetryPolicyLimitDefinition Create() => new() + { + Attempt = RetryAttemptLimitDefinitionFactory.Create(), + Duration = Duration.FromMinutes(30) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RunTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RunTaskDefinitionFactory.cs new file mode 100644 index 0000000..d9afabd --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RunTaskDefinitionFactory.cs @@ -0,0 +1,37 @@ +// 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.UnitTests.Services; + +internal static class RunTaskDefinitionFactory +{ + internal static RunTaskDefinition CreateContainer() => new() + { + Run = ProcessTypeDefinitionFactory.CreateContainer() + }; + + internal static RunTaskDefinition CreateShell() => new() + { + Run = ProcessTypeDefinitionFactory.CreateShell() + }; + + internal static RunTaskDefinition CreateScript() => new() + { + Run = ProcessTypeDefinitionFactory.CreateScript() + }; + + internal static RunTaskDefinition CreateWorkflow() => new() + { + Run = ProcessTypeDefinitionFactory.CreateWorkflow() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeDescriptorFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeDescriptorFactory.cs new file mode 100644 index 0000000..50a8116 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeDescriptorFactory.cs @@ -0,0 +1,27 @@ +// 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.UnitTests.Services; + +internal static class RuntimeDescriptorFactory +{ + internal static RuntimeDescriptor Create() => new() + { + Name = "test", + Version = "1.0.0", + Metadata = new() + { + ["key"] = "value" + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeErrorFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeErrorFactory.cs new file mode 100644 index 0000000..2336ac5 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeErrorFactory.cs @@ -0,0 +1,26 @@ +// 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.UnitTests.Services; + +internal static class RuntimeErrorFactory +{ + internal static Error Create() => new() + { + Type = ErrorType.Runtime, + Title = ErrorTitle.Runtime, + Status = ErrorStatus.Runtime, + Detail = "An unexpected error occurred during task execution", + Instance = new Uri("/tasks/123", UriKind.RelativeOrAbsolute) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeExpressionEvaluationConfigurationFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeExpressionEvaluationConfigurationFactory.cs new file mode 100644 index 0000000..9b5682f --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/RuntimeExpressionEvaluationConfigurationFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class RuntimeExpressionEvaluationConfigurationFactory +{ + internal static RuntimeExpressionEvaluationConfiguration Create() => new() + { + Language = RuntimeExpressions.Languages.JQ, + Mode = RuntimeExpressionEvaluationMode.Strict + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SchemaDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SchemaDefinitionFactory.cs new file mode 100644 index 0000000..d73adaf --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SchemaDefinitionFactory.cs @@ -0,0 +1,36 @@ +// 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.UnitTests.Services; + +internal static class SchemaDefinitionFactory +{ + internal static SchemaDefinition Create() => new() + { + Format = SchemaFormat.Json, + Document = new JsonObject + { + ["type"] = "object", + ["properties"] = new JsonObject + { + ["name"] = new JsonObject { ["type"] = "string" } + } + } + }; + + internal static SchemaDefinition CreateWithResource() => new() + { + Format = SchemaFormat.Json, + Resource = ExternalResourceDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ScriptProcessDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ScriptProcessDefinitionFactory.cs new file mode 100644 index 0000000..9ac5989 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ScriptProcessDefinitionFactory.cs @@ -0,0 +1,25 @@ +// 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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.UnitTests.Services; + +internal static class ScriptProcessDefinitionFactory +{ + internal static ScriptProcessDefinition Create() => new() + { + Language = "javascript", + Code = "console.log('Hello, World!')" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ShellProcessDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ShellProcessDefinitionFactory.cs new file mode 100644 index 0000000..fcee7d9 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/ShellProcessDefinitionFactory.cs @@ -0,0 +1,30 @@ +// 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 ServerlessWorkflow.Sdk.Models.Processes; + +namespace ServerlessWorkflow.Sdk.UnitTests.Services; + +internal static class ShellProcessDefinitionFactory +{ + internal static ShellProcessDefinition Create() => new() + { + Command = "echo", + Arguments = ["Hello", "World"], + Environment = new EquatableDictionary( + new Dictionary + { + ["PATH"] = "/usr/bin" + }) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SubscriptionIteratorDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SubscriptionIteratorDefinitionFactory.cs new file mode 100644 index 0000000..75a5349 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SubscriptionIteratorDefinitionFactory.cs @@ -0,0 +1,24 @@ +// 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.UnitTests.Services; + +internal static class SubscriptionIteratorDefinitionFactory +{ + internal static SubscriptionIteratorDefinition Create() => new() + { + Item = "event", + At = "eventIndex", + Output = OutputDataModelDefinitionFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SwitchCaseDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SwitchCaseDefinitionFactory.cs new file mode 100644 index 0000000..dfa365b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SwitchCaseDefinitionFactory.cs @@ -0,0 +1,23 @@ +// 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.UnitTests.Services; + +internal static class SwitchCaseDefinitionFactory +{ + internal static SwitchCaseDefinition Create() => new() + { + When = "${ .orderStatus == 'approved' }", + Then = "processOrder" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SwitchTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SwitchTaskDefinitionFactory.cs new file mode 100644 index 0000000..4b8041b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/SwitchTaskDefinitionFactory.cs @@ -0,0 +1,28 @@ +// 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.UnitTests.Services; + +internal static class SwitchTaskDefinitionFactory +{ + internal static SwitchTaskDefinition Create() + { + var cases = new Map(); + cases.Add(new MapEntry("approved", SwitchCaseDefinitionFactory.Create())); + cases.Add(new MapEntry("default", new SwitchCaseDefinition { Then = "end" })); + return new() + { + Switch = cases + }; + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TaskDefinitionFactory.cs new file mode 100644 index 0000000..5cd131a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TaskDefinitionFactory.cs @@ -0,0 +1,25 @@ +// 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.UnitTests.Services; + +internal static class TaskDefinitionFactory +{ + internal static SetTaskDefinition CreateSetTask() => new() + { + Set = new() + { + ["key"] = "value" + } + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TaskDescriptorFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TaskDescriptorFactory.cs new file mode 100644 index 0000000..af6640b --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TaskDescriptorFactory.cs @@ -0,0 +1,34 @@ +// 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.UnitTests.Services; + +internal static class TaskDescriptorFactory +{ + internal static TaskDescriptor Create() => new() + { + Id = "test-task", + Name = "test", + Definition = TaskDefinitionFactory.CreateSetTask(), + Reference = JsonPointer.Parse("/test"), + Input = new JsonObject() + { + ["key"] = "value" + }, + Output = new JsonObject() + { + ["key"] = "value" + }, + StartedAt = DateTimeDescriptorFactory.Create() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TimeoutDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TimeoutDefinitionFactory.cs new file mode 100644 index 0000000..9cadd9a --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TimeoutDefinitionFactory.cs @@ -0,0 +1,27 @@ +// 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.UnitTests.Services; + +internal static class TimeoutDefinitionFactory +{ + internal static TimeoutDefinition Create() => new() + { + After = DurationFactory.Create() + }; + + internal static TimeoutDefinition CreateWithExpression() => new() + { + After = ".timeout" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TryTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TryTaskDefinitionFactory.cs new file mode 100644 index 0000000..999bb5d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/TryTaskDefinitionFactory.cs @@ -0,0 +1,28 @@ +// 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.UnitTests.Services; + +internal static class TryTaskDefinitionFactory +{ + internal static TryTaskDefinition Create() + { + var tryTasks = new Map(); + tryTasks.Add(new MapEntry("riskyTask", TaskDefinitionFactory.CreateSetTask())); + return new() + { + Try = tryTasks, + Catch = ErrorCatcherDefinitionFactory.Create() + }; + } +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WaitTaskDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WaitTaskDefinitionFactory.cs new file mode 100644 index 0000000..bef2505 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WaitTaskDefinitionFactory.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Services; + +internal static class WaitTaskDefinitionFactory +{ + internal static WaitTaskDefinition Create() => new() + { + Wait = Duration.FromSeconds(30) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionFactory.cs index f0331c4..30ad1a2 100644 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionFactory.cs +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionFactory.cs @@ -1,4 +1,4 @@ -// Copyright © 2024-Present The Serverless Workflow Specification Authors +// 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. @@ -11,172 +11,44 @@ // See the License for the specific language governing permissions and // limitations under the License. -using ServerlessWorkflow.Sdk.Builders; - namespace ServerlessWorkflow.Sdk.UnitTests.Services; internal static class WorkflowDefinitionFactory { - internal static WorkflowDefinition Create() { - return new WorkflowDefinitionBuilder() - .WithName("fake-name") - .WithVersion("0.1.0") - .WithTitle("Fake Title") - .WithSummary("Fake MD summary") - .WithTag("fakeTagName", "fakeTagValue") - .UseAuthentication("fakeBasic", authentication => authentication - .Basic() - .WithUsername("fake-user") - .WithPassword("fake-password")) - .UseAuthentication("fakeBearer", authentication => authentication - .Bearer() - .WithToken("fake-token")) - .UseAuthentication("fakeOAuth2", authentication => authentication - .OAuth2() - .WithAuthority(new("https://fake-authority.com")) - .WithGrantType(OAuth2GrantType.ClientCredentials) - .WithClient(client => client - .WithId("fake-client-id") - .WithSecret("fake-client-secret"))) - .UseFunction("fakeFunction1", function => function - .Call() - .Function("http") - .With("method", "post") - .With("uri", "https://test.com")) - .UseFunction("fakeFunction2", function => function - .Run() - .Shell() - .WithCommand(@"echo ""Hello, World!""")) - .UseExtension("fakeLoggingExtension", extension => extension - .ExtendAll() - .When("fake-expression") - .Before(tasks => tasks - .Do("fake-http-call", task => task - .Call("http") - .With("method", "post") - .With("uri", "https://fake.log.collector.com") - .With("body", new - { - message = @"${ ""Executing task '\($task.reference)'..."" }" - }))) - .After(tasks => tasks - .Do("fake-http-call", task => task - .Call("http") - .With("method", "post") - .With("uri", "https://fake.log.collector.com") - .With("body", new - { - message = @"${ ""Executed task '\($task.reference)'..."" }" - })))) - .UseSecret("fake-secret") - .Do("todo-1", task => task - .Call("http") - .If("fake-condition") - .With("method", "get") - .With("uri", "https://unit-tests.serverlessworkflow.io")) - .Do("todo-2", task => task - .Emit(e => e - .With("type", "io.serverlessworkflow.unit-tests.fake.event.type.v1"))) - .Do("todo-3", task => task - .For() - .Each("color") - .In(".colors") - .At("index") - .Do(tasks => tasks - .Do("fake-http-call", subtask => subtask - .Set("processed", ".processed + [$color]")))) - .Do("todo-4", task => task - .Listen() - .To(to => to - .Any() - .Event(e => e - .With("foo", "bar")) - .Event(e => e - .With(new Dictionary() { { "foo", "bar" }, { "bar", "baz" } })))) - .Do("todo-5", task => task - .Raise(error => error - .WithType("fake-error-type") - .WithStatus("400") - .WithTitle("fake-error-title"))) - .Do("todo-6", task => task - .Run() - .Container() - .WithImage("fake-image:latest") - .WithCommand("fake command --arg1 arg1") - .WithEnvironment("ASPNET_ENVIRONMENT", "Development")) - .Do("todo-7", task => task - .Run() - .Shell() - .WithCommand("fake command --arg1 arg1") - .WithArgument("--arg2 arg2") - .WithEnvironment("ASPNET_ENVIRONMENT", "Development")) - .Do("todo-8", task => task - .Run() - .Script() - .WithLanguage("js") - .WithCode(@"console.log(""Hello, World!"")")) - .Do("todo-9", task => task - .Run() - .Workflow() - .WithName("fake-workflow") - .WithVersion("1.0.0") - .WithInput(new { foo = "bar" })) - .Do("todo-10", task => task - .Set("foo", "bar") - .Set("bar", new { baz = "foo" })) - .Do("todo-11", task => task - .Switch() - .Case("case-1", @case => @case - .When("fake-condition") - .Then(FlowDirective.Continue)) - .Case("case-2", @case => @case - .When("another-fake-condition") - .Then(FlowDirective.Exit)) - .Case("default", @case => @case - .Then(FlowDirective.End))) - .Do("todo-12", task => task - .Try() - .Do(tasks => tasks - .Do("setFoo", subtask => subtask - .Set("foo", "bar"))) - .Catch(error => error - .Errors(filter => filter - .With("status", ". == 400")) - .As("error") - .When("fake-condition") - .ExceptWhen("another-fake-condition") - .Retry(retry => retry - .When("fake-condition") - .ExceptWhen("another-fake-condition") - .Limit(limits => limits - .Attempt() - .Count(10))) - .Do(tasks => tasks - .Do("setFoo", subtask => subtask - .Set("foo", "bar"))))) - .Do("todo-13", task => task - .Wait() - .For(Duration.FromMinutes(5))) - .Do("todo-14", task => task - .Do(tasks => tasks - .Do("todo-14-1", task => task - .Call("http") - .With("method", "get") - .With("uri", "https://unit-tests.serverlessworkflow.io")) - .Do("todo-14-2", task => task - .Emit(e => e - .With("type", "io.serverlessworkflow.unit-tests.fake.event.type.v1"))) - .Do("todo-14-3", task => task - .For() - .Each("color") - .In(".colors") - .At("index") - .Do(tasks => tasks - .Do("setProcessed", subtask => subtask - .Set("processed", ".processed + [$color]")))))) - .Build(); + var tasks = new Map + { + new MapEntry("greet", TaskDefinitionFactory.CreateSetTask()) + }; + return new() + { + Document = WorkflowDefinitionMetadataFactory.Create(), + Input = InputDataModelDefinitionFactory.Create(), + Use = ComponentDefinitionCollectionFactory.Create(), + Timeout = TimeoutDefinitionFactory.Create(), + Output = OutputDataModelDefinitionFactory.Create(), + Schedule = WorkflowScheduleDefinitionFactory.CreateWithCron(), + Evaluate = RuntimeExpressionEvaluationConfigurationFactory.Create(), + Do = tasks + }; } + internal static WorkflowDefinition CreateMinimal() + { + var tasks = new Map + { + new MapEntry("greet", TaskDefinitionFactory.CreateSetTask()) + }; + return new() + { + Document = new WorkflowDefinitionMetadata + { + Dsl = "1.0.0", + Name = "minimal-workflow", + Version = "0.1.0" + }, + Do = tasks + }; + } } diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionMetadataFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionMetadataFactory.cs new file mode 100644 index 0000000..42bcf8e --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDefinitionMetadataFactory.cs @@ -0,0 +1,33 @@ +// 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.UnitTests.Services; + +internal static class WorkflowDefinitionMetadataFactory +{ + internal static WorkflowDefinitionMetadata Create() => new() + { + Dsl = "1.0.0", + Namespace = "com.example", + Name = "my-workflow", + Version = "1.0.0", + Title = "My Workflow", + Summary = "A sample workflow definition", + Tags = new EquatableDictionary( + new Dictionary + { + ["environment"] = "production", + ["team"] = "platform" + }) + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDescriptorFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDescriptorFactory.cs new file mode 100644 index 0000000..b489aa3 --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowDescriptorFactory.cs @@ -0,0 +1,28 @@ +// 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.UnitTests.Services; + +internal static class WorkflowDescriptorFactory +{ + internal static WorkflowDescriptor Create() => new() + { + Id = "test", + Definition = WorkflowDefinitionFactory.Create(), + Input = new() + { + ["key"] = "value" + }, + StartedAt = DateTimeDescriptorFactory.Create() + }; +} \ No newline at end of file diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowProcessDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowProcessDefinitionFactory.cs new file mode 100644 index 0000000..6215ecb --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowProcessDefinitionFactory.cs @@ -0,0 +1,24 @@ +// 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.UnitTests.Services; + +internal static class WorkflowProcessDefinitionFactory +{ + internal static WorkflowProcessDefinition Create() => new() + { + Namespace = "com.example", + Name = "my-subworkflow", + Version = "1.0.0" + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowScheduleDefinitionFactory.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowScheduleDefinitionFactory.cs new file mode 100644 index 0000000..d0df02d --- /dev/null +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Services/WorkflowScheduleDefinitionFactory.cs @@ -0,0 +1,37 @@ +// 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.UnitTests.Services; + +internal static class WorkflowScheduleDefinitionFactory +{ + internal static WorkflowScheduleDefinition CreateWithCron() => new() + { + Cron = "0 0 * * *" + }; + + internal static WorkflowScheduleDefinition CreateWithEvery() => new() + { + Every = Duration.FromMinutes(5) + }; + + internal static WorkflowScheduleDefinition CreateWithAfter() => new() + { + After = Duration.FromSeconds(30) + }; + + internal static WorkflowScheduleDefinition CreateWithEvent() => new() + { + On = EventConsumptionStrategyDefinitionFactory.CreateOne() + }; +} diff --git a/tests/ServerlessWorkflow.Sdk.UnitTests/Usings.cs b/tests/ServerlessWorkflow.Sdk.UnitTests/Usings.cs index 3a0dcdd..448b1ce 100644 --- a/tests/ServerlessWorkflow.Sdk.UnitTests/Usings.cs +++ b/tests/ServerlessWorkflow.Sdk.UnitTests/Usings.cs @@ -11,6 +11,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +global using AwesomeAssertions; +global using AwesomeAssertions.Primitives; +global using Json.More; +global using Json.Patch; +global using Json.Pointer; +global using Microsoft.Extensions.Logging; +global using Moq; +global using ServerlessWorkflow.Sdk.Builders; +global using ServerlessWorkflow.Sdk.IO; global using ServerlessWorkflow.Sdk.Models; +global using ServerlessWorkflow.Sdk.Models.Authentication; +global using ServerlessWorkflow.Sdk.Models.Calls; +global using ServerlessWorkflow.Sdk.Models.Processes; +global using ServerlessWorkflow.Sdk.Models.Tasks; +global using ServerlessWorkflow.Sdk.Runtime; +global using ServerlessWorkflow.Sdk.Runtime.Models; +global using ServerlessWorkflow.Sdk.Runtime.Services; +global using ServerlessWorkflow.Sdk.Runtime.Services.Executors; +global using ServerlessWorkflow.Sdk.Serialization.Json; global using ServerlessWorkflow.Sdk.UnitTests.Services; -global using FluentAssertions; +global using System.Net; +global using System.Reactive.Subjects; +global using System.Text.Json; +global using System.Text.Json.Nodes; +global using Yaml2JsonNode;