Skip to content

Commit

Permalink
feat: GO Feature Flag dotnet provider (open-feature#24)
Browse files Browse the repository at this point in the history
GO Feature Flag dotnet provider

Signed-off-by: Thomas Poignant <[email protected]>
Signed-off-by: Vladimir Petrusevici <[email protected]>
  • Loading branch information
thomaspoignant authored and vpetrusevici committed Oct 18, 2023
1 parent a3132dd commit b261260
Show file tree
Hide file tree
Showing 22 changed files with 1,352 additions and 0 deletions.
14 changes: 14 additions & 0 deletions DotnetSdkContrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Hooks.O
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagd.Test", "test\OpenFeature.Contrib.Providers.Flagd.Test\OpenFeature.Contrib.Providers.Flagd.Test.csproj", "{206323A0-7334-4723-8394-C31C150B95DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.GOFeatureFlag", "src\OpenFeature.Contrib.Providers.GOFeatureFlag\OpenFeature.Contrib.Providers.GOFeatureFlag.csproj", "{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.GOFeatureFlag.Test", "test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test.csproj", "{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -40,11 +44,21 @@ Global
{206323A0-7334-4723-8394-C31C150B95DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{206323A0-7334-4723-8394-C31C150B95DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{206323A0-7334-4723-8394-C31C150B95DC}.Release|Any CPU.Build.0 = Release|Any CPU
{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}.Release|Any CPU.Build.0 = Release|Any CPU
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
{82D10BAE-F1EE-432A-BD5D-DECAD07A84FE} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
{199FA48A-06EF-4E15-8206-C095D1455A99} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
{206323A0-7334-4723-8394-C31C150B95DC} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
EndGlobalSection
EndGlobal
10 changes: 10 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
"extra-files": [
"OpenFeature.Contrib.Providers.Flagd.csproj"
]
},
"src/OpenFeature.Contrib.Providers.GOFeatureFlag": {
"package-name": "OpenFeature.Contrib.Providers.GOFeatureFlag",
"release-type": "simple",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"versioning": "default",
"extra-files": [
"OpenFeature.Contrib.Providers.GOFeatureFlag.csproj"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace OpenFeature.Contrib.Providers.GOFeatureFlag
{
/// <summary>
/// GOFeatureFlagRequest is the object formatting the request to the relay proxy.
/// </summary>
/// <typeparam name="T">Type of the default value.</typeparam>
public class GOFeatureFlagRequest<T>
{
/// <summary>
/// GoFeatureFlagUser is the representation of the user.
/// </summary>
public GoFeatureFlagUser User { get; set; }

/// <summary>
/// default value if we have an error.
/// </summary>
public T DefaultValue { get; set; }
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Net.Http;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag
{
/// <Summary>
/// GoFeatureFlagProviderOptions contains the options to initialise the provider.
/// </Summary>
public class GoFeatureFlagProviderOptions
{
/// <Summary>
/// (mandatory) endpoint contains the DNS of your GO Feature Flag relay proxy
/// example: https://mydomain.com/gofeatureflagproxy/
/// </Summary>
public string Endpoint { get; set; }

/// <Summary>
/// (optional) timeout we are waiting when calling the go-feature-flag relay proxy API.
/// Default: 10000 ms
/// </Summary>
public TimeSpan Timeout { get; set; } = new TimeSpan(10000 * TimeSpan.TicksPerMillisecond);

/// <Summary>
/// (optional) If you want to provide your own HttpMessageHandler.
/// Default: null
/// </Summary>
public HttpMessageHandler HttpMessageHandler { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace OpenFeature.Contrib.Providers.GOFeatureFlag
{
/// <summary>
/// GoFeatureFlagResponse is the response returned by the relay proxy.
/// </summary>
public class GoFeatureFlagResponse
{
/// <summary>
/// trackEvent is true when this call was tracked in GO Feature Flag.
/// </summary>
public bool trackEvents { get; set; }

/// <summary>
/// variationType contains the name of the variation used for this flag.
/// </summary>
public string variationType { get; set; }

/// <summary>
/// failed is true if GO Feature Flag had an issue.
/// </summary>
public bool failed { get; set; }

/// <summary>
/// version of the flag used (optional)
/// </summary>
public string version { get; set; }

/// <summary>
/// reason used to choose this variation.
/// </summary>
public string reason { get; set; }

/// <summary>
/// errorCode is empty if everything went ok.
/// </summary>
public string errorCode { get; set; }

/// <summary>
/// value contains the result of the flag.
/// </summary>
public object value { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using OpenFeature.Contrib.Providers.GOFeatureFlag.exception;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag
{
/// <summary>
/// GOFeatureFlagUser is the representation of a User inside GO Feature Flag.
/// </summary>
public class GoFeatureFlagUser
{
private const string AnonymousField = "anonymous";
private const string KeyField = "targetingKey";
private string Key { get; set; }
private bool Anonymous { get; set; }
private Dictionary<string, object> Custom { get; set; }

/**
* FromEvaluationContext convert the evaluation context into a GOFeatureFlagUser Object.
*/
public static GoFeatureFlagUser FromEvaluationContext(EvaluationContext ctx)
{
try
{
if (ctx is null)
throw new InvalidEvaluationContext("GO Feature Flag need an Evaluation context to work.");
if (!ctx.GetValue(KeyField).IsString)
throw new InvalidTargetingKey("targetingKey field MUST be a string.");
}
catch (KeyNotFoundException e)
{
throw new InvalidTargetingKey("targetingKey field is mandatory.", e);
}

var anonymous = ctx.ContainsKey(AnonymousField) && ctx.GetValue(AnonymousField).IsBoolean
? ctx.GetValue(AnonymousField).AsBoolean
: false;

var custom = ctx.AsDictionary().ToDictionary(x => x.Key, x => x.Value.AsObject);
custom.Remove(AnonymousField);
custom.Remove(KeyField);

return new GoFeatureFlagUser
{
Key = ctx.GetValue("targetingKey").AsString,
Anonymous = anonymous.Value,
Custom = custom
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>OpenFeature.Contrib.GOFeatureFlag</PackageId>
<VersionNumber>0.1.0</VersionNumber> <!--x-release-please-version -->
<Version>$(VersionNumber)</Version>
<AssemblyVersion>$(VersionNumber)</AssemblyVersion>
<FileVersion>$(VersionNumber)</FileVersion>
<Description>GO Feature Flag provider for .NET</Description>
<PackageProjectUrl>https://gofeatureflag.org</PackageProjectUrl>
<RepositoryUrl>https://github.com/open-feature/dotnet-sdk-contrib</RepositoryUrl>
<Authors>Thomas Poignant</Authors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="7.0.0" />
</ItemGroup>

<PropertyGroup>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

</Project>
93 changes: 93 additions & 0 deletions src/OpenFeature.Contrib.Providers.GOFeatureFlag/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# GO Feature Flag .NET Provider

GO Feature Flag provider allows you to connect to your GO Feature Flag instance.

[GO Feature Flag](https://gofeatureflag.org) believes in simplicity and offers a simple and lightweight solution to use feature flags.
Our focus is to avoid any complex infrastructure work to use GO Feature Flag.

This is a complete feature flagging solution with the possibility to target only a group of users, use any types of flags, store your configuration in various location and advanced rollout functionality. You can also collect usage data of your flags and be notified of configuration changes.

# .Net SDK usage

## Install dependencies

The first things we will do is install the **Open Feature SDK** and the **GO Feature Flag provider**.

### .NET Cli
```shell
dotnet add package OpenFeature.Contrib.Providers.GOFeatureFlag
```
### Package Manager

```shell
NuGet\Install-Package OpenFeature.Contrib.Providers.GOFeatureFlag
```
### Package Reference

```xml
<PackageReference Include="OpenFeature.Contrib.Providers.GOFeatureFlag" />
```
### Packet cli

```shell
paket add OpenFeature.Contrib.Providers.GOFeatureFlag
```

### Cake

```shell
// Install OpenFeature.Contrib.Providers.GOFeatureFlag as a Cake Addin
#addin nuget:?package=OpenFeature.Contrib.Providers.GOFeatureFlag

// Install OpenFeature.Contrib.Providers.GOFeatureFlag as a Cake Tool
#tool nuget:?package=OpenFeature.Contrib.Providers.GOFeatureFlag
```

## Initialize your Open Feature client

To evaluate the flags you need to have an Open Feature configured in you app.
This code block shows you how you can create a client that you can use in your application.

```csharp
using OpenFeature;
using OpenFeature.Contrib.Providers.GOFeatureFlag;

// ...
var goFeatureFlagProvider = new GoFeatureFlagProvider(new GoFeatureFlagProviderOptions
{
Endpoint = "http://localhost:1031/",
Timeout = new TimeSpan(1000 * TimeSpan.TicksPerMillisecond)
});
Api.Instance.SetProvider(goFeatureFlagProvider);
var client = Api.Instance.GetClient("my-app");
```

## Evaluate your flag

This code block explain how you can create an `EvaluationContext` and use it to evaluate your flag.


> In this example we are evaluating a `boolean` flag, but other types are available.
>
> **Refer to the [Open Feature documentation](https://docs.openfeature.dev/docs/reference/concepts/evaluation-api#basic-evaluation) to know more about it.**
```csharp
// Context of your flag evaluation.
// With GO Feature Flag you MUST have a targetingKey that is a unique identifier of the user.
var userContext = EvaluationContext.Builder()
.Set("targetingKey", "1d1b9238-2591-4a47-94cf-d2bc080892f1") // user unique identifier (mandatory)
.Set("firstname", "john")
.Set("lastname", "doe")
.Set("email", "[email protected]")
.Set("admin", true) // this field is used in the targeting rule of the flag "flag-only-for-admin"
.Set("anonymous", false)
.Build();

var adminFlag = await client.GetBooleanValue("flag-only-for-admin", false, userContext);
if (adminFlag) {
// flag "flag-only-for-admin" is true for the user
} else {
// flag "flag-only-for-admin" is false for the user
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace OpenFeature.Contrib.Providers.GOFeatureFlag.exception
{
/// <summary>
/// Exception thrown when a flag is disabled
/// </summary>
public class FlagDisabled : GoFeatureFlagException
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using OpenFeature.Constant;
using OpenFeature.Error;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag.exception
{
/// <summary>
/// Exception thrown when the flag is not found by GO Feature Flag relay proxy.
/// </summary>
public class FlagNotFoundError : FeatureProviderException
{
/// <summary>
/// Constructor of the exception
/// </summary>
/// <param name="message">Message to display</param>
/// <param name="innerException">Original exception</param>
public FlagNotFoundError(string message, Exception innerException = null) : base(ErrorType.FlagNotFound,
message, innerException)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using OpenFeature.Constant;
using OpenFeature.Error;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag.exception
{
/// <summary>
/// Exception throw when we don't have a specific case.
/// </summary>
public class GeneralError : FeatureProviderException
{
/// <summary>
/// Constructor of the exception
/// </summary>
/// <param name="message">Message to display</param>
/// <param name="innerException">Original exception</param>
public GeneralError(string message, Exception innerException = null) : base(ErrorType.General, message,
innerException)
{
}
}
}
Loading

0 comments on commit b261260

Please sign in to comment.