-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Ryan Lamb <[email protected]>
- Loading branch information
1 parent
9185b76
commit 1264589
Showing
6 changed files
with
390 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using OpenFeature.Model; | ||
|
||
namespace OpenFeature | ||
{ | ||
/// <summary> | ||
/// A key-value collection of strings to objects used for passing data between hook stages. | ||
/// <para> | ||
/// This collection is scoped to a single evaluation for a single hook. Each hook stage for the evaluation | ||
/// will share the same <see cref="HookData"/>. | ||
/// </para> | ||
/// <para> | ||
/// This collection is intended for use only during the execution of individual hook stages, a reference | ||
/// to the collection should not be retained. | ||
/// </para> | ||
/// <para> | ||
/// This collection is not thread-safe. | ||
/// </para> | ||
/// </summary> | ||
/// <seealso href="https://github.com/open-feature/spec/blob/main/specification/sections/04-hooks.md#46-hook-data"/> | ||
public sealed class HookData | ||
{ | ||
private readonly Dictionary<string, object> _data = []; | ||
|
||
/// <summary> | ||
/// Set the key to the given value. | ||
/// </summary> | ||
/// <param name="key">The key for the value</param> | ||
/// <param name="value">The value to set</param> | ||
public void Set(string key, object value) | ||
{ | ||
this._data[key] = value; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the value at the specified key as an object. | ||
/// <remarks> | ||
/// For <see cref="Value"/> types use <see cref="Get"/> instead. | ||
/// </remarks> | ||
/// </summary> | ||
/// <param name="key">The key of the value to be retrieved</param> | ||
/// <returns>The object associated with the key</returns> | ||
/// <exception cref="KeyNotFoundException"> | ||
/// Thrown when the context does not contain the specified key | ||
/// </exception> | ||
/// <exception cref="ArgumentNullException"> | ||
/// Thrown when the key is <see langword="null" /> | ||
/// </exception> | ||
public object Get(string key) | ||
{ | ||
return this._data[key]; | ||
} | ||
|
||
/// <summary> | ||
/// Return a count of all values. | ||
/// </summary> | ||
public int Count => this._data.Count; | ||
|
||
/// <summary> | ||
/// Return an enumerator for all values. | ||
/// </summary> | ||
/// <returns>An enumerator for all values</returns> | ||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() | ||
{ | ||
return this._data.GetEnumerator(); | ||
} | ||
|
||
/// <summary> | ||
/// Return a list containing all the keys in the hook data | ||
/// </summary> | ||
public IImmutableList<string> Keys => this._data.Keys.ToImmutableList(); | ||
|
||
/// <summary> | ||
/// Return an enumerable containing all the values of the hook data | ||
/// </summary> | ||
public IImmutableList<object> Values => this._data.Values.ToImmutableList(); | ||
|
||
/// <summary> | ||
/// Gets all values as a read only dictionary. | ||
/// <remarks> | ||
/// The dictionary references the original values and is not a thread-safe copy. | ||
/// </remarks> | ||
/// </summary> | ||
/// <returns>A <see cref="IDictionary{TKey,TValue}"/> representation of the hook data</returns> | ||
public IReadOnlyDictionary<string, object> AsDictionary() | ||
{ | ||
return this._data; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
using OpenFeature.Model; | ||
|
||
namespace OpenFeature; | ||
|
||
internal partial class HookRunner<T> | ||
{ | ||
private readonly ImmutableList<Hook> _hooks; | ||
|
||
private readonly List<HookContext<T>> _hookContexts; | ||
|
||
private EvaluationContext _evaluationContext; | ||
|
||
private readonly ILogger _logger; | ||
|
||
public HookRunner(ImmutableList<Hook>? hooks, EvaluationContext evaluationContext, | ||
SharedHookContext<T> sharedHookContext, | ||
ILogger? logger = null) | ||
{ | ||
this._evaluationContext = evaluationContext; | ||
this._logger = logger ?? NullLogger<FeatureClient>.Instance; | ||
this._hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); | ||
this._hookContexts = new List<HookContext<T>>(hooks.Count); | ||
for (var i = 0; i < hooks.Count; i++) | ||
{ | ||
this._hookContexts.Add(sharedHookContext.ToHookContext(evaluationContext)); | ||
} | ||
} | ||
|
||
public async Task<EvaluationContext> TriggerBeforeHooksAsync(FlagEvaluationOptions? options, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
var evalContextBuilder = EvaluationContext.Builder(); | ||
evalContextBuilder.Merge(this._evaluationContext); | ||
|
||
for (var i = 0; i < this._hooks.Count; i++) | ||
{ | ||
var hook = this._hooks[i]; | ||
var hookContext = this._hookContexts[i]; | ||
|
||
var resp = await hook.BeforeAsync(hookContext, options?.HookHints, cancellationToken).ConfigureAwait(false); | ||
if (resp != null) | ||
{ | ||
evalContextBuilder.Merge(resp); | ||
this._evaluationContext = evalContextBuilder.Build(); | ||
for (var j = 0; j < this._hookContexts.Count; j++) | ||
{ | ||
this._hookContexts[j] = this._hookContexts[j].WithNewEvaluationContext(this._evaluationContext); | ||
} | ||
} | ||
else | ||
{ | ||
this.HookReturnedNull(hook.GetType().Name); | ||
} | ||
} | ||
|
||
return this._evaluationContext; | ||
} | ||
|
||
public async Task TriggerAfterHooksAsync(FlagEvaluationDetails<T> evaluationDetails, | ||
FlagEvaluationOptions? options, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
// After hooks run in reverse. | ||
for (var i = this._hooks.Count - 1; i >= 0; i--) | ||
{ | ||
var hook = this._hooks[i]; | ||
var hookContext = this._hookContexts[i]; | ||
await hook.AfterAsync(hookContext, evaluationDetails, options?.HookHints, cancellationToken) | ||
.ConfigureAwait(false); | ||
} | ||
} | ||
|
||
public async Task TriggerErrorHooksAsync(Exception exception, | ||
FlagEvaluationOptions? options, CancellationToken cancellationToken = default) | ||
{ | ||
// Error hooks run in reverse. | ||
for (var i = this._hooks.Count - 1; i >= 0; i--) | ||
{ | ||
var hook = this._hooks[i]; | ||
var hookContext = this._hookContexts[i]; | ||
try | ||
{ | ||
await hook.ErrorAsync(hookContext, exception, options?.HookHints, cancellationToken) | ||
.ConfigureAwait(false); | ||
} | ||
catch (Exception e) | ||
{ | ||
this.ErrorHookError(hook.GetType().Name, e); | ||
} | ||
} | ||
} | ||
|
||
public async Task TriggerFinallyHooksAsync(FlagEvaluationDetails<T> evaluation, FlagEvaluationOptions? options, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
// Finally hooks run in reverse | ||
for (var i = this._hooks.Count - 1; i >= 0; i--) | ||
{ | ||
var hook = this._hooks[i]; | ||
var hookContext = this._hookContexts[i]; | ||
try | ||
{ | ||
await hook.FinallyAsync(hookContext, evaluation, options?.HookHints, cancellationToken) | ||
.ConfigureAwait(false); | ||
} | ||
catch (Exception e) | ||
{ | ||
this.FinallyHookError(hook.GetType().Name, e); | ||
} | ||
} | ||
} | ||
|
||
[LoggerMessage(100, LogLevel.Debug, "Hook {HookName} returned null, nothing to merge back into context")] | ||
partial void HookReturnedNull(string hookName); | ||
|
||
[LoggerMessage(103, LogLevel.Error, "Error while executing Error hook {HookName}")] | ||
partial void ErrorHookError(string hookName, Exception exception); | ||
|
||
[LoggerMessage(104, LogLevel.Error, "Error while executing Finally hook {HookName}")] | ||
partial void FinallyHookError(string hookName, Exception exception); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.