diff --git a/CHANGELOG.md b/CHANGELOG.md index a415bbd87..f3ec402b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Introduce default version setting to DurableTaskClient and expose to orchestrator ([#393](https://github.com/microsoft/durabletask-dotnet/pull/393)) - Add support for local credential types in DTS libraries ([#396](https://github.com/microsoft/durabletask-dotnet/pull/396)) +- Add utility for easier version comparison in orchestration context ([#394](https://github.com/microsoft/durabletask-dotnet/pull/394)) ## v1.8.1 diff --git a/src/Abstractions/TaskOrchestrationContext.cs b/src/Abstractions/TaskOrchestrationContext.cs index 4eb087bae..ecba913be 100644 --- a/src/Abstractions/TaskOrchestrationContext.cs +++ b/src/Abstractions/TaskOrchestrationContext.cs @@ -403,6 +403,52 @@ public virtual ILogger CreateReplaySafeLogger(Type type) public virtual ILogger CreateReplaySafeLogger() => new ReplaySafeLogger(this, this.LoggerFactory.CreateLogger()); + /// + /// Checks if the current orchestration version is greater than the specified version. + /// + /// + /// + /// If both versions are empty, this is considered false as neither can be greater. + /// + /// + /// An empty context version is less than a defined version in the parameter. + /// + /// + /// An empty parameter version is less than a defined version in the context. + /// + /// + /// The version to check against. + /// True if the orchestration's version is greater than the provided version, false otherwise. + public virtual int CompareVersionTo(string version) + { + // Both versions are empty, treat as equal. + if (string.IsNullOrWhiteSpace(this.Version) && string.IsNullOrWhiteSpace(version)) + { + return 0; + } + + // An empty version in the context is always less than a defined version in the parameter. + if (string.IsNullOrWhiteSpace(this.Version)) + { + return -1; + } + + // An empty version in the parameter is always less than a defined version in the context. + if (string.IsNullOrWhiteSpace(version)) + { + return 1; + } + + // If both versions use the .NET Version class, return that comparison. + if (System.Version.TryParse(this.Version, out Version contextVersion) && System.Version.TryParse(version, out Version otherVersion)) + { + return contextVersion.CompareTo(otherVersion); + } + + // If we have gotten to here, we don't know the syntax of the versions we are comparing, use a string comparison as a final check. + return string.Compare(this.Version, version, StringComparison.OrdinalIgnoreCase); + } + class ReplaySafeLogger : ILogger { readonly TaskOrchestrationContext context; diff --git a/test/Abstractions.Tests/TaskOrchestrationContextVersionTests.cs b/test/Abstractions.Tests/TaskOrchestrationContextVersionTests.cs new file mode 100644 index 000000000..00d59a6de --- /dev/null +++ b/test/Abstractions.Tests/TaskOrchestrationContextVersionTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; + +namespace Microsoft.DurableTask.Abstractions.Tests; +public class TaskOrchestrationContextVersionTests +{ + [Theory] + [InlineData("1.1", "1.0", 1)] + [InlineData("1.0", "1.1", -1)] + [InlineData("1.0", "1.0", 0)] + [InlineData("", "1.0", -1)] + [InlineData("1.0", "", 1)] + [InlineData("", "", 0)] + [InlineData("1.0.1", "1.0.0", 1)] + [InlineData("1.0.0", "1.0.1", -1)] + [InlineData("alpha", "beta", -1)] + [InlineData("beta", "alpha", 1)] + [InlineData("alpha", "alpha", 0)] + public void OrchestrationContext_Version_ComparisonTests(string orchestrationVersion, string otherVersion, int comparisonResult) + { + TaskOrchestrationContext orchestrationContext = new TestTaskOrchestrationContext(orchestrationVersion); + + if (comparisonResult > 0) + { + orchestrationContext.CompareVersionTo(otherVersion).Should().BeGreaterThan(0); + } + else if (comparisonResult < 0) + { + orchestrationContext.CompareVersionTo(otherVersion).Should().BeLessThan(0); + } + else + { + orchestrationContext.CompareVersionTo(otherVersion).Should().Be(0); + } + } + + class TestTaskOrchestrationContext : TaskOrchestrationContext + { + internal readonly string version = string.Empty; + + public TestTaskOrchestrationContext(string version) + { + this.version = version; + } + public override TaskName Name => throw new NotImplementedException(); + + public override string InstanceId => throw new NotImplementedException(); + + public override ParentOrchestrationInstance? Parent => throw new NotImplementedException(); + + public override DateTime CurrentUtcDateTime => throw new NotImplementedException(); + + public override bool IsReplaying => throw new NotImplementedException(); + + public override string Version => this.version; + + protected override ILoggerFactory LoggerFactory => throw new NotImplementedException(); + + public override Task CallActivityAsync(TaskName name, object? input = null, TaskOptions? options = null) + { + throw new NotImplementedException(); + } + + public override Task CallSubOrchestratorAsync(TaskName orchestratorName, object? input = null, TaskOptions? options = null) + { + throw new NotImplementedException(); + } + + public override void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true) + { + throw new NotImplementedException(); + } + + public override Task CreateTimer(DateTime fireAt, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override T? GetInput() where T : default + { + throw new NotImplementedException(); + } + + public override Guid NewGuid() + { + throw new NotImplementedException(); + } + + public override void SendEvent(string instanceId, string eventName, object payload) + { + throw new NotImplementedException(); + } + + public override void SetCustomStatus(object? customStatus) + { + throw new NotImplementedException(); + } + + public override Task WaitForExternalEvent(string eventName, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + } +}