Skip to content

Commit d980a94

Browse files
kinyoklionbenjiro
andauthored
feat!: Implement builders and immutable contexts. (#77)
Allow for the context provided to the client to be immutable, and for immutability for the duration of a hook. See: #56 Signed-off-by: Ryan Lamb <[email protected]> Co-authored-by: Benjamin Evenson <[email protected]>
1 parent b59750b commit d980a94

18 files changed

+564
-470
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ OpenFeature.Instance.SetProvider(new NoOpProvider());
2525
var client = OpenFeature.Instance.GetClient();
2626
// Evaluation the `my-feature` feature flag
2727
var isEnabled = await client.GetBooleanValue("my-feature", false);
28+
29+
// Evaluating with a context.
30+
var evaluationContext = EvaluationContext.Builder()
31+
.Set("my-key", "my-value")
32+
.Build();
33+
34+
// Evaluation the `my-conditional` feature flag
35+
var isEnabled = await client.GetBooleanValue("my-conditional", false, evaluationContext);
2836
```
2937

3038
### Provider

build/Common.props

+4
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@
2121
<MicrosoftExtensionsLoggerVer>[2.0,6.0)</MicrosoftExtensionsLoggerVer>
2222
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
2323
</PropertyGroup>
24+
25+
<ItemGroup>
26+
<PackageReference Include="System.Collections.Immutable" Version="[1.7.1, 7.0.0)" />
27+
</ItemGroup>
2428
</Project>

src/OpenFeatureSDK/Hook.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public abstract class Hook
3131
public virtual Task<EvaluationContext> Before<T>(HookContext<T> context,
3232
IReadOnlyDictionary<string, object> hints = null)
3333
{
34-
return Task.FromResult(new EvaluationContext());
34+
return Task.FromResult(EvaluationContext.Empty);
3535
}
3636

3737
/// <summary>
+35-176
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34

45
namespace OpenFeatureSDK.Model
56
{
@@ -8,9 +9,31 @@ namespace OpenFeatureSDK.Model
89
/// to the feature flag evaluation context.
910
/// </summary>
1011
/// <seealso href="https://github.com/open-feature/spec/blob/main/specification/evaluation-context.md">Evaluation context</seealso>
11-
public class EvaluationContext
12+
public sealed class EvaluationContext
1213
{
13-
private readonly Structure _structure = new Structure();
14+
private readonly Structure _structure;
15+
16+
/// <summary>
17+
/// Internal constructor used by the builder.
18+
/// </summary>
19+
/// <param name="content">The content of the context.</param>
20+
internal EvaluationContext(Structure content)
21+
{
22+
this._structure = content;
23+
}
24+
25+
/// <summary>
26+
/// Private constructor for making an empty <see cref="EvaluationContext"/>.
27+
/// </summary>
28+
private EvaluationContext()
29+
{
30+
this._structure = Structure.Empty;
31+
}
32+
33+
/// <summary>
34+
/// An empty evaluation context.
35+
/// </summary>
36+
public static EvaluationContext Empty { get; } = new EvaluationContext();
1437

1538
/// <summary>
1639
/// Gets the Value at the specified key
@@ -35,15 +58,6 @@ public class EvaluationContext
3558
/// </exception>
3659
public bool ContainsKey(string key) => this._structure.ContainsKey(key);
3760

38-
/// <summary>
39-
/// Removes the Value at the specified key
40-
/// </summary>
41-
/// <param name="key">The key of the value to be removed</param>
42-
/// <exception cref="ArgumentNullException">
43-
/// Thrown when the key is <see langword="null" />
44-
/// </exception>
45-
public void Remove(string key) => this._structure.Remove(key);
46-
4761
/// <summary>
4862
/// Gets the value associated with the specified key
4963
/// </summary>
@@ -59,153 +73,9 @@ public class EvaluationContext
5973
/// Gets all values as a Dictionary
6074
/// </summary>
6175
/// <returns>New <see cref="IDictionary{TKey,TValue}"/> representation of this Structure</returns>
62-
public IDictionary<string, Value> AsDictionary()
63-
{
64-
return new Dictionary<string, Value>(this._structure.AsDictionary());
65-
}
66-
67-
/// <summary>
68-
/// Add a new bool Value to the evaluation context
69-
/// </summary>
70-
/// <param name="key">The key of the value to be added</param>
71-
/// <param name="value">The value to be added</param>
72-
/// <returns>This <see cref="EvaluationContext"/></returns>
73-
/// <exception cref="ArgumentNullException">
74-
/// Thrown when the key is <see langword="null" />
75-
/// </exception>
76-
/// <exception cref="ArgumentException">
77-
/// Thrown when an element with the same key is already contained in the context
78-
/// </exception>
79-
public EvaluationContext Add(string key, bool value)
76+
public IImmutableDictionary<string, Value> AsDictionary()
8077
{
81-
this._structure.Add(key, value);
82-
return this;
83-
}
84-
85-
/// <summary>
86-
/// Add a new string Value to the evaluation context
87-
/// </summary>
88-
/// <param name="key">The key of the value to be added</param>
89-
/// <param name="value">The value to be added</param>
90-
/// <returns>This <see cref="EvaluationContext"/></returns>
91-
/// <exception cref="ArgumentNullException">
92-
/// Thrown when the key is <see langword="null" />
93-
/// </exception>
94-
/// <exception cref="ArgumentException">
95-
/// Thrown when an element with the same key is already contained in the context
96-
/// </exception>
97-
public EvaluationContext Add(string key, string value)
98-
{
99-
this._structure.Add(key, value);
100-
return this;
101-
}
102-
103-
/// <summary>
104-
/// Add a new int Value to the evaluation context
105-
/// </summary>
106-
/// <param name="key">The key of the value to be added</param>
107-
/// <param name="value">The value to be added</param>
108-
/// <returns>This <see cref="EvaluationContext"/></returns>
109-
/// <exception cref="ArgumentNullException">
110-
/// Thrown when the key is <see langword="null" />
111-
/// </exception>
112-
/// <exception cref="ArgumentException">
113-
/// Thrown when an element with the same key is already contained in the context
114-
/// </exception>
115-
public EvaluationContext Add(string key, int value)
116-
{
117-
this._structure.Add(key, value);
118-
return this;
119-
}
120-
121-
/// <summary>
122-
/// Add a new double Value to the evaluation context
123-
/// </summary>
124-
/// <param name="key">The key of the value to be added</param>
125-
/// <param name="value">The value to be added</param>
126-
/// <returns>This <see cref="EvaluationContext"/></returns>
127-
/// <exception cref="ArgumentNullException">
128-
/// Thrown when the key is <see langword="null" />
129-
/// </exception>
130-
/// <exception cref="ArgumentException">
131-
/// Thrown when an element with the same key is already contained in the context
132-
/// </exception>
133-
public EvaluationContext Add(string key, double value)
134-
{
135-
this._structure.Add(key, value);
136-
return this;
137-
}
138-
139-
/// <summary>
140-
/// Add a new DateTime Value to the evaluation context
141-
/// </summary>
142-
/// <param name="key">The key of the value to be added</param>
143-
/// <param name="value">The value to be added</param>
144-
/// <returns>This <see cref="EvaluationContext"/></returns>
145-
/// <exception cref="ArgumentNullException">
146-
/// Thrown when the key is <see langword="null" />
147-
/// </exception>
148-
/// <exception cref="ArgumentException">
149-
/// Thrown when an element with the same key is already contained in the context
150-
/// </exception>
151-
public EvaluationContext Add(string key, DateTime value)
152-
{
153-
this._structure.Add(key, value);
154-
return this;
155-
}
156-
157-
/// <summary>
158-
/// Add a new Structure Value to the evaluation context
159-
/// </summary>
160-
/// <param name="key">The key of the value to be added</param>
161-
/// <param name="value">The value to be added</param>
162-
/// <returns>This <see cref="EvaluationContext"/></returns>
163-
/// <exception cref="ArgumentNullException">
164-
/// Thrown when the key is <see langword="null" />
165-
/// </exception>
166-
/// <exception cref="ArgumentException">
167-
/// Thrown when an element with the same key is already contained in the context
168-
/// </exception>
169-
public EvaluationContext Add(string key, Structure value)
170-
{
171-
this._structure.Add(key, value);
172-
return this;
173-
}
174-
175-
/// <summary>
176-
/// Add a new List Value to the evaluation context
177-
/// </summary>
178-
/// <param name="key">The key of the value to be added</param>
179-
/// <param name="value">The value to be added</param>
180-
/// <returns>This <see cref="EvaluationContext"/></returns>
181-
/// <exception cref="ArgumentNullException">
182-
/// Thrown when the key is <see langword="null" />
183-
/// </exception>
184-
/// <exception cref="ArgumentException">
185-
/// Thrown when an element with the same key is already contained in the context
186-
/// </exception>
187-
public EvaluationContext Add(string key, List<Value> value)
188-
{
189-
this._structure.Add(key, value);
190-
return this;
191-
}
192-
193-
/// <summary>
194-
/// Add a new Value to the evaluation context
195-
/// </summary>
196-
/// <param name="key">The key of the value to be added</param>
197-
/// <param name="value">The value to be added</param>
198-
/// <returns>This <see cref="EvaluationContext"/></returns>
199-
/// <exception cref="ArgumentNullException">
200-
/// Thrown when the key is <see langword="null" />
201-
/// </exception>
202-
/// <exception cref="ArgumentException">
203-
/// Thrown when an element with the same key is already contained in the context
204-
/// </exception>
205-
public EvaluationContext Add(string key, Value value)
206-
{
207-
this._structure.Add(key, value);
208-
return this;
78+
return this._structure.AsDictionary();
20979
}
21080

21181
/// <summary>
@@ -214,32 +84,21 @@ public EvaluationContext Add(string key, Value value)
21484
public int Count => this._structure.Count;
21585

21686
/// <summary>
217-
/// Merges provided evaluation context into this one.
218-
/// Any duplicate keys will be overwritten.
87+
/// Return an enumerator for all values
21988
/// </summary>
220-
/// <param name="other"><see cref="EvaluationContext"/></param>
221-
public void Merge(EvaluationContext other)
89+
/// <returns>An enumerator for all values</returns>
90+
public IEnumerator<KeyValuePair<string, Value>> GetEnumerator()
22291
{
223-
foreach (var key in other._structure.Keys)
224-
{
225-
if (this._structure.ContainsKey(key))
226-
{
227-
this._structure[key] = other._structure[key];
228-
}
229-
else
230-
{
231-
this._structure.Add(key, other._structure[key]);
232-
}
233-
}
92+
return this._structure.GetEnumerator();
23493
}
23594

23695
/// <summary>
237-
/// Return an enumerator for all values
96+
/// Get a builder which can build an <see cref="EvaluationContext"/>.
23897
/// </summary>
239-
/// <returns>An enumerator for all values</returns>
240-
public IEnumerator<KeyValuePair<string, Value>> GetEnumerator()
98+
/// <returns>The builder</returns>
99+
public static EvaluationContextBuilder Builder()
241100
{
242-
return this._structure.GetEnumerator();
101+
return new EvaluationContextBuilder();
243102
}
244103
}
245104
}

0 commit comments

Comments
 (0)