Skip to content

Commit 8ff1817

Browse files
toddbaertCommCody
andcommitted
feat: implement in-memory provider
Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Joris Goovaerts <[email protected]>
1 parent cc6c404 commit 8ff1817

File tree

12 files changed

+356
-29
lines changed

12 files changed

+356
-29
lines changed

.github/workflows/e2e.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ on:
1313
jobs:
1414
e2e-tests:
1515
runs-on: ubuntu-latest
16-
services:
17-
flagd:
18-
image: ghcr.io/open-feature/flagd-testbed:latest
19-
ports:
20-
- 8013:8013
2116
steps:
2217
- uses: actions/checkout@v4
2318
with:
@@ -36,7 +31,7 @@ jobs:
3631
- name: Initialize Tests
3732
run: |
3833
git submodule update --init --recursive
39-
cp test-harness/features/evaluation.feature test/OpenFeature.E2ETests/Features/
34+
cp spec/specification/assets/gherkin/evaluation.feature test/OpenFeature.E2ETests/Features/
4035
4136
- name: Run Tests
4237
run: dotnet test test/OpenFeature.E2ETests/ --configuration Release --logger GitHubActions

.gitmodules

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
[submodule "test-harness"]
2-
path = test-harness
3-
url = https://github.com/open-feature/test-harness.git
1+
[submodule "spec"]
2+
path = spec
3+
url = git@github.com:open-feature/spec.git

CONTRIBUTING.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,6 @@ To be able to run the e2e tests, first we need to initialize the submodule and c
6767
git submodule update --init --recursive && cp test-harness/features/evaluation.feature test/OpenFeature.E2ETests/Features/
6868
```
6969

70-
Afterwards, you need to start flagd locally:
71-
72-
```bash
73-
docker run -p 8013:8013 ghcr.io/open-feature/flagd-testbed:latest
74-
```
75-
7670
Now you can run the tests using:
7771

7872
```bash

Directory.Packages.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
2121
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2222
<PackageVersion Include="NSubstitute" Version="5.1.0" />
23-
<PackageVersion Include="OpenFeature.Contrib.Providers.Flagd" Version="0.1.8" />
2423
<PackageVersion Include="SpecFlow" Version="3.9.74" />
2524
<PackageVersion Include="SpecFlow.Tools.MsBuild.Generation" Version="3.9.74" />
2625
<PackageVersion Include="SpecFlow.xUnit" Version="3.9.74" />

spec

Submodule spec added at b58c3b4
File renamed without changes.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using OpenFeature.Constant;
4+
using OpenFeature.Error;
5+
using OpenFeature.Model;
6+
7+
namespace OpenFeature.Providers.Memory
8+
{
9+
/// <summary>
10+
/// Flag representation for the in-memory provider.
11+
/// </summary>
12+
public class Flag
13+
{
14+
15+
}
16+
17+
/// <summary>
18+
/// Flag representation for the in-memory provider.
19+
/// </summary>
20+
public class Flag<T> : Flag
21+
{
22+
private Dictionary<string, T> Variants;
23+
private string DefaultVariant;
24+
private Func<EvaluationContext, string> ContextEvaluator;
25+
26+
/// <summary>
27+
/// Flag representation for the in-memory provider.
28+
/// </summary>
29+
/// <param name="variants">dictionary of variants and their corresponding values</param>
30+
/// <param name="defaultVariant">default variant (should match 1 key in variants dictionary)</param>
31+
/// <param name="contextEvaluator">optional context-sensitive evaluation function</param>
32+
public Flag(Dictionary<string, T> variants, string defaultVariant, Func<EvaluationContext, string> contextEvaluator = null)
33+
{
34+
this.Variants = variants;
35+
this.DefaultVariant = defaultVariant;
36+
this.ContextEvaluator = contextEvaluator;
37+
}
38+
39+
internal ResolutionDetails<T> Evaluate(string flagKey, T defaultValue, EvaluationContext evaluationContext)
40+
{
41+
T value;
42+
if (this.ContextEvaluator == null)
43+
{
44+
if (this.Variants.TryGetValue(this.DefaultVariant, out value))
45+
{
46+
return new ResolutionDetails<T>(
47+
flagKey,
48+
value,
49+
variant: this.DefaultVariant,
50+
reason: Reason.Static
51+
);
52+
}
53+
else
54+
{
55+
throw new GeneralException($"variant {this.DefaultVariant} not found");
56+
}
57+
}
58+
else
59+
{
60+
string variant = this.ContextEvaluator.Invoke(evaluationContext);
61+
this.Variants.TryGetValue(variant, out value);
62+
if (value == null)
63+
{
64+
throw new GeneralException($"variant {variant} not found");
65+
}
66+
else
67+
{
68+
return new ResolutionDetails<T>(
69+
flagKey,
70+
value,
71+
variant: variant,
72+
reason: Reason.TargetingMatch
73+
);
74+
}
75+
}
76+
}
77+
}
78+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using OpenFeature.Constant;
7+
using OpenFeature.Error;
8+
using OpenFeature.Model;
9+
10+
namespace OpenFeature.Providers.Memory
11+
{
12+
/// <summary>
13+
/// The in memory provider.
14+
/// Useful for testing and demonstration purposes.
15+
/// </summary>
16+
/// <seealso href="https://openfeature.dev/specification/appendix-a#in-memory-provider">In Memory Provider specification</seealso>
17+
public class InMemoryProvider : FeatureProvider
18+
{
19+
20+
private readonly Metadata _metadata = new Metadata("InMemory");
21+
22+
private Dictionary<string, Flag> _flags;
23+
24+
/// <inheritdoc/>
25+
public override Metadata GetMetadata()
26+
{
27+
return this._metadata;
28+
}
29+
30+
/// <summary>
31+
/// Construct a new InMemoryProvider.
32+
/// </summary>
33+
/// <param name="flags">dictionary of Flags</param>
34+
public InMemoryProvider(IDictionary<string, Flag> flags = null)
35+
{
36+
if (flags == null)
37+
{
38+
this._flags = new Dictionary<string, Flag>();
39+
}
40+
else
41+
{
42+
this._flags = new Dictionary<string, Flag>(flags); // shallow copy
43+
}
44+
}
45+
46+
/// <summary>
47+
/// Updating provider flags configuration, replacing all flags.
48+
/// </summary>
49+
/// <param name="flags">the flags to use instead of the previous flags.</param>
50+
public async ValueTask UpdateFlags(IDictionary<string, Flag> flags)
51+
{
52+
if (flags is null)
53+
throw new ArgumentNullException(nameof(flags));
54+
this._flags = new Dictionary<string, Flag>(flags); // shallow copy
55+
var @event = new ProviderEventPayload
56+
{
57+
Type = ProviderEventTypes.ProviderConfigurationChanged,
58+
ProviderName = _metadata.Name,
59+
FlagsChanged = flags.Keys.ToList(), // emit all
60+
Message = "flags changed",
61+
};
62+
await this.EventChannel.Writer.WriteAsync(@event).ConfigureAwait(false);
63+
}
64+
65+
/// <inheritdoc/>
66+
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
67+
string flagKey,
68+
bool defaultValue,
69+
EvaluationContext context = null)
70+
{
71+
return Task.FromResult(Resolve(flagKey, defaultValue, context));
72+
}
73+
74+
/// <inheritdoc/>
75+
public override Task<ResolutionDetails<string>> ResolveStringValue(
76+
string flagKey,
77+
string defaultValue,
78+
EvaluationContext context = null)
79+
{
80+
return Task.FromResult(Resolve(flagKey, defaultValue, context));
81+
}
82+
83+
/// <inheritdoc/>
84+
public override Task<ResolutionDetails<int>> ResolveIntegerValue(
85+
string flagKey,
86+
int defaultValue,
87+
EvaluationContext context = null)
88+
{
89+
return Task.FromResult(Resolve(flagKey, defaultValue, context));
90+
}
91+
92+
/// <inheritdoc/>
93+
public override Task<ResolutionDetails<double>> ResolveDoubleValue(
94+
string flagKey,
95+
double defaultValue,
96+
EvaluationContext context = null)
97+
{
98+
return Task.FromResult(Resolve(flagKey, defaultValue, context));
99+
}
100+
101+
/// <inheritdoc/>
102+
public override Task<ResolutionDetails<Value>> ResolveStructureValue(
103+
string flagKey,
104+
Value defaultValue,
105+
EvaluationContext context = null)
106+
{
107+
return Task.FromResult(Resolve(flagKey, defaultValue, context));
108+
}
109+
110+
private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, EvaluationContext context)
111+
{
112+
if (!this._flags.TryGetValue(flagKey, out var flag))
113+
{
114+
throw new FlagNotFoundException($"flag {flag} not found");
115+
}
116+
else
117+
{
118+
if (typeof(Flag<T>).Equals(flag.GetType())) {
119+
return (flag as Flag<T>).Evaluate(flagKey, defaultValue, context);
120+
} else {
121+
throw new TypeMismatchException($"flag {flag} not found");
122+
}
123+
}
124+
}
125+
}
126+
}

test-harness

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/OpenFeature.E2ETests/OpenFeature.E2ETests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
<PrivateAssets>all</PrivateAssets>
1717
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1818
</PackageReference>
19-
<PackageReference Include="OpenFeature.Contrib.Providers.Flagd" />
2019
<PackageReference Include="SpecFlow" />
2120
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" />
2221
<PackageReference Include="SpecFlow.xUnit" />

0 commit comments

Comments
 (0)