Skip to content

Commit

Permalink
feat: Harness Feature Flag Provider
Browse files Browse the repository at this point in the history
This PR is the inital openfeature provider for the Harness feature flag SDK.
It supports
 * basic targeting (via target name and identifier)
 * bool, number, string, evaluations

Left to do:
 * structured evaluations
 * custom target attributes
 * unit testing
 * provider configuration
  • Loading branch information
davejohnston committed Nov 21, 2023
1 parent 84f453d commit 852247a
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
7 changes: 7 additions & 0 deletions DotnetSdkContrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
63 changes: 63 additions & 0 deletions src/OpenFeature.Contrib.Providers.Harness/HarnessAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using io.harness.cfsdk.client.dto;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.Harness;

/// <summary>
/// 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.
/// </summary>
public static class HarnessAdapter
{
/// <summary>
/// Convert the Harness evaluation result to a OpenFeature ResolutionDetails.
/// </summary>
/// <param name="flagKey"></param>
/// <param name="defaultValue"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ResolutionDetails<T> HarnessResponse<T>(string flagKey, T defaultValue)
{
/*
* string flagKey, T value, ErrorType errorType = ErrorType.None, string reason = null,
string variant = null, string errorMessage = null
*/
return new ResolutionDetails<T>(
flagKey,
defaultValue);
}

/// <summary>
/// Convert the OpenFeature EvaluationContext to a harness target.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
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<string, string>(){{"email", "[email protected]"}})
Target target = Target.builder()
.Name(name.AsString)
.Identifier(identifier.AsString)
.build();
return target;

}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>OpenFeature.Contrib.Providers.Harness</PackageId>
<VersionNumber>0.0.1</VersionNumber>
<Version>$(VersionNumber)</Version>
<AssemblyVersion>$(VersionNumber)</AssemblyVersion>
<FileVersion>$(VersionNumber)</FileVersion>
<Description>Harness Feature Flag provider for .NET</Description>
<PackageProjectUrl>https://www.harness.io/products/feature-flags</PackageProjectUrl>
<RepositoryUrl>https://github.com/open-feature/dotnet-sdk-contrib</RepositoryUrl>
<Authors>Dave Johnston</Authors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="7.0.4" />
<PackageReference Include="ff-dotnet-server-sdk" Version="1.1.*" />
</ItemGroup>

<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>

</Project>
69 changes: 69 additions & 0 deletions src/OpenFeature.Contrib.Providers.Harness/Provider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Threading.Tasks;
using io.harness.cfsdk.client.api;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.Harness;


/// <summary>
/// HarnessProvider is the .NET provider implementation for the Harness feature flag SDK
/// </summary>
public class Provider : FeatureProvider
{
private const string HarnessProviderName = "Harness Provider";

private readonly Metadata _metadata = new (HarnessProviderName);
private readonly ICfClient _client;

/// <summary>
/// Constructor of the Harness provider.
/// </summary>
public Provider(ICfClient client)
{
_client = client;
}

/// <inheritdoc/>
public override Metadata GetMetadata()
{
return this._metadata;
}

/// <inheritdoc/>
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null)
{
var result = _client.boolVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue);
return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, result));
}

/// <inheritdoc/>
public override Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null)
{
var result = _client.stringVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue);
return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, result));
}

/// <inheritdoc/>
public override Task<ResolutionDetails<int>> 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)));
}

/// <inheritdoc/>
public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null)
{
var result = _client.numberVariation(flagKey, HarnessAdapter.CreateTarget(context), defaultValue);
return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, result));
}

/// <inheritdoc/>
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null)
{
// TODO need to implement this
return Task.FromResult(HarnessAdapter.HarnessResponse(flagKey, defaultValue));
}


}

0 comments on commit 852247a

Please sign in to comment.