Skip to content

Commit

Permalink
feat!: Implement builders and immutable contexts. (#77)
Browse files Browse the repository at this point in the history
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 <4955475+kinyoklion@users.noreply.github.com>
Co-authored-by: Benjamin Evenson <2031163+benjiro@users.noreply.github.com>
  • Loading branch information
kinyoklion and benjiro authored Oct 6, 2022
1 parent b59750b commit d980a94
Show file tree
Hide file tree
Showing 18 changed files with 564 additions and 470 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ OpenFeature.Instance.SetProvider(new NoOpProvider());
var client = OpenFeature.Instance.GetClient();
// Evaluation the `my-feature` feature flag
var isEnabled = await client.GetBooleanValue("my-feature", false);

// Evaluating with a context.
var evaluationContext = EvaluationContext.Builder()
.Set("my-key", "my-value")
.Build();

// Evaluation the `my-conditional` feature flag
var isEnabled = await client.GetBooleanValue("my-conditional", false, evaluationContext);
```

### Provider
Expand Down
4 changes: 4 additions & 0 deletions build/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@
<MicrosoftExtensionsLoggerVer>[2.0,6.0)</MicrosoftExtensionsLoggerVer>
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="[1.7.1, 7.0.0)" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/OpenFeatureSDK/Hook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public abstract class Hook
public virtual Task<EvaluationContext> Before<T>(HookContext<T> context,
IReadOnlyDictionary<string, object> hints = null)
{
return Task.FromResult(new EvaluationContext());
return Task.FromResult(EvaluationContext.Empty);
}

/// <summary>
Expand Down
211 changes: 35 additions & 176 deletions src/OpenFeatureSDK/Model/EvaluationContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace OpenFeatureSDK.Model
{
Expand All @@ -8,9 +9,31 @@ namespace OpenFeatureSDK.Model
/// to the feature flag evaluation context.
/// </summary>
/// <seealso href="https://github.com/open-feature/spec/blob/main/specification/evaluation-context.md">Evaluation context</seealso>
public class EvaluationContext
public sealed class EvaluationContext
{
private readonly Structure _structure = new Structure();
private readonly Structure _structure;

/// <summary>
/// Internal constructor used by the builder.
/// </summary>
/// <param name="content">The content of the context.</param>
internal EvaluationContext(Structure content)
{
this._structure = content;
}

/// <summary>
/// Private constructor for making an empty <see cref="EvaluationContext"/>.
/// </summary>
private EvaluationContext()
{
this._structure = Structure.Empty;
}

/// <summary>
/// An empty evaluation context.
/// </summary>
public static EvaluationContext Empty { get; } = new EvaluationContext();

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

/// <summary>
/// Removes the Value at the specified key
/// </summary>
/// <param name="key">The key of the value to be removed</param>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
public void Remove(string key) => this._structure.Remove(key);

/// <summary>
/// Gets the value associated with the specified key
/// </summary>
Expand All @@ -59,153 +73,9 @@ public class EvaluationContext
/// Gets all values as a Dictionary
/// </summary>
/// <returns>New <see cref="IDictionary{TKey,TValue}"/> representation of this Structure</returns>
public IDictionary<string, Value> AsDictionary()
{
return new Dictionary<string, Value>(this._structure.AsDictionary());
}

/// <summary>
/// Add a new bool Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, bool value)
public IImmutableDictionary<string, Value> AsDictionary()
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new string Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, string value)
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new int Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, int value)
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new double Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, double value)
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new DateTime Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, DateTime value)
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new Structure Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, Structure value)
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new List Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, List<Value> value)
{
this._structure.Add(key, value);
return this;
}

/// <summary>
/// Add a new Value to the evaluation context
/// </summary>
/// <param name="key">The key of the value to be added</param>
/// <param name="value">The value to be added</param>
/// <returns>This <see cref="EvaluationContext"/></returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the key is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when an element with the same key is already contained in the context
/// </exception>
public EvaluationContext Add(string key, Value value)
{
this._structure.Add(key, value);
return this;
return this._structure.AsDictionary();
}

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

/// <summary>
/// Merges provided evaluation context into this one.
/// Any duplicate keys will be overwritten.
/// Return an enumerator for all values
/// </summary>
/// <param name="other"><see cref="EvaluationContext"/></param>
public void Merge(EvaluationContext other)
/// <returns>An enumerator for all values</returns>
public IEnumerator<KeyValuePair<string, Value>> GetEnumerator()
{
foreach (var key in other._structure.Keys)
{
if (this._structure.ContainsKey(key))
{
this._structure[key] = other._structure[key];
}
else
{
this._structure.Add(key, other._structure[key]);
}
}
return this._structure.GetEnumerator();
}

/// <summary>
/// Return an enumerator for all values
/// Get a builder which can build an <see cref="EvaluationContext"/>.
/// </summary>
/// <returns>An enumerator for all values</returns>
public IEnumerator<KeyValuePair<string, Value>> GetEnumerator()
/// <returns>The builder</returns>
public static EvaluationContextBuilder Builder()
{
return this._structure.GetEnumerator();
return new EvaluationContextBuilder();
}
}
}
Loading

0 comments on commit d980a94

Please sign in to comment.