diff --git a/DotnetSdkContrib.sln b/DotnetSdkContrib.sln index 91f68f30..ea55d3c5 100644 --- a/DotnetSdkContrib.sln +++ b/DotnetSdkContrib.sln @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Provide EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagsmith.Test", "test\OpenFeature.Contrib.Providers.Flagsmith.Test\OpenFeature.Contrib.Providers.Flagsmith.Test.csproj", "{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Harness", "src\OpenFeature.Contrib.Providers.Harness\OpenFeature.Contrib.Providers.Harness.csproj", "{C16E48D2-6116-47C6-8F21-09195DCFE03C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +63,10 @@ Global {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Release|Any CPU.Build.0 = Release|Any CPU + {C16E48D2-6116-47C6-8F21-09195DCFE03C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C16E48D2-6116-47C6-8F21-09195DCFE03C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C16E48D2-6116-47C6-8F21-09195DCFE03C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C16E48D2-6116-47C6-8F21-09195DCFE03C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -74,5 +80,6 @@ Global {4041B63F-9CF6-4886-8FC7-BD1A7E45F859} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE} {47008BEE-7888-4B9B-8884-712A922C3F9B} = {0E563821-BD08-4B7F-BF9D-395CAD80F026} {C3BA23C2-BEC3-4683-A64A-C914C3D8037E} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE} + {C16E48D2-6116-47C6-8F21-09195DCFE03C} = {0E563821-BD08-4B7F-BF9D-395CAD80F026} EndGlobalSection EndGlobal diff --git a/src/OpenFeature.Contrib.Providers.Harness/HarnessAdapter.cs b/src/OpenFeature.Contrib.Providers.Harness/HarnessAdapter.cs new file mode 100644 index 00000000..ff6ae0c7 --- /dev/null +++ b/src/OpenFeature.Contrib.Providers.Harness/HarnessAdapter.cs @@ -0,0 +1,63 @@ +using io.harness.cfsdk.client.dto; +using OpenFeature.Model; + +namespace OpenFeature.Contrib.Providers.Harness; + +/// +/// HarnessAdapter is the .NET adapter for the Harness feature flag SDK +/// It provides functions to convert the OpenFeature EvaluationContext to +/// a harness target, and functions to convert the Harness evaluation result +/// to a OpenFeature ResolutionDetails. +/// +public static class HarnessAdapter +{ + /// + /// Convert the Harness evaluation result to a OpenFeature ResolutionDetails. + /// + /// + /// + /// + /// + public static ResolutionDetails HarnessResponse(string flagKey, T defaultValue) + { + /* + * string flagKey, T value, ErrorType errorType = ErrorType.None, string reason = null, + string variant = null, string errorMessage = null + */ + return new ResolutionDetails( + flagKey, + defaultValue); + } + + /// + /// Convert the OpenFeature EvaluationContext to a harness target. + /// + /// + /// + public static Target CreateTarget(EvaluationContext context) + { + // Get the identifier, if it is missing or empty return null + if (context.TryGetValue("identifier", out var identifier) != true || identifier.IsString != true) + { + return null; + } + + // Get the name, if it is missing or empty return null + if (context.TryGetValue("name", out var name) != true || name.IsString != true) + { + return null; + } + + // Create a target (different targets can get different results based on rules) + // TODO we need to deal with target attributes + // .Attributes(new Dictionary(){{"email", "demo@harness.io"}}) + Target target = Target.builder() + .Name(name.AsString) + .Identifier(identifier.AsString) + .build(); + return target; + + } + + +} \ No newline at end of file diff --git a/src/OpenFeature.Contrib.Providers.Harness/OpenFeature.Contrib.Providers.Harness.csproj b/src/OpenFeature.Contrib.Providers.Harness/OpenFeature.Contrib.Providers.Harness.csproj new file mode 100644 index 00000000..4adf98cc --- /dev/null +++ b/src/OpenFeature.Contrib.Providers.Harness/OpenFeature.Contrib.Providers.Harness.csproj @@ -0,0 +1,23 @@ + + + OpenFeature.Contrib.Providers.Harness + 0.0.1 + $(VersionNumber) + $(VersionNumber) + $(VersionNumber) + Harness Feature Flag provider for .NET + https://www.harness.io/products/feature-flags + https://github.com/open-feature/dotnet-sdk-contrib + Dave Johnston + + + + + + + + + latest + + + diff --git a/src/OpenFeature.Contrib.Providers.Harness/Provider.cs b/src/OpenFeature.Contrib.Providers.Harness/Provider.cs new file mode 100644 index 00000000..b59dcbec --- /dev/null +++ b/src/OpenFeature.Contrib.Providers.Harness/Provider.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using io.harness.cfsdk.client.api; +using OpenFeature.Model; + +namespace OpenFeature.Contrib.Providers.Harness; + + +/// +/// HarnessProvider is the .NET provider implementation for the Harness feature flag SDK +/// +public class Provider : FeatureProvider +{ + private const string HarnessProviderName = "Harness Provider"; + + private readonly Metadata _metadata = new (HarnessProviderName); + private readonly ICfClient _client; + + /// + /// Constructor of the Harness provider. + /// + public Provider(ICfClient client) + { + _client = client; + } + + /// + public override Metadata GetMetadata() + { + return this._metadata; + } + + /// + public override Task> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null) + { + var result = _client.boolVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue); + return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, result)); + } + + /// + public override Task> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null) + { + var result = _client.stringVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue); + return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, result)); + } + + /// + public override Task> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null) + { + var result = _client.numberVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue); + return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, Convert.ToInt32(result))); + } + + /// + public override Task> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null) + { + var result = _client.numberVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue); + return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, result)); + } + + /// + public override Task> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null) + { + // TODO need to implement this + return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, defaultValue)); + } + + +} \ No newline at end of file