Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement in-memory provider #232

Merged
merged 16 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ on:
jobs:
e2e-tests:
runs-on: ubuntu-latest
services:
flagd:
image: ghcr.io/open-feature/flagd-testbed:latest
ports:
- 8013:8013
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -36,7 +31,7 @@ jobs:
- name: Initialize Tests
run: |
git submodule update --init --recursive
cp test-harness/features/evaluation.feature test/OpenFeature.E2ETests/Features/
cp spec/specification/assets/gherkin/evaluation.feature test/OpenFeature.E2ETests/Features/

- name: Run Tests
run: dotnet test test/OpenFeature.E2ETests/ --configuration Release --logger GitHubActions
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "test-harness"]
path = test-harness
url = https://github.com/open-feature/test-harness.git
[submodule "spec"]
path = spec
url = https://github.com/open-feature/spec.git
6 changes: 0 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ To be able to run the e2e tests, first we need to initialize the submodule and c
git submodule update --init --recursive && cp test-harness/features/evaluation.feature test/OpenFeature.E2ETests/Features/
```

Afterwards, you need to start flagd locally:

```bash
docker run -p 8013:8013 ghcr.io/open-feature/flagd-testbed:latest
```

Now you can run the tests using:

```bash
Expand Down
1 change: 0 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="OpenFeature.Contrib.Providers.Flagd" Version="0.1.8" />
<PackageVersion Include="SpecFlow" Version="3.9.74" />
<PackageVersion Include="SpecFlow.Tools.MsBuild.Generation" Version="3.9.74" />
<PackageVersion Include="SpecFlow.xUnit" Version="3.9.74" />
Expand Down
1 change: 1 addition & 0 deletions spec
Submodule spec added at b58c3b
78 changes: 78 additions & 0 deletions src/OpenFeature/Providers/Memory/Flag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using OpenFeature.Constant;
using OpenFeature.Error;
using OpenFeature.Model;

toddbaert marked this conversation as resolved.
Show resolved Hide resolved
namespace OpenFeature.Providers.Memory
{
/// <summary>
/// Flag representation for the in-memory provider.
/// </summary>
public class Flag
{

}
toddbaert marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Flag representation for the in-memory provider.
/// </summary>
public class Flag<T> : Flag
toddbaert marked this conversation as resolved.
Show resolved Hide resolved
{
private Dictionary<string, T> Variants;
private string DefaultVariant;
private Func<EvaluationContext, string> ContextEvaluator;

/// <summary>
/// Flag representation for the in-memory provider.
/// </summary>
/// <param name="variants">dictionary of variants and their corresponding values</param>
/// <param name="defaultVariant">default variant (should match 1 key in variants dictionary)</param>
/// <param name="contextEvaluator">optional context-sensitive evaluation function</param>
public Flag(Dictionary<string, T> variants, string defaultVariant, Func<EvaluationContext, string> contextEvaluator = null)
{
this.Variants = variants;
this.DefaultVariant = defaultVariant;
this.ContextEvaluator = contextEvaluator;
}

internal ResolutionDetails<T> Evaluate(string flagKey, T defaultValue, EvaluationContext evaluationContext)
{
T value;
if (this.ContextEvaluator == null)
{
if (this.Variants.TryGetValue(this.DefaultVariant, out value))
{
return new ResolutionDetails<T>(
flagKey,
value,
variant: this.DefaultVariant,
reason: Reason.Static
);
}
else
{
throw new GeneralException($"variant {this.DefaultVariant} not found");

Check warning on line 55 in src/OpenFeature/Providers/Memory/Flag.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/Flag.cs#L54-L55

Added lines #L54 - L55 were not covered by tests
}
}
else
{
string variant = this.ContextEvaluator.Invoke(evaluationContext);
toddbaert marked this conversation as resolved.
Show resolved Hide resolved
this.Variants.TryGetValue(variant, out value);

Check warning on line 61 in src/OpenFeature/Providers/Memory/Flag.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/Flag.cs#L59-L61

Added lines #L59 - L61 were not covered by tests
if (value == null)
{
throw new GeneralException($"variant {variant} not found");

Check warning on line 64 in src/OpenFeature/Providers/Memory/Flag.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/Flag.cs#L63-L64

Added lines #L63 - L64 were not covered by tests
}
else
{
return new ResolutionDetails<T>(
flagKey,
value,
variant: variant,
reason: Reason.TargetingMatch
);

Check warning on line 73 in src/OpenFeature/Providers/Memory/Flag.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/Flag.cs#L67-L73

Added lines #L67 - L73 were not covered by tests
}
}
}
}
}
129 changes: 129 additions & 0 deletions src/OpenFeature/Providers/Memory/InMemoryProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using OpenFeature.Constant;
using OpenFeature.Error;
using OpenFeature.Model;

toddbaert marked this conversation as resolved.
Show resolved Hide resolved
namespace OpenFeature.Providers.Memory
{
/// <summary>
/// The in memory provider.
/// Useful for testing and demonstration purposes.
/// </summary>
/// <seealso href="https://openfeature.dev/specification/appendix-a#in-memory-provider">In Memory Provider specification</seealso>
public class InMemoryProvider : FeatureProvider
askpt marked this conversation as resolved.
Show resolved Hide resolved
{

private readonly Metadata _metadata = new Metadata("InMemory");

private Dictionary<string, Flag> _flags;

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

/// <summary>
/// Construct a new InMemoryProvider.
/// </summary>
/// <param name="flags">dictionary of Flags</param>
public InMemoryProvider(IDictionary<string, Flag> flags = null)
{
if (flags == null)
{
this._flags = new Dictionary<string, Flag>();
}

Check warning on line 39 in src/OpenFeature/Providers/Memory/InMemoryProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/InMemoryProvider.cs#L37-L39

Added lines #L37 - L39 were not covered by tests
else
{
this._flags = new Dictionary<string, Flag>(flags); // shallow copy
}
}

/// <summary>
/// Updating provider flags configuration, replacing all flags.
/// </summary>
/// <param name="flags">the flags to use instead of the previous flags.</param>
public async ValueTask UpdateFlags(IDictionary<string, Flag> flags)
{
if (flags is null)
throw new ArgumentNullException(nameof(flags));

Check warning on line 53 in src/OpenFeature/Providers/Memory/InMemoryProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/InMemoryProvider.cs#L53

Added line #L53 was not covered by tests
this._flags = new Dictionary<string, Flag>(flags); // shallow copy
var @event = new ProviderEventPayload
{
Type = ProviderEventTypes.ProviderConfigurationChanged,
ProviderName = _metadata.Name,
FlagsChanged = flags.Keys.ToList(), // emit all
Message = "flags changed",
};
await this.EventChannel.Writer.WriteAsync(@event).ConfigureAwait(false);
}

/// <inheritdoc/>
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
string flagKey,
bool defaultValue,
EvaluationContext context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}

/// <inheritdoc/>
public override Task<ResolutionDetails<string>> ResolveStringValue(
string flagKey,
string defaultValue,
EvaluationContext context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}

/// <inheritdoc/>
public override Task<ResolutionDetails<int>> ResolveIntegerValue(
string flagKey,
int defaultValue,
EvaluationContext context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}

Check warning on line 90 in src/OpenFeature/Providers/Memory/InMemoryProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/InMemoryProvider.cs#L88-L90

Added lines #L88 - L90 were not covered by tests

/// <inheritdoc/>
public override Task<ResolutionDetails<double>> ResolveDoubleValue(
string flagKey,
double defaultValue,
EvaluationContext context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}

Check warning on line 99 in src/OpenFeature/Providers/Memory/InMemoryProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/InMemoryProvider.cs#L97-L99

Added lines #L97 - L99 were not covered by tests

/// <inheritdoc/>
public override Task<ResolutionDetails<Value>> ResolveStructureValue(
string flagKey,
Value defaultValue,
EvaluationContext context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}

Check warning on line 108 in src/OpenFeature/Providers/Memory/InMemoryProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/InMemoryProvider.cs#L106-L108

Added lines #L106 - L108 were not covered by tests

private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, EvaluationContext context)
{
if (!this._flags.TryGetValue(flagKey, out var flag))
{
throw new FlagNotFoundException($"flag {flag} not found");
}
else
{
if (typeof(Flag<T>).Equals(flag.GetType()))
toddbaert marked this conversation as resolved.
Show resolved Hide resolved
{
return (flag as Flag<T>).Evaluate(flagKey, defaultValue, context);
}
else
{
throw new TypeMismatchException($"flag {flag} not found");

Check warning on line 124 in src/OpenFeature/Providers/Memory/InMemoryProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/InMemoryProvider.cs#L123-L124

Added lines #L123 - L124 were not covered by tests
toddbaert marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}
1 change: 0 additions & 1 deletion test-harness
Submodule test-harness deleted from 01c4a4
1 change: 0 additions & 1 deletion test/OpenFeature.E2ETests/OpenFeature.E2ETests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="OpenFeature.Contrib.Providers.Flagd" />
<PackageReference Include="SpecFlow" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" />
<PackageReference Include="SpecFlow.xUnit" />
Expand Down
Loading