Skip to content

Commit 33154d2

Browse files
toddbaertaustindrenskiaskptkinyoklion
authoredJun 17, 2024··
feat!: add CancellationTokens, ValueTasks hooks (#268)
This PR is a combination of #184 and #185. Changes include: - adding cancellation tokens - in all cases where async operations include side-effects (`setProviderAsync`, `InitializeAsync`, I've specified in the in-line doc that the cancellation token's purpose is to cancel such side-effects - so setting a provider and canceling that operation still results in that provider's being set, but async side-effect should be cancelled. I'm interested in feedback here, I think we need to consider the semantics around this... I suppose the alternative would be to always ensure any state changes only occur after async side-effects, if they weren't cancelled beforehand. - adding "Async" suffix to all async methods - remove deprecated sync `SetProvider` methods - Using `ValueTask` for hook methods - I've decided against converting all `Tasks` to `ValueTasks`, from the [official .NET docs](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask?view=net-8.0): > the default choice for any asynchronous method that does not return a result should be to return a [Task](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-8.0). Only if performance analysis proves it worthwhile should a ValueTask be used instead of a [Task](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-8.0). - I think for hooks, `ValueTask` especially makes sense since often hooks are synchronous, in fact async hooks are probably the less likely variant. - I've kept the resolver methods as `Task`, but there could be an argument for making them `ValueTask`, since some providers resolve asynchronously. - I'm still a bit dubious on the entire idea of `ValueTask`, so I'm really interested in feedback here - associated test updates UPDATE: After chewing on this for a night, I'm starting to feel: - We should simply remove cancellation tokens from Init/Shutdown. We can always add them later, which would be non-breaking. I think the value is low and the complexity is potentially high. - ValueTask is only a good idea for hooks, because: - Hooks will very often be synchronous under the hood - We (SDK authors) await the hooks, not consumer code, so we can be careful of the potential pitfalls of ValueTask. I think everywhere else we should stick to Task. --------- Signed-off-by: Austin Drenski <[email protected]> Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Austin Drenski <[email protected]> Co-authored-by: André Silva <[email protected]> Co-authored-by: Ryan Lamb <[email protected]>
1 parent acd0385 commit 33154d2

21 files changed

+1001
-940
lines changed
 

‎README.md

+320-320
Large diffs are not rendered by default.

‎src/OpenFeature/Api.cs

+5-28
Original file line numberDiff line numberDiff line change
@@ -37,40 +37,17 @@ static Api() { }
3737
private Api() { }
3838

3939
/// <summary>
40-
/// Sets the default feature provider to given clientName without awaiting its initialization.
41-
/// </summary>
42-
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect.</remarks>
43-
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
44-
[Obsolete("Will be removed in later versions; use SetProviderAsync, which can be awaited")]
45-
public void SetProvider(FeatureProvider featureProvider)
46-
{
47-
this._eventExecutor.RegisterDefaultFeatureProvider(featureProvider);
48-
_ = this._repository.SetProvider(featureProvider, this.GetContext());
49-
}
50-
51-
/// <summary>
52-
/// Sets the default feature provider. In order to wait for the provider to be set, and initialization to complete,
40+
/// Sets the feature provider. In order to wait for the provider to be set, and initialization to complete,
5341
/// await the returned task.
5442
/// </summary>
5543
/// <remarks>The provider cannot be set to null. Attempting to set the provider to null has no effect.</remarks>
5644
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
57-
public async Task SetProviderAsync(FeatureProvider? featureProvider)
45+
public async Task SetProviderAsync(FeatureProvider featureProvider)
5846
{
5947
this._eventExecutor.RegisterDefaultFeatureProvider(featureProvider);
60-
await this._repository.SetProvider(featureProvider, this.GetContext()).ConfigureAwait(false);
48+
await this._repository.SetProviderAsync(featureProvider, this.GetContext()).ConfigureAwait(false);
6149
}
6250

63-
/// <summary>
64-
/// Sets the feature provider to given clientName without awaiting its initialization.
65-
/// </summary>
66-
/// <param name="clientName">Name of client</param>
67-
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
68-
[Obsolete("Will be removed in later versions; use SetProviderAsync, which can be awaited")]
69-
public void SetProvider(string clientName, FeatureProvider featureProvider)
70-
{
71-
this._eventExecutor.RegisterClientFeatureProvider(clientName, featureProvider);
72-
_ = this._repository.SetProvider(clientName, featureProvider, this.GetContext());
73-
}
7451

7552
/// <summary>
7653
/// Sets the feature provider to given clientName. In order to wait for the provider to be set, and
@@ -85,7 +62,7 @@ public async Task SetProviderAsync(string clientName, FeatureProvider featurePro
8562
throw new ArgumentNullException(nameof(clientName));
8663
}
8764
this._eventExecutor.RegisterClientFeatureProvider(clientName, featureProvider);
88-
await this._repository.SetProvider(clientName, featureProvider, this.GetContext()).ConfigureAwait(false);
65+
await this._repository.SetProviderAsync(clientName, featureProvider, this.GetContext()).ConfigureAwait(false);
8966
}
9067

9168
/// <summary>
@@ -248,7 +225,7 @@ public EvaluationContext GetContext()
248225
/// Once shut down is complete, API is reset and ready to use again.
249226
/// </para>
250227
/// </summary>
251-
public async Task Shutdown()
228+
public async Task ShutdownAsync()
252229
{
253230
await using (this._eventExecutor.ConfigureAwait(false))
254231
await using (this._repository.ConfigureAwait(false))

‎src/OpenFeature/EventExecutor.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
namespace OpenFeature
1212
{
13+
internal delegate Task ShutdownDelegate(CancellationToken cancellationToken);
14+
1315
internal sealed partial class EventExecutor : IAsyncDisposable
1416
{
1517
private readonly object _lockObj = new object();
@@ -30,7 +32,7 @@ public EventExecutor()
3032
eventProcessing.Start();
3133
}
3234

33-
public ValueTask DisposeAsync() => new(this.Shutdown());
35+
public ValueTask DisposeAsync() => new(this.ShutdownAsync());
3436

3537
internal void SetLogger(ILogger logger) => this._logger = logger;
3638

@@ -317,10 +319,9 @@ private void InvokeEventHandler(EventHandlerDelegate eventHandler, Event e)
317319
}
318320
}
319321

320-
public async Task Shutdown()
322+
public async Task ShutdownAsync()
321323
{
322324
this.EventChannel.Writer.Complete();
323-
324325
await this.EventChannel.Reader.Completion.ConfigureAwait(false);
325326
}
326327

‎src/OpenFeature/FeatureProvider.cs

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Immutable;
2+
using System.Threading;
23
using System.Threading.Channels;
34
using System.Threading.Tasks;
45
using OpenFeature.Constant;
@@ -43,49 +44,54 @@ public abstract class FeatureProvider
4344
/// <param name="flagKey">Feature flag key</param>
4445
/// <param name="defaultValue">Default value</param>
4546
/// <param name="context"><see cref="EvaluationContext"/></param>
47+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
4648
/// <returns><see cref="ResolutionDetails{T}"/></returns>
47-
public abstract Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue,
48-
EvaluationContext? context = null);
49+
public abstract Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(string flagKey, bool defaultValue,
50+
EvaluationContext? context = null, CancellationToken cancellationToken = default);
4951

5052
/// <summary>
5153
/// Resolves a string feature flag
5254
/// </summary>
5355
/// <param name="flagKey">Feature flag key</param>
5456
/// <param name="defaultValue">Default value</param>
5557
/// <param name="context"><see cref="EvaluationContext"/></param>
58+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
5659
/// <returns><see cref="ResolutionDetails{T}"/></returns>
57-
public abstract Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue,
58-
EvaluationContext? context = null);
60+
public abstract Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue,
61+
EvaluationContext? context = null, CancellationToken cancellationToken = default);
5962

6063
/// <summary>
6164
/// Resolves a integer feature flag
6265
/// </summary>
6366
/// <param name="flagKey">Feature flag key</param>
6467
/// <param name="defaultValue">Default value</param>
6568
/// <param name="context"><see cref="EvaluationContext"/></param>
69+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
6670
/// <returns><see cref="ResolutionDetails{T}"/></returns>
67-
public abstract Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue,
68-
EvaluationContext? context = null);
71+
public abstract Task<ResolutionDetails<int>> ResolveIntegerValueAsync(string flagKey, int defaultValue,
72+
EvaluationContext? context = null, CancellationToken cancellationToken = default);
6973

7074
/// <summary>
7175
/// Resolves a double feature flag
7276
/// </summary>
7377
/// <param name="flagKey">Feature flag key</param>
7478
/// <param name="defaultValue">Default value</param>
7579
/// <param name="context"><see cref="EvaluationContext"/></param>
80+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
7681
/// <returns><see cref="ResolutionDetails{T}"/></returns>
77-
public abstract Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue,
78-
EvaluationContext? context = null);
82+
public abstract Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue,
83+
EvaluationContext? context = null, CancellationToken cancellationToken = default);
7984

8085
/// <summary>
8186
/// Resolves a structured feature flag
8287
/// </summary>
8388
/// <param name="flagKey">Feature flag key</param>
8489
/// <param name="defaultValue">Default value</param>
8590
/// <param name="context"><see cref="EvaluationContext"/></param>
91+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
8692
/// <returns><see cref="ResolutionDetails{T}"/></returns>
87-
public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
88-
EvaluationContext? context = null);
93+
public abstract Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue,
94+
EvaluationContext? context = null, CancellationToken cancellationToken = default);
8995

9096
/// <summary>
9197
/// Get the status of the provider.
@@ -95,7 +101,7 @@ public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flag
95101
/// If a provider does not override this method, then its status will be assumed to be
96102
/// <see cref="ProviderStatus.Ready"/>. If a provider implements this method, and supports initialization,
97103
/// then it should start in the <see cref="ProviderStatus.NotReady"/>status . If the status is
98-
/// <see cref="ProviderStatus.NotReady"/>, then the Api will call the <see cref="Initialize" /> when the
104+
/// <see cref="ProviderStatus.NotReady"/>, then the Api will call the <see cref="InitializeAsync" /> when the
99105
/// provider is set.
100106
/// </remarks>
101107
public virtual ProviderStatus GetStatus() => ProviderStatus.Ready;
@@ -107,6 +113,7 @@ public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flag
107113
/// </para>
108114
/// </summary>
109115
/// <param name="context"><see cref="EvaluationContext"/></param>
116+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel any async side effects.</param>
110117
/// <returns>A task that completes when the initialization process is complete.</returns>
111118
/// <remarks>
112119
/// <para>
@@ -118,7 +125,7 @@ public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flag
118125
/// the <see cref="GetStatus"/> method after initialization is complete.
119126
/// </para>
120127
/// </remarks>
121-
public virtual Task Initialize(EvaluationContext context)
128+
public virtual Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default)
122129
{
123130
// Intentionally left blank.
124131
return Task.CompletedTask;
@@ -129,7 +136,8 @@ public virtual Task Initialize(EvaluationContext context)
129136
/// Providers can overwrite this method, if they have special shutdown actions needed.
130137
/// </summary>
131138
/// <returns>A task that completes when the shutdown process is complete.</returns>
132-
public virtual Task Shutdown()
139+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel any async side effects.</param>
140+
public virtual Task ShutdownAsync(CancellationToken cancellationToken = default)
133141
{
134142
// Intentionally left blank.
135143
return Task.CompletedTask;

‎src/OpenFeature/Hook.cs

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using OpenFeature.Model;
56

@@ -26,12 +27,13 @@ public abstract class Hook
2627
/// </summary>
2728
/// <param name="context">Provides context of innovation</param>
2829
/// <param name="hints">Caller provided data</param>
30+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
2931
/// <typeparam name="T">Flag value type (bool|number|string|object)</typeparam>
3032
/// <returns>Modified EvaluationContext that is used for the flag evaluation</returns>
31-
public virtual Task<EvaluationContext> Before<T>(HookContext<T> context,
32-
IReadOnlyDictionary<string, object>? hints = null)
33+
public virtual ValueTask<EvaluationContext> BeforeAsync<T>(HookContext<T> context,
34+
IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
3335
{
34-
return Task.FromResult(EvaluationContext.Empty);
36+
return new ValueTask<EvaluationContext>(EvaluationContext.Empty);
3537
}
3638

3739
/// <summary>
@@ -40,11 +42,12 @@ public virtual Task<EvaluationContext> Before<T>(HookContext<T> context,
4042
/// <param name="context">Provides context of innovation</param>
4143
/// <param name="details">Flag evaluation information</param>
4244
/// <param name="hints">Caller provided data</param>
45+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
4346
/// <typeparam name="T">Flag value type (bool|number|string|object)</typeparam>
44-
public virtual Task After<T>(HookContext<T> context, FlagEvaluationDetails<T> details,
45-
IReadOnlyDictionary<string, object>? hints = null)
47+
public virtual ValueTask AfterAsync<T>(HookContext<T> context, FlagEvaluationDetails<T> details,
48+
IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
4649
{
47-
return Task.CompletedTask;
50+
return new ValueTask();
4851
}
4952

5053
/// <summary>
@@ -53,22 +56,24 @@ public virtual Task After<T>(HookContext<T> context, FlagEvaluationDetails<T> de
5356
/// <param name="context">Provides context of innovation</param>
5457
/// <param name="error">Exception representing what went wrong</param>
5558
/// <param name="hints">Caller provided data</param>
59+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
5660
/// <typeparam name="T">Flag value type (bool|number|string|object)</typeparam>
57-
public virtual Task Error<T>(HookContext<T> context, Exception error,
58-
IReadOnlyDictionary<string, object>? hints = null)
61+
public virtual ValueTask ErrorAsync<T>(HookContext<T> context, Exception error,
62+
IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
5963
{
60-
return Task.CompletedTask;
64+
return new ValueTask();
6165
}
6266

6367
/// <summary>
6468
/// Called unconditionally after flag evaluation.
6569
/// </summary>
6670
/// <param name="context">Provides context of innovation</param>
6771
/// <param name="hints">Caller provided data</param>
72+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
6873
/// <typeparam name="T">Flag value type (bool|number|string|object)</typeparam>
69-
public virtual Task Finally<T>(HookContext<T> context, IReadOnlyDictionary<string, object>? hints = null)
74+
public virtual ValueTask FinallyAsync<T>(HookContext<T> context, IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
7075
{
71-
return Task.CompletedTask;
76+
return new ValueTask();
7277
}
7378
}
7479
}

‎src/OpenFeature/IFeatureClient.cs

+21-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using OpenFeature.Model;
45

@@ -59,8 +60,9 @@ public interface IFeatureClient : IEventBus
5960
/// <param name="defaultValue">Default value</param>
6061
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
6162
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
63+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
6264
/// <returns>Resolved flag value.</returns>
63-
Task<bool> GetBooleanValue(string flagKey, bool defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
65+
Task<bool> GetBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
6466

6567
/// <summary>
6668
/// Resolves a boolean feature flag
@@ -69,8 +71,9 @@ public interface IFeatureClient : IEventBus
6971
/// <param name="defaultValue">Default value</param>
7072
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
7173
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
74+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
7275
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
73-
Task<FlagEvaluationDetails<bool>> GetBooleanDetails(string flagKey, bool defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
76+
Task<FlagEvaluationDetails<bool>> GetBooleanDetailsAsync(string flagKey, bool defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
7477

7578
/// <summary>
7679
/// Resolves a string feature flag
@@ -79,8 +82,9 @@ public interface IFeatureClient : IEventBus
7982
/// <param name="defaultValue">Default value</param>
8083
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
8184
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
85+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
8286
/// <returns>Resolved flag value.</returns>
83-
Task<string> GetStringValue(string flagKey, string defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
87+
Task<string> GetStringValueAsync(string flagKey, string defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
8488

8589
/// <summary>
8690
/// Resolves a string feature flag
@@ -89,8 +93,9 @@ public interface IFeatureClient : IEventBus
8993
/// <param name="defaultValue">Default value</param>
9094
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
9195
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
96+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
9297
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
93-
Task<FlagEvaluationDetails<string>> GetStringDetails(string flagKey, string defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
98+
Task<FlagEvaluationDetails<string>> GetStringDetailsAsync(string flagKey, string defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
9499

95100
/// <summary>
96101
/// Resolves a integer feature flag
@@ -99,8 +104,9 @@ public interface IFeatureClient : IEventBus
99104
/// <param name="defaultValue">Default value</param>
100105
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
101106
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
107+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
102108
/// <returns>Resolved flag value.</returns>
103-
Task<int> GetIntegerValue(string flagKey, int defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
109+
Task<int> GetIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
104110

105111
/// <summary>
106112
/// Resolves a integer feature flag
@@ -109,8 +115,9 @@ public interface IFeatureClient : IEventBus
109115
/// <param name="defaultValue">Default value</param>
110116
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
111117
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
118+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
112119
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
113-
Task<FlagEvaluationDetails<int>> GetIntegerDetails(string flagKey, int defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
120+
Task<FlagEvaluationDetails<int>> GetIntegerDetailsAsync(string flagKey, int defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
114121

115122
/// <summary>
116123
/// Resolves a double feature flag
@@ -119,8 +126,9 @@ public interface IFeatureClient : IEventBus
119126
/// <param name="defaultValue">Default value</param>
120127
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
121128
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
129+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
122130
/// <returns>Resolved flag value.</returns>
123-
Task<double> GetDoubleValue(string flagKey, double defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
131+
Task<double> GetDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
124132

125133
/// <summary>
126134
/// Resolves a double feature flag
@@ -129,8 +137,9 @@ public interface IFeatureClient : IEventBus
129137
/// <param name="defaultValue">Default value</param>
130138
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
131139
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
140+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
132141
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
133-
Task<FlagEvaluationDetails<double>> GetDoubleDetails(string flagKey, double defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
142+
Task<FlagEvaluationDetails<double>> GetDoubleDetailsAsync(string flagKey, double defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
134143

135144
/// <summary>
136145
/// Resolves a structure object feature flag
@@ -139,8 +148,9 @@ public interface IFeatureClient : IEventBus
139148
/// <param name="defaultValue">Default value</param>
140149
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
141150
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
151+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
142152
/// <returns>Resolved flag value.</returns>
143-
Task<Value> GetObjectValue(string flagKey, Value defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
153+
Task<Value> GetObjectValueAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
144154

145155
/// <summary>
146156
/// Resolves a structure object feature flag
@@ -149,7 +159,8 @@ public interface IFeatureClient : IEventBus
149159
/// <param name="defaultValue">Default value</param>
150160
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
151161
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
162+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
152163
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
153-
Task<FlagEvaluationDetails<Value>> GetObjectDetails(string flagKey, Value defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null);
164+
Task<FlagEvaluationDetails<Value>> GetObjectDetailsAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, FlagEvaluationOptions? config = null, CancellationToken cancellationToken = default);
154165
}
155166
}

‎src/OpenFeature/NoOpProvider.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Threading;
12
using System.Threading.Tasks;
23
using OpenFeature.Constant;
34
using OpenFeature.Model;
@@ -13,27 +14,27 @@ public override Metadata GetMetadata()
1314
return this._metadata;
1415
}
1516

16-
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext? context = null)
17+
public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
1718
{
1819
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
1920
}
2021

21-
public override Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext? context = null)
22+
public override Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
2223
{
2324
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
2425
}
2526

26-
public override Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext? context = null)
27+
public override Task<ResolutionDetails<int>> ResolveIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
2728
{
2829
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
2930
}
3031

31-
public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext? context = null)
32+
public override Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
3233
{
3334
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
3435
}
3536

36-
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext? context = null)
37+
public override Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
3738
{
3839
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
3940
}

‎src/OpenFeature/OpenFeatureClient.cs

+60-58
Large diffs are not rendered by default.

‎src/OpenFeature/ProviderRepository.cs

+18-16
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public async ValueTask DisposeAsync()
3535
{
3636
using (this._providersLock)
3737
{
38-
await this.Shutdown().ConfigureAwait(false);
38+
await this.ShutdownAsync().ConfigureAwait(false);
3939
}
4040
}
4141

@@ -62,7 +62,7 @@ public async ValueTask DisposeAsync()
6262
/// initialization
6363
/// </param>
6464
/// <param name="afterShutdown">called after a provider is shutdown, can be used to remove event handlers</param>
65-
public async Task SetProvider(
65+
public async Task SetProviderAsync(
6666
FeatureProvider? featureProvider,
6767
EvaluationContext context,
6868
Action<FeatureProvider>? afterSet = null,
@@ -92,19 +92,19 @@ public async Task SetProvider(
9292
// We want to allow shutdown to happen concurrently with initialization, and the caller to not
9393
// wait for it.
9494
#pragma warning disable CS4014
95-
this.ShutdownIfUnused(oldProvider, afterShutdown, afterError);
95+
this.ShutdownIfUnusedAsync(oldProvider, afterShutdown, afterError);
9696
#pragma warning restore CS4014
9797
}
9898
finally
9999
{
100100
this._providersLock.ExitWriteLock();
101101
}
102102

103-
await InitProvider(this._defaultProvider, context, afterInitialization, afterError)
103+
await InitProviderAsync(this._defaultProvider, context, afterInitialization, afterError)
104104
.ConfigureAwait(false);
105105
}
106106

107-
private static async Task InitProvider(
107+
private static async Task InitProviderAsync(
108108
FeatureProvider? newProvider,
109109
EvaluationContext context,
110110
Action<FeatureProvider>? afterInitialization,
@@ -118,7 +118,7 @@ private static async Task InitProvider(
118118
{
119119
try
120120
{
121-
await newProvider.Initialize(context).ConfigureAwait(false);
121+
await newProvider.InitializeAsync(context).ConfigureAwait(false);
122122
afterInitialization?.Invoke(newProvider);
123123
}
124124
catch (Exception ex)
@@ -152,13 +152,15 @@ private static async Task InitProvider(
152152
/// initialization
153153
/// </param>
154154
/// <param name="afterShutdown">called after a provider is shutdown, can be used to remove event handlers</param>
155-
public async Task SetProvider(string? clientName,
155+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel any async side effects.</param>
156+
public async Task SetProviderAsync(string clientName,
156157
FeatureProvider? featureProvider,
157158
EvaluationContext context,
158159
Action<FeatureProvider>? afterSet = null,
159160
Action<FeatureProvider>? afterInitialization = null,
160161
Action<FeatureProvider, Exception>? afterError = null,
161-
Action<FeatureProvider>? afterShutdown = null)
162+
Action<FeatureProvider>? afterShutdown = null,
163+
CancellationToken cancellationToken = default)
162164
{
163165
// Cannot set a provider for a null clientName.
164166
if (clientName == null)
@@ -187,21 +189,21 @@ public async Task SetProvider(string? clientName,
187189
// We want to allow shutdown to happen concurrently with initialization, and the caller to not
188190
// wait for it.
189191
#pragma warning disable CS4014
190-
this.ShutdownIfUnused(oldProvider, afterShutdown, afterError);
192+
this.ShutdownIfUnusedAsync(oldProvider, afterShutdown, afterError);
191193
#pragma warning restore CS4014
192194
}
193195
finally
194196
{
195197
this._providersLock.ExitWriteLock();
196198
}
197199

198-
await InitProvider(featureProvider, context, afterInitialization, afterError).ConfigureAwait(false);
200+
await InitProviderAsync(featureProvider, context, afterInitialization, afterError).ConfigureAwait(false);
199201
}
200202

201203
/// <remarks>
202204
/// Shutdown the feature provider if it is unused. This must be called within a write lock of the _providersLock.
203205
/// </remarks>
204-
private async Task ShutdownIfUnused(
206+
private async Task ShutdownIfUnusedAsync(
205207
FeatureProvider? targetProvider,
206208
Action<FeatureProvider>? afterShutdown,
207209
Action<FeatureProvider, Exception>? afterError)
@@ -216,7 +218,7 @@ private async Task ShutdownIfUnused(
216218
return;
217219
}
218220

219-
await SafeShutdownProvider(targetProvider, afterShutdown, afterError).ConfigureAwait(false);
221+
await SafeShutdownProviderAsync(targetProvider, afterShutdown, afterError).ConfigureAwait(false);
220222
}
221223

222224
/// <remarks>
@@ -228,7 +230,7 @@ private async Task ShutdownIfUnused(
228230
/// it would not be meaningful to emit an error.
229231
/// </para>
230232
/// </remarks>
231-
private static async Task SafeShutdownProvider(FeatureProvider? targetProvider,
233+
private static async Task SafeShutdownProviderAsync(FeatureProvider? targetProvider,
232234
Action<FeatureProvider>? afterShutdown,
233235
Action<FeatureProvider, Exception>? afterError)
234236
{
@@ -239,7 +241,7 @@ private static async Task SafeShutdownProvider(FeatureProvider? targetProvider,
239241

240242
try
241243
{
242-
await targetProvider.Shutdown().ConfigureAwait(false);
244+
await targetProvider.ShutdownAsync().ConfigureAwait(false);
243245
afterShutdown?.Invoke(targetProvider);
244246
}
245247
catch (Exception ex)
@@ -281,7 +283,7 @@ public FeatureProvider GetProvider(string? clientName)
281283
: this.GetProvider();
282284
}
283285

284-
public async Task Shutdown(Action<FeatureProvider, Exception>? afterError = null)
286+
public async Task ShutdownAsync(Action<FeatureProvider, Exception>? afterError = null, CancellationToken cancellationToken = default)
285287
{
286288
var providers = new HashSet<FeatureProvider>();
287289
this._providersLock.EnterWriteLock();
@@ -305,7 +307,7 @@ public async Task Shutdown(Action<FeatureProvider, Exception>? afterError = null
305307
foreach (var targetProvider in providers)
306308
{
307309
// We don't need to take any actions after shutdown.
308-
await SafeShutdownProvider(targetProvider, null, afterError).ConfigureAwait(false);
310+
await SafeShutdownProviderAsync(targetProvider, null, afterError).ConfigureAwait(false);
309311
}
310312
}
311313
}

‎src/OpenFeature/Providers/Memory/InMemoryProvider.cs

+7-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using OpenFeature.Constant;
56
using OpenFeature.Error;
@@ -45,7 +46,7 @@ public InMemoryProvider(IDictionary<string, Flag>? flags = null)
4546
/// Updating provider flags configuration, replacing all flags.
4647
/// </summary>
4748
/// <param name="flags">the flags to use instead of the previous flags.</param>
48-
public async ValueTask UpdateFlags(IDictionary<string, Flag>? flags = null)
49+
public async Task UpdateFlags(IDictionary<string, Flag>? flags = null)
4950
{
5051
var changed = this._flags.Keys.ToList();
5152
if (flags == null)
@@ -68,46 +69,31 @@ public async ValueTask UpdateFlags(IDictionary<string, Flag>? flags = null)
6869
}
6970

7071
/// <inheritdoc/>
71-
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
72-
string flagKey,
73-
bool defaultValue,
74-
EvaluationContext? context = null)
72+
public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
7573
{
7674
return Task.FromResult(Resolve(flagKey, defaultValue, context));
7775
}
7876

7977
/// <inheritdoc/>
80-
public override Task<ResolutionDetails<string>> ResolveStringValue(
81-
string flagKey,
82-
string defaultValue,
83-
EvaluationContext? context = null)
78+
public override Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
8479
{
8580
return Task.FromResult(Resolve(flagKey, defaultValue, context));
8681
}
8782

8883
/// <inheritdoc/>
89-
public override Task<ResolutionDetails<int>> ResolveIntegerValue(
90-
string flagKey,
91-
int defaultValue,
92-
EvaluationContext? context = null)
84+
public override Task<ResolutionDetails<int>> ResolveIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
9385
{
9486
return Task.FromResult(Resolve(flagKey, defaultValue, context));
9587
}
9688

9789
/// <inheritdoc/>
98-
public override Task<ResolutionDetails<double>> ResolveDoubleValue(
99-
string flagKey,
100-
double defaultValue,
101-
EvaluationContext? context = null)
90+
public override Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
10291
{
10392
return Task.FromResult(Resolve(flagKey, defaultValue, context));
10493
}
10594

10695
/// <inheritdoc/>
107-
public override Task<ResolutionDetails<Value>> ResolveStructureValue(
108-
string flagKey,
109-
Value defaultValue,
110-
EvaluationContext? context = null)
96+
public override Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default)
11197
{
11298
return Task.FromResult(Resolve(flagKey, defaultValue, context));
11399
}

‎test/OpenFeature.Benchmarks/OpenFeatureClientBenchmarks.cs

+15-15
Original file line numberDiff line numberDiff line change
@@ -45,62 +45,62 @@ public OpenFeatureClientBenchmarks()
4545

4646
[Benchmark]
4747
public async Task OpenFeatureClient_GetBooleanValue_WithoutEvaluationContext_WithoutFlagEvaluationOptions() =>
48-
await _client.GetBooleanValue(_flagName, _defaultBoolValue);
48+
await _client.GetBooleanValueAsync(_flagName, _defaultBoolValue);
4949

5050
[Benchmark]
5151
public async Task OpenFeatureClient_GetBooleanValue_WithEmptyEvaluationContext_WithoutFlagEvaluationOptions() =>
52-
await _client.GetBooleanValue(_flagName, _defaultBoolValue, EvaluationContext.Empty);
52+
await _client.GetBooleanValueAsync(_flagName, _defaultBoolValue, EvaluationContext.Empty);
5353

5454
[Benchmark]
5555
public async Task OpenFeatureClient_GetBooleanValue_WithEmptyEvaluationContext_WithEmptyFlagEvaluationOptions() =>
56-
await _client.GetBooleanValue(_flagName, _defaultBoolValue, EvaluationContext.Empty, _emptyFlagOptions);
56+
await _client.GetBooleanValueAsync(_flagName, _defaultBoolValue, EvaluationContext.Empty, _emptyFlagOptions);
5757

5858
[Benchmark]
5959
public async Task OpenFeatureClient_GetStringValue_WithoutEvaluationContext_WithoutFlagEvaluationOptions() =>
60-
await _client.GetStringValue(_flagName, _defaultStringValue);
60+
await _client.GetStringValueAsync(_flagName, _defaultStringValue);
6161

6262
[Benchmark]
6363
public async Task OpenFeatureClient_GetStringValue_WithEmptyEvaluationContext_WithoutFlagEvaluationOptions() =>
64-
await _client.GetStringValue(_flagName, _defaultStringValue, EvaluationContext.Empty);
64+
await _client.GetStringValueAsync(_flagName, _defaultStringValue, EvaluationContext.Empty);
6565

6666
[Benchmark]
6767
public async Task OpenFeatureClient_GetStringValue_WithoutEvaluationContext_WithEmptyFlagEvaluationOptions() =>
68-
await _client.GetStringValue(_flagName, _defaultStringValue, EvaluationContext.Empty, _emptyFlagOptions);
68+
await _client.GetStringValueAsync(_flagName, _defaultStringValue, EvaluationContext.Empty, _emptyFlagOptions);
6969

7070
[Benchmark]
7171
public async Task OpenFeatureClient_GetIntegerValue_WithoutEvaluationContext_WithoutFlagEvaluationOptions() =>
72-
await _client.GetIntegerValue(_flagName, _defaultIntegerValue);
72+
await _client.GetIntegerValueAsync(_flagName, _defaultIntegerValue);
7373

7474
[Benchmark]
7575
public async Task OpenFeatureClient_GetIntegerValue_WithEmptyEvaluationContext_WithoutFlagEvaluationOptions() =>
76-
await _client.GetIntegerValue(_flagName, _defaultIntegerValue, EvaluationContext.Empty);
76+
await _client.GetIntegerValueAsync(_flagName, _defaultIntegerValue, EvaluationContext.Empty);
7777

7878
[Benchmark]
7979
public async Task OpenFeatureClient_GetIntegerValue_WithEmptyEvaluationContext_WithEmptyFlagEvaluationOptions() =>
80-
await _client.GetIntegerValue(_flagName, _defaultIntegerValue, EvaluationContext.Empty, _emptyFlagOptions);
80+
await _client.GetIntegerValueAsync(_flagName, _defaultIntegerValue, EvaluationContext.Empty, _emptyFlagOptions);
8181

8282
[Benchmark]
8383
public async Task OpenFeatureClient_GetDoubleValue_WithoutEvaluationContext_WithoutFlagEvaluationOptions() =>
84-
await _client.GetDoubleValue(_flagName, _defaultDoubleValue);
84+
await _client.GetDoubleValueAsync(_flagName, _defaultDoubleValue);
8585

8686
[Benchmark]
8787
public async Task OpenFeatureClient_GetDoubleValue_WithEmptyEvaluationContext_WithoutFlagEvaluationOptions() =>
88-
await _client.GetDoubleValue(_flagName, _defaultDoubleValue, EvaluationContext.Empty);
88+
await _client.GetDoubleValueAsync(_flagName, _defaultDoubleValue, EvaluationContext.Empty);
8989

9090
[Benchmark]
9191
public async Task OpenFeatureClient_GetDoubleValue_WithEmptyEvaluationContext_WithEmptyFlagEvaluationOptions() =>
92-
await _client.GetDoubleValue(_flagName, _defaultDoubleValue, EvaluationContext.Empty, _emptyFlagOptions);
92+
await _client.GetDoubleValueAsync(_flagName, _defaultDoubleValue, EvaluationContext.Empty, _emptyFlagOptions);
9393

9494
[Benchmark]
9595
public async Task OpenFeatureClient_GetObjectValue_WithoutEvaluationContext_WithoutFlagEvaluationOptions() =>
96-
await _client.GetObjectValue(_flagName, _defaultStructureValue);
96+
await _client.GetObjectValueAsync(_flagName, _defaultStructureValue);
9797

9898
[Benchmark]
9999
public async Task OpenFeatureClient_GetObjectValue_WithEmptyEvaluationContext_WithoutFlagEvaluationOptions() =>
100-
await _client.GetObjectValue(_flagName, _defaultStructureValue, EvaluationContext.Empty);
100+
await _client.GetObjectValueAsync(_flagName, _defaultStructureValue, EvaluationContext.Empty);
101101

102102
[Benchmark]
103103
public async Task OpenFeatureClient_GetObjectValue_WithEmptyEvaluationContext_WithEmptyFlagEvaluationOptions() =>
104-
await _client.GetObjectValue(_flagName, _defaultStructureValue, EvaluationContext.Empty, _emptyFlagOptions);
104+
await _client.GetObjectValueAsync(_flagName, _defaultStructureValue, EvaluationContext.Empty, _emptyFlagOptions);
105105
}
106106
}

‎test/OpenFeature.E2ETests/Steps/EvaluationStepDefinitions.cs

+14-14
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void GivenAProviderIsRegistered()
4949
[When(@"a boolean flag with key ""(.*)"" is evaluated with default value ""(.*)""")]
5050
public void Whenabooleanflagwithkeyisevaluatedwithdefaultvalue(string flagKey, bool defaultValue)
5151
{
52-
this.booleanFlagValue = client?.GetBooleanValue(flagKey, defaultValue);
52+
this.booleanFlagValue = client?.GetBooleanValueAsync(flagKey, defaultValue);
5353
}
5454

5555
[Then(@"the resolved boolean value should be ""(.*)""")]
@@ -61,7 +61,7 @@ public void Thentheresolvedbooleanvalueshouldbe(bool expectedValue)
6161
[When(@"a string flag with key ""(.*)"" is evaluated with default value ""(.*)""")]
6262
public void Whenastringflagwithkeyisevaluatedwithdefaultvalue(string flagKey, string defaultValue)
6363
{
64-
this.stringFlagValue = client?.GetStringValue(flagKey, defaultValue);
64+
this.stringFlagValue = client?.GetStringValueAsync(flagKey, defaultValue);
6565
}
6666

6767
[Then(@"the resolved string value should be ""(.*)""")]
@@ -73,7 +73,7 @@ public void Thentheresolvedstringvalueshouldbe(string expected)
7373
[When(@"an integer flag with key ""(.*)"" is evaluated with default value (.*)")]
7474
public void Whenanintegerflagwithkeyisevaluatedwithdefaultvalue(string flagKey, int defaultValue)
7575
{
76-
this.intFlagValue = client?.GetIntegerValue(flagKey, defaultValue);
76+
this.intFlagValue = client?.GetIntegerValueAsync(flagKey, defaultValue);
7777
}
7878

7979
[Then(@"the resolved integer value should be (.*)")]
@@ -85,7 +85,7 @@ public void Thentheresolvedintegervalueshouldbe(int expected)
8585
[When(@"a float flag with key ""(.*)"" is evaluated with default value (.*)")]
8686
public void Whenafloatflagwithkeyisevaluatedwithdefaultvalue(string flagKey, double defaultValue)
8787
{
88-
this.doubleFlagValue = client?.GetDoubleValue(flagKey, defaultValue);
88+
this.doubleFlagValue = client?.GetDoubleValueAsync(flagKey, defaultValue);
8989
}
9090

9191
[Then(@"the resolved float value should be (.*)")]
@@ -97,7 +97,7 @@ public void Thentheresolvedfloatvalueshouldbe(double expected)
9797
[When(@"an object flag with key ""(.*)"" is evaluated with a null default value")]
9898
public void Whenanobjectflagwithkeyisevaluatedwithanulldefaultvalue(string flagKey)
9999
{
100-
this.objectFlagValue = client?.GetObjectValue(flagKey, new Value());
100+
this.objectFlagValue = client?.GetObjectValueAsync(flagKey, new Value());
101101
}
102102

103103
[Then(@"the resolved object value should be contain fields ""(.*)"", ""(.*)"", and ""(.*)"", with values ""(.*)"", ""(.*)"" and (.*), respectively")]
@@ -112,7 +112,7 @@ public void Thentheresolvedobjectvalueshouldbecontainfieldsandwithvaluesandrespe
112112
[When(@"a boolean flag with key ""(.*)"" is evaluated with details and default value ""(.*)""")]
113113
public void Whenabooleanflagwithkeyisevaluatedwithdetailsanddefaultvalue(string flagKey, bool defaultValue)
114114
{
115-
this.booleanFlagDetails = client?.GetBooleanDetails(flagKey, defaultValue);
115+
this.booleanFlagDetails = client?.GetBooleanDetailsAsync(flagKey, defaultValue);
116116
}
117117

118118
[Then(@"the resolved boolean details value should be ""(.*)"", the variant should be ""(.*)"", and the reason should be ""(.*)""")]
@@ -127,7 +127,7 @@ public void Thentheresolvedbooleandetailsvalueshouldbethevariantshouldbeandthere
127127
[When(@"a string flag with key ""(.*)"" is evaluated with details and default value ""(.*)""")]
128128
public void Whenastringflagwithkeyisevaluatedwithdetailsanddefaultvalue(string flagKey, string defaultValue)
129129
{
130-
this.stringFlagDetails = client?.GetStringDetails(flagKey, defaultValue);
130+
this.stringFlagDetails = client?.GetStringDetailsAsync(flagKey, defaultValue);
131131
}
132132

133133
[Then(@"the resolved string details value should be ""(.*)"", the variant should be ""(.*)"", and the reason should be ""(.*)""")]
@@ -142,7 +142,7 @@ public void Thentheresolvedstringdetailsvalueshouldbethevariantshouldbeandtherea
142142
[When(@"an integer flag with key ""(.*)"" is evaluated with details and default value (.*)")]
143143
public void Whenanintegerflagwithkeyisevaluatedwithdetailsanddefaultvalue(string flagKey, int defaultValue)
144144
{
145-
this.intFlagDetails = client?.GetIntegerDetails(flagKey, defaultValue);
145+
this.intFlagDetails = client?.GetIntegerDetailsAsync(flagKey, defaultValue);
146146
}
147147

148148
[Then(@"the resolved integer details value should be (.*), the variant should be ""(.*)"", and the reason should be ""(.*)""")]
@@ -157,7 +157,7 @@ public void Thentheresolvedintegerdetailsvalueshouldbethevariantshouldbeandthere
157157
[When(@"a float flag with key ""(.*)"" is evaluated with details and default value (.*)")]
158158
public void Whenafloatflagwithkeyisevaluatedwithdetailsanddefaultvalue(string flagKey, double defaultValue)
159159
{
160-
this.doubleFlagDetails = client?.GetDoubleDetails(flagKey, defaultValue);
160+
this.doubleFlagDetails = client?.GetDoubleDetailsAsync(flagKey, defaultValue);
161161
}
162162

163163
[Then(@"the resolved float details value should be (.*), the variant should be ""(.*)"", and the reason should be ""(.*)""")]
@@ -172,7 +172,7 @@ public void Thentheresolvedfloatdetailsvalueshouldbethevariantshouldbeandthereas
172172
[When(@"an object flag with key ""(.*)"" is evaluated with details and a null default value")]
173173
public void Whenanobjectflagwithkeyisevaluatedwithdetailsandanulldefaultvalue(string flagKey)
174174
{
175-
this.objectFlagDetails = client?.GetObjectDetails(flagKey, new Value());
175+
this.objectFlagDetails = client?.GetObjectDetailsAsync(flagKey, new Value());
176176
}
177177

178178
[Then(@"the resolved object details value should be contain fields ""(.*)"", ""(.*)"", and ""(.*)"", with values ""(.*)"", ""(.*)"" and (.*), respectively")]
@@ -206,7 +206,7 @@ public void Givenaflagwithkeyisevaluatedwithdefaultvalue(string flagKey, string
206206
{
207207
contextAwareFlagKey = flagKey;
208208
contextAwareDefaultValue = defaultValue;
209-
contextAwareValue = client?.GetStringValue(flagKey, contextAwareDefaultValue, context)?.Result;
209+
contextAwareValue = client?.GetStringValueAsync(flagKey, contextAwareDefaultValue, context)?.Result;
210210
}
211211

212212
[Then(@"the resolved string response should be ""(.*)""")]
@@ -218,7 +218,7 @@ public void Thentheresolvedstringresponseshouldbe(string expected)
218218
[Then(@"the resolved flag value is ""(.*)"" when the context is empty")]
219219
public void Giventheresolvedflagvalueiswhenthecontextisempty(string expected)
220220
{
221-
string? emptyContextValue = client?.GetStringValue(contextAwareFlagKey!, contextAwareDefaultValue!, new EvaluationContextBuilder().Build()).Result;
221+
string? emptyContextValue = client?.GetStringValueAsync(contextAwareFlagKey!, contextAwareDefaultValue!, EvaluationContext.Empty).Result;
222222
Assert.Equal(expected, emptyContextValue);
223223
}
224224

@@ -227,7 +227,7 @@ public void Whenanonexistentstringflagwithkeyisevaluatedwithdetailsandadefaultva
227227
{
228228
this.notFoundFlagKey = flagKey;
229229
this.notFoundDefaultValue = defaultValue;
230-
this.notFoundDetails = client?.GetStringDetails(this.notFoundFlagKey, this.notFoundDefaultValue).Result;
230+
this.notFoundDetails = client?.GetStringDetailsAsync(this.notFoundFlagKey, this.notFoundDefaultValue).Result;
231231
}
232232

233233
[Then(@"the default string value should be returned")]
@@ -248,7 +248,7 @@ public void Whenastringflagwithkeyisevaluatedasanintegerwithdetailsandadefaultva
248248
{
249249
this.typeErrorFlagKey = flagKey;
250250
this.typeErrorDefaultValue = defaultValue;
251-
this.typeErrorDetails = client?.GetIntegerDetails(this.typeErrorFlagKey, this.typeErrorDefaultValue).Result;
251+
this.typeErrorDetails = client?.GetIntegerDetailsAsync(this.typeErrorFlagKey, this.typeErrorDefaultValue).Result;
252252
}
253253

254254
[Then(@"the default integer value should be returned")]

‎test/OpenFeature.Tests/FeatureProviderTests.cs

+19-19
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,27 @@ public async Task Provider_Must_Resolve_Flag_Values()
4444

4545
var boolResolutionDetails = new ResolutionDetails<bool>(flagName, defaultBoolValue, ErrorType.None,
4646
NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
47-
(await provider.ResolveBooleanValue(flagName, defaultBoolValue)).Should()
47+
(await provider.ResolveBooleanValueAsync(flagName, defaultBoolValue)).Should()
4848
.BeEquivalentTo(boolResolutionDetails);
4949

5050
var integerResolutionDetails = new ResolutionDetails<int>(flagName, defaultIntegerValue, ErrorType.None,
5151
NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
52-
(await provider.ResolveIntegerValue(flagName, defaultIntegerValue)).Should()
52+
(await provider.ResolveIntegerValueAsync(flagName, defaultIntegerValue)).Should()
5353
.BeEquivalentTo(integerResolutionDetails);
5454

5555
var doubleResolutionDetails = new ResolutionDetails<double>(flagName, defaultDoubleValue, ErrorType.None,
5656
NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
57-
(await provider.ResolveDoubleValue(flagName, defaultDoubleValue)).Should()
57+
(await provider.ResolveDoubleValueAsync(flagName, defaultDoubleValue)).Should()
5858
.BeEquivalentTo(doubleResolutionDetails);
5959

6060
var stringResolutionDetails = new ResolutionDetails<string>(flagName, defaultStringValue, ErrorType.None,
6161
NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
62-
(await provider.ResolveStringValue(flagName, defaultStringValue)).Should()
62+
(await provider.ResolveStringValueAsync(flagName, defaultStringValue)).Should()
6363
.BeEquivalentTo(stringResolutionDetails);
6464

6565
var structureResolutionDetails = new ResolutionDetails<Value>(flagName, defaultStructureValue,
6666
ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
67-
(await provider.ResolveStructureValue(flagName, defaultStructureValue)).Should()
67+
(await provider.ResolveStructureValueAsync(flagName, defaultStructureValue)).Should()
6868
.BeEquivalentTo(structureResolutionDetails);
6969
}
7070

@@ -84,59 +84,59 @@ public async Task Provider_Must_ErrorType()
8484
var providerMock = Substitute.For<FeatureProvider>();
8585
const string testMessage = "An error message";
8686

87-
providerMock.ResolveBooleanValue(flagName, defaultBoolValue, Arg.Any<EvaluationContext>())
87+
providerMock.ResolveBooleanValueAsync(flagName, defaultBoolValue, Arg.Any<EvaluationContext>())
8888
.Returns(new ResolutionDetails<bool>(flagName, defaultBoolValue, ErrorType.General,
8989
NoOpProvider.ReasonNoOp, NoOpProvider.Variant, testMessage));
9090

91-
providerMock.ResolveIntegerValue(flagName, defaultIntegerValue, Arg.Any<EvaluationContext>())
91+
providerMock.ResolveIntegerValueAsync(flagName, defaultIntegerValue, Arg.Any<EvaluationContext>())
9292
.Returns(new ResolutionDetails<int>(flagName, defaultIntegerValue, ErrorType.ParseError,
9393
NoOpProvider.ReasonNoOp, NoOpProvider.Variant, testMessage));
9494

95-
providerMock.ResolveDoubleValue(flagName, defaultDoubleValue, Arg.Any<EvaluationContext>())
95+
providerMock.ResolveDoubleValueAsync(flagName, defaultDoubleValue, Arg.Any<EvaluationContext>())
9696
.Returns(new ResolutionDetails<double>(flagName, defaultDoubleValue, ErrorType.InvalidContext,
9797
NoOpProvider.ReasonNoOp, NoOpProvider.Variant, testMessage));
9898

99-
providerMock.ResolveStringValue(flagName, defaultStringValue, Arg.Any<EvaluationContext>())
99+
providerMock.ResolveStringValueAsync(flagName, defaultStringValue, Arg.Any<EvaluationContext>())
100100
.Returns(new ResolutionDetails<string>(flagName, defaultStringValue, ErrorType.TypeMismatch,
101101
NoOpProvider.ReasonNoOp, NoOpProvider.Variant, testMessage));
102102

103-
providerMock.ResolveStructureValue(flagName, defaultStructureValue, Arg.Any<EvaluationContext>())
103+
providerMock.ResolveStructureValueAsync(flagName, defaultStructureValue, Arg.Any<EvaluationContext>())
104104
.Returns(new ResolutionDetails<Value>(flagName, defaultStructureValue, ErrorType.FlagNotFound,
105105
NoOpProvider.ReasonNoOp, NoOpProvider.Variant, testMessage));
106106

107-
providerMock.ResolveStructureValue(flagName2, defaultStructureValue, Arg.Any<EvaluationContext>())
107+
providerMock.ResolveStructureValueAsync(flagName2, defaultStructureValue, Arg.Any<EvaluationContext>())
108108
.Returns(new ResolutionDetails<Value>(flagName2, defaultStructureValue, ErrorType.ProviderNotReady,
109109
NoOpProvider.ReasonNoOp, NoOpProvider.Variant, testMessage));
110110

111-
providerMock.ResolveBooleanValue(flagName2, defaultBoolValue, Arg.Any<EvaluationContext>())
111+
providerMock.ResolveBooleanValueAsync(flagName2, defaultBoolValue, Arg.Any<EvaluationContext>())
112112
.Returns(new ResolutionDetails<bool>(flagName2, defaultBoolValue, ErrorType.TargetingKeyMissing,
113113
NoOpProvider.ReasonNoOp, NoOpProvider.Variant));
114114

115-
var boolRes = await providerMock.ResolveBooleanValue(flagName, defaultBoolValue);
115+
var boolRes = await providerMock.ResolveBooleanValueAsync(flagName, defaultBoolValue);
116116
boolRes.ErrorType.Should().Be(ErrorType.General);
117117
boolRes.ErrorMessage.Should().Be(testMessage);
118118

119-
var intRes = await providerMock.ResolveIntegerValue(flagName, defaultIntegerValue);
119+
var intRes = await providerMock.ResolveIntegerValueAsync(flagName, defaultIntegerValue);
120120
intRes.ErrorType.Should().Be(ErrorType.ParseError);
121121
intRes.ErrorMessage.Should().Be(testMessage);
122122

123-
var doubleRes = await providerMock.ResolveDoubleValue(flagName, defaultDoubleValue);
123+
var doubleRes = await providerMock.ResolveDoubleValueAsync(flagName, defaultDoubleValue);
124124
doubleRes.ErrorType.Should().Be(ErrorType.InvalidContext);
125125
doubleRes.ErrorMessage.Should().Be(testMessage);
126126

127-
var stringRes = await providerMock.ResolveStringValue(flagName, defaultStringValue);
127+
var stringRes = await providerMock.ResolveStringValueAsync(flagName, defaultStringValue);
128128
stringRes.ErrorType.Should().Be(ErrorType.TypeMismatch);
129129
stringRes.ErrorMessage.Should().Be(testMessage);
130130

131-
var structRes1 = await providerMock.ResolveStructureValue(flagName, defaultStructureValue);
131+
var structRes1 = await providerMock.ResolveStructureValueAsync(flagName, defaultStructureValue);
132132
structRes1.ErrorType.Should().Be(ErrorType.FlagNotFound);
133133
structRes1.ErrorMessage.Should().Be(testMessage);
134134

135-
var structRes2 = await providerMock.ResolveStructureValue(flagName2, defaultStructureValue);
135+
var structRes2 = await providerMock.ResolveStructureValueAsync(flagName2, defaultStructureValue);
136136
structRes2.ErrorType.Should().Be(ErrorType.ProviderNotReady);
137137
structRes2.ErrorMessage.Should().Be(testMessage);
138138

139-
var boolRes2 = await providerMock.ResolveBooleanValue(flagName2, defaultBoolValue);
139+
var boolRes2 = await providerMock.ResolveBooleanValueAsync(flagName2, defaultBoolValue);
140140
boolRes2.ErrorType.Should().Be(ErrorType.TargetingKeyMissing);
141141
boolRes2.ErrorMessage.Should().BeNull();
142142
}

‎test/OpenFeature.Tests/OpenFeatureClientTests.cs

+88-58
Large diffs are not rendered by default.

‎test/OpenFeature.Tests/OpenFeatureEventTests.cs

+11-11
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public async Task Event_Executor_Should_Propagate_Events_ToGlobal_Handler()
4646
eventHandler.Received().Invoke(Arg.Is<ProviderEventPayload>(payload => payload == myEvent.EventPayload));
4747

4848
// shut down the event executor
49-
await eventExecutor.Shutdown();
49+
await eventExecutor.ShutdownAsync();
5050

5151
// the next event should not be propagated to the event handler
5252
var newEventPayload = new ProviderEventPayload
@@ -78,9 +78,9 @@ public async Task API_Level_Event_Handlers_Should_Be_Registered()
7878
var testProvider = new TestProvider();
7979
await Api.Instance.SetProviderAsync(testProvider);
8080

81-
testProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
82-
testProvider.SendEvent(ProviderEventTypes.ProviderError);
83-
testProvider.SendEvent(ProviderEventTypes.ProviderStale);
81+
await testProvider.SendEventAsync(ProviderEventTypes.ProviderConfigurationChanged);
82+
await testProvider.SendEventAsync(ProviderEventTypes.ProviderError);
83+
await testProvider.SendEventAsync(ProviderEventTypes.ProviderStale);
8484

8585
await Utils.AssertUntilAsync(_ => eventHandler
8686
.Received()
@@ -148,7 +148,7 @@ public async Task API_Level_Event_Handlers_Should_Be_Informed_About_Ready_State_
148148

149149
var testProvider = new TestProvider();
150150
#pragma warning disable CS0618// Type or member is obsolete
151-
Api.Instance.SetProvider(testProvider);
151+
await Api.Instance.SetProviderAsync(testProvider);
152152
#pragma warning restore CS0618// Type or member is obsolete
153153

154154
Api.Instance.AddHandler(ProviderEventTypes.ProviderReady, eventHandler);
@@ -228,12 +228,12 @@ public async Task API_Level_Event_Handlers_Should_Be_Exchangeable()
228228
var testProvider = new TestProvider();
229229
await Api.Instance.SetProviderAsync(testProvider);
230230

231-
testProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
231+
await testProvider.SendEventAsync(ProviderEventTypes.ProviderConfigurationChanged);
232232

233233
var newTestProvider = new TestProvider();
234234
await Api.Instance.SetProviderAsync(newTestProvider);
235235

236-
newTestProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
236+
await newTestProvider.SendEventAsync(ProviderEventTypes.ProviderConfigurationChanged);
237237

238238
await Utils.AssertUntilAsync(
239239
_ => eventHandler.Received(2).Invoke(Arg.Is<ProviderEventPayload>(payload => payload.ProviderName == testProvider.GetMetadata().Name && payload.Type == ProviderEventTypes.ProviderReady))
@@ -407,7 +407,7 @@ public async Task Client_Level_Event_Handlers_Should_Be_Receive_Events_From_Name
407407

408408
client.AddHandler(ProviderEventTypes.ProviderConfigurationChanged, clientEventHandler);
409409

410-
defaultProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
410+
await defaultProvider.SendEventAsync(ProviderEventTypes.ProviderConfigurationChanged);
411411

412412
// verify that the client received the event from the default provider as there is no named provider registered yet
413413
await Utils.AssertUntilAsync(
@@ -419,8 +419,8 @@ await Utils.AssertUntilAsync(
419419
await Api.Instance.SetProviderAsync(client.GetMetadata().Name!, clientProvider);
420420

421421
// now, send another event for the default handler
422-
defaultProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
423-
clientProvider.SendEvent(ProviderEventTypes.ProviderConfigurationChanged);
422+
await defaultProvider.SendEventAsync(ProviderEventTypes.ProviderConfigurationChanged);
423+
await clientProvider.SendEventAsync(ProviderEventTypes.ProviderConfigurationChanged);
424424

425425
// now the client should have received only the event from the named provider
426426
await Utils.AssertUntilAsync(
@@ -479,7 +479,7 @@ public async Task Client_Level_Event_Handlers_Should_Be_Removable()
479479
await Utils.AssertUntilAsync(_ => myClient.RemoveHandler(ProviderEventTypes.ProviderReady, eventHandler));
480480

481481
// send another event from the provider - this one should not be received
482-
testProvider.SendEvent(ProviderEventTypes.ProviderReady);
482+
await testProvider.SendEventAsync(ProviderEventTypes.ProviderReady);
483483

484484
// wait a bit and make sure we only have received the first event, but nothing after removing the event handler
485485
await Utils.AssertUntilAsync(

‎test/OpenFeature.Tests/OpenFeatureHookTests.cs

+213-146
Large diffs are not rendered by default.

‎test/OpenFeature.Tests/OpenFeatureTests.cs

+19-31
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Diagnostics.CodeAnalysis;
32
using System.Linq;
43
using System.Threading.Tasks;
@@ -32,13 +31,13 @@ public async Task OpenFeature_Should_Initialize_Provider()
3231
providerMockDefault.GetStatus().Returns(ProviderStatus.NotReady);
3332

3433
await Api.Instance.SetProviderAsync(providerMockDefault);
35-
await providerMockDefault.Received(1).Initialize(Api.Instance.GetContext());
34+
await providerMockDefault.Received(1).InitializeAsync(Api.Instance.GetContext());
3635

3736
var providerMockNamed = Substitute.For<FeatureProvider>();
3837
providerMockNamed.GetStatus().Returns(ProviderStatus.NotReady);
3938

4039
await Api.Instance.SetProviderAsync("the-name", providerMockNamed);
41-
await providerMockNamed.Received(1).Initialize(Api.Instance.GetContext());
40+
await providerMockNamed.Received(1).InitializeAsync(Api.Instance.GetContext());
4241
}
4342

4443
[Fact]
@@ -50,27 +49,27 @@ public async Task OpenFeature_Should_Shutdown_Unused_Provider()
5049
providerA.GetStatus().Returns(ProviderStatus.NotReady);
5150

5251
await Api.Instance.SetProviderAsync(providerA);
53-
await providerA.Received(1).Initialize(Api.Instance.GetContext());
52+
await providerA.Received(1).InitializeAsync(Api.Instance.GetContext());
5453

5554
var providerB = Substitute.For<FeatureProvider>();
5655
providerB.GetStatus().Returns(ProviderStatus.NotReady);
5756

5857
await Api.Instance.SetProviderAsync(providerB);
59-
await providerB.Received(1).Initialize(Api.Instance.GetContext());
60-
await providerA.Received(1).Shutdown();
58+
await providerB.Received(1).InitializeAsync(Api.Instance.GetContext());
59+
await providerA.Received(1).ShutdownAsync();
6160

6261
var providerC = Substitute.For<FeatureProvider>();
6362
providerC.GetStatus().Returns(ProviderStatus.NotReady);
6463

6564
await Api.Instance.SetProviderAsync("named", providerC);
66-
await providerC.Received(1).Initialize(Api.Instance.GetContext());
65+
await providerC.Received(1).InitializeAsync(Api.Instance.GetContext());
6766

6867
var providerD = Substitute.For<FeatureProvider>();
6968
providerD.GetStatus().Returns(ProviderStatus.NotReady);
7069

7170
await Api.Instance.SetProviderAsync("named", providerD);
72-
await providerD.Received(1).Initialize(Api.Instance.GetContext());
73-
await providerC.Received(1).Shutdown();
71+
await providerD.Received(1).InitializeAsync(Api.Instance.GetContext());
72+
await providerC.Received(1).ShutdownAsync();
7473
}
7574

7675
[Fact]
@@ -86,10 +85,10 @@ public async Task OpenFeature_Should_Support_Shutdown()
8685
await Api.Instance.SetProviderAsync(providerA);
8786
await Api.Instance.SetProviderAsync("named", providerB);
8887

89-
await Api.Instance.Shutdown();
88+
await Api.Instance.ShutdownAsync();
9089

91-
await providerA.Received(1).Shutdown();
92-
await providerB.Received(1).Shutdown();
90+
await providerA.Received(1).ShutdownAsync();
91+
await providerB.Received(1).ShutdownAsync();
9392
}
9493

9594
[Fact]
@@ -128,8 +127,8 @@ public async Task OpenFeature_Should_Assign_Provider_To_Existing_Client()
128127
const string name = "new-client";
129128
var openFeature = Api.Instance;
130129

131-
await openFeature.SetProviderAsync(name, new TestProvider()).ConfigureAwait(true);
132-
await openFeature.SetProviderAsync(name, new NoOpFeatureProvider()).ConfigureAwait(true);
130+
await openFeature.SetProviderAsync(name, new TestProvider());
131+
await openFeature.SetProviderAsync(name, new NoOpFeatureProvider());
133132

134133
openFeature.GetProviderMetadata(name).Name.Should().Be(NoOpProvider.NoOpProviderName);
135134
}
@@ -141,8 +140,8 @@ public async Task OpenFeature_Should_Allow_Multiple_Client_Names_Of_Same_Instanc
141140
var openFeature = Api.Instance;
142141
var provider = new TestProvider();
143142

144-
await openFeature.SetProviderAsync("a", provider).ConfigureAwait(true);
145-
await openFeature.SetProviderAsync("b", provider).ConfigureAwait(true);
143+
await openFeature.SetProviderAsync("a", provider);
144+
await openFeature.SetProviderAsync("b", provider);
146145

147146
var clientA = openFeature.GetProvider("a");
148147
var clientB = openFeature.GetProvider("b");
@@ -233,28 +232,17 @@ public async Task OpenFeature_Should_Allow_Multiple_Client_Mapping()
233232
{
234233
var openFeature = Api.Instance;
235234

236-
await openFeature.SetProviderAsync("client1", new TestProvider()).ConfigureAwait(true);
237-
await openFeature.SetProviderAsync("client2", new NoOpFeatureProvider()).ConfigureAwait(true);
235+
await openFeature.SetProviderAsync("client1", new TestProvider());
236+
await openFeature.SetProviderAsync("client2", new NoOpFeatureProvider());
238237

239238
var client1 = openFeature.GetClient("client1");
240239
var client2 = openFeature.GetClient("client2");
241240

242241
client1.GetMetadata().Name.Should().Be("client1");
243242
client2.GetMetadata().Name.Should().Be("client2");
244243

245-
(await client1.GetBooleanValue("test", false)).Should().BeTrue();
246-
(await client2.GetBooleanValue("test", false)).Should().BeFalse();
247-
}
248-
249-
[Fact]
250-
public async Task SetProviderAsync_Should_Throw_When_Null_ClientName()
251-
{
252-
var openFeature = Api.Instance;
253-
254-
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() => openFeature.SetProviderAsync(null!, new TestProvider()));
255-
256-
exception.Should().BeOfType<ArgumentNullException>();
257-
exception.ParamName.Should().Be("clientName");
244+
(await client1.GetBooleanValueAsync("test", false)).Should().BeTrue();
245+
(await client2.GetBooleanValueAsync("test", false)).Should().BeFalse();
258246
}
259247
}
260248
}

‎test/OpenFeature.Tests/ProviderRepositoryTests.cs

+90-110
Large diffs are not rendered by default.

‎test/OpenFeature.Tests/Providers/Memory/InMemoryProviderTests.cs

+26-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Threading.Tasks;
34
using OpenFeature.Constant;
45
using OpenFeature.Error;
56
using OpenFeature.Model;
@@ -109,45 +110,45 @@ public InMemoryProviderTests()
109110
}
110111

111112
[Fact]
112-
public async void GetBoolean_ShouldEvaluateWithReasonAndVariant()
113+
public async Task GetBoolean_ShouldEvaluateWithReasonAndVariant()
113114
{
114-
ResolutionDetails<bool> details = await this.commonProvider.ResolveBooleanValue("boolean-flag", false, EvaluationContext.Empty);
115+
ResolutionDetails<bool> details = await this.commonProvider.ResolveBooleanValueAsync("boolean-flag", false, EvaluationContext.Empty);
115116
Assert.True(details.Value);
116117
Assert.Equal(Reason.Static, details.Reason);
117118
Assert.Equal("on", details.Variant);
118119
}
119120

120121
[Fact]
121-
public async void GetString_ShouldEvaluateWithReasonAndVariant()
122+
public async Task GetString_ShouldEvaluateWithReasonAndVariant()
122123
{
123-
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValue("string-flag", "nope", EvaluationContext.Empty);
124+
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValueAsync("string-flag", "nope", EvaluationContext.Empty);
124125
Assert.Equal("hi", details.Value);
125126
Assert.Equal(Reason.Static, details.Reason);
126127
Assert.Equal("greeting", details.Variant);
127128
}
128129

129130
[Fact]
130-
public async void GetInt_ShouldEvaluateWithReasonAndVariant()
131+
public async Task GetInt_ShouldEvaluateWithReasonAndVariant()
131132
{
132-
ResolutionDetails<int> details = await this.commonProvider.ResolveIntegerValue("integer-flag", 13, EvaluationContext.Empty);
133+
ResolutionDetails<int> details = await this.commonProvider.ResolveIntegerValueAsync("integer-flag", 13, EvaluationContext.Empty);
133134
Assert.Equal(10, details.Value);
134135
Assert.Equal(Reason.Static, details.Reason);
135136
Assert.Equal("ten", details.Variant);
136137
}
137138

138139
[Fact]
139-
public async void GetDouble_ShouldEvaluateWithReasonAndVariant()
140+
public async Task GetDouble_ShouldEvaluateWithReasonAndVariant()
140141
{
141-
ResolutionDetails<double> details = await this.commonProvider.ResolveDoubleValue("float-flag", 13, EvaluationContext.Empty);
142+
ResolutionDetails<double> details = await this.commonProvider.ResolveDoubleValueAsync("float-flag", 13, EvaluationContext.Empty);
142143
Assert.Equal(0.5, details.Value);
143144
Assert.Equal(Reason.Static, details.Reason);
144145
Assert.Equal("half", details.Variant);
145146
}
146147

147148
[Fact]
148-
public async void GetStruct_ShouldEvaluateWithReasonAndVariant()
149+
public async Task GetStruct_ShouldEvaluateWithReasonAndVariant()
149150
{
150-
ResolutionDetails<Value> details = await this.commonProvider.ResolveStructureValue("object-flag", new Value(), EvaluationContext.Empty);
151+
ResolutionDetails<Value> details = await this.commonProvider.ResolveStructureValueAsync("object-flag", new Value(), EvaluationContext.Empty);
151152
Assert.Equal(true, details.Value.AsStructure?["showImages"].AsBoolean);
152153
Assert.Equal("Check out these pics!", details.Value.AsStructure?["title"].AsString);
153154
Assert.Equal(100, details.Value.AsStructure?["imagesPerPage"].AsInteger);
@@ -156,49 +157,49 @@ public async void GetStruct_ShouldEvaluateWithReasonAndVariant()
156157
}
157158

158159
[Fact]
159-
public async void GetString_ContextSensitive_ShouldEvaluateWithReasonAndVariant()
160+
public async Task GetString_ContextSensitive_ShouldEvaluateWithReasonAndVariant()
160161
{
161162
EvaluationContext context = EvaluationContext.Builder().Set("email", "me@faas.com").Build();
162-
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValue("context-aware", "nope", context);
163+
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValueAsync("context-aware", "nope", context);
163164
Assert.Equal("INTERNAL", details.Value);
164165
Assert.Equal(Reason.TargetingMatch, details.Reason);
165166
Assert.Equal("internal", details.Variant);
166167
}
167168

168169
[Fact]
169-
public async void EmptyFlags_ShouldWork()
170+
public async Task EmptyFlags_ShouldWork()
170171
{
171172
var provider = new InMemoryProvider();
172173
await provider.UpdateFlags();
173174
Assert.Equal("InMemory", provider.GetMetadata().Name);
174175
}
175176

176177
[Fact]
177-
public async void MissingFlag_ShouldThrow()
178+
public async Task MissingFlag_ShouldThrow()
178179
{
179-
await Assert.ThrowsAsync<FlagNotFoundException>(() => this.commonProvider.ResolveBooleanValue("missing-flag", false, EvaluationContext.Empty));
180+
await Assert.ThrowsAsync<FlagNotFoundException>(() => this.commonProvider.ResolveBooleanValueAsync("missing-flag", false, EvaluationContext.Empty));
180181
}
181182

182183
[Fact]
183-
public async void MismatchedFlag_ShouldThrow()
184+
public async Task MismatchedFlag_ShouldThrow()
184185
{
185-
await Assert.ThrowsAsync<TypeMismatchException>(() => this.commonProvider.ResolveStringValue("boolean-flag", "nope", EvaluationContext.Empty));
186+
await Assert.ThrowsAsync<TypeMismatchException>(() => this.commonProvider.ResolveStringValueAsync("boolean-flag", "nope", EvaluationContext.Empty));
186187
}
187188

188189
[Fact]
189-
public async void MissingDefaultVariant_ShouldThrow()
190+
public async Task MissingDefaultVariant_ShouldThrow()
190191
{
191-
await Assert.ThrowsAsync<GeneralException>(() => this.commonProvider.ResolveBooleanValue("invalid-flag", false, EvaluationContext.Empty));
192+
await Assert.ThrowsAsync<GeneralException>(() => this.commonProvider.ResolveBooleanValueAsync("invalid-flag", false, EvaluationContext.Empty));
192193
}
193194

194195
[Fact]
195-
public async void MissingEvaluatedVariant_ShouldThrow()
196+
public async Task MissingEvaluatedVariant_ShouldThrow()
196197
{
197-
await Assert.ThrowsAsync<GeneralException>(() => this.commonProvider.ResolveBooleanValue("invalid-evaluator-flag", false, EvaluationContext.Empty));
198+
await Assert.ThrowsAsync<GeneralException>(() => this.commonProvider.ResolveBooleanValueAsync("invalid-evaluator-flag", false, EvaluationContext.Empty));
198199
}
199200

200201
[Fact]
201-
public async void PutConfiguration_shouldUpdateConfigAndRunHandlers()
202+
public async Task PutConfiguration_shouldUpdateConfigAndRunHandlers()
202203
{
203204
var provider = new InMemoryProvider(new Dictionary<string, Flag>(){
204205
{
@@ -211,7 +212,7 @@ public async void PutConfiguration_shouldUpdateConfigAndRunHandlers()
211212
)
212213
}});
213214

214-
ResolutionDetails<bool> details = await provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty);
215+
ResolutionDetails<bool> details = await provider.ResolveBooleanValueAsync("old-flag", false, EvaluationContext.Empty);
215216
Assert.True(details.Value);
216217

217218
// update flags
@@ -229,10 +230,10 @@ await provider.UpdateFlags(new Dictionary<string, Flag>(){
229230
var res = await provider.GetEventChannel().Reader.ReadAsync() as ProviderEventPayload;
230231
Assert.Equal(ProviderEventTypes.ProviderConfigurationChanged, res?.Type);
231232

232-
await Assert.ThrowsAsync<FlagNotFoundException>(() => provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty));
233+
await Assert.ThrowsAsync<FlagNotFoundException>(() => provider.ResolveBooleanValueAsync("old-flag", false, EvaluationContext.Empty));
233234

234235
// new flag should be present, old gone (defaults), handler run.
235-
ResolutionDetails<string> detailsAfter = await provider.ResolveStringValue("new-flag", "nope", EvaluationContext.Empty);
236+
ResolutionDetails<string> detailsAfter = await provider.ResolveStringValueAsync("new-flag", "nope", EvaluationContext.Empty);
236237
Assert.True(details.Value);
237238
Assert.Equal("hi", detailsAfter.Value);
238239
}

‎test/OpenFeature.Tests/TestImplementations.cs

+25-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
4+
using System.Threading;
45
using System.Threading.Tasks;
56
using OpenFeature.Constant;
67
using OpenFeature.Model;
@@ -11,25 +12,25 @@ public class TestHookNoOverride : Hook { }
1112

1213
public class TestHook : Hook
1314
{
14-
public override Task<EvaluationContext> Before<T>(HookContext<T> context, IReadOnlyDictionary<string, object>? hints = null)
15+
public override ValueTask<EvaluationContext> BeforeAsync<T>(HookContext<T> context, IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
1516
{
16-
return Task.FromResult(EvaluationContext.Empty);
17+
return new ValueTask<EvaluationContext>(EvaluationContext.Empty);
1718
}
1819

19-
public override Task After<T>(HookContext<T> context, FlagEvaluationDetails<T> details,
20-
IReadOnlyDictionary<string, object>? hints = null)
20+
public override ValueTask AfterAsync<T>(HookContext<T> context, FlagEvaluationDetails<T> details,
21+
IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
2122
{
22-
return Task.CompletedTask;
23+
return new ValueTask();
2324
}
2425

25-
public override Task Error<T>(HookContext<T> context, Exception error, IReadOnlyDictionary<string, object>? hints = null)
26+
public override ValueTask ErrorAsync<T>(HookContext<T> context, Exception error, IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
2627
{
27-
return Task.CompletedTask;
28+
return new ValueTask();
2829
}
2930

30-
public override Task Finally<T>(HookContext<T> context, IReadOnlyDictionary<string, object>? hints = null)
31+
public override ValueTask FinallyAsync<T>(HookContext<T> context, IReadOnlyDictionary<string, object>? hints = null, CancellationToken cancellationToken = default)
3132
{
32-
return Task.CompletedTask;
33+
return new ValueTask();
3334
}
3435
}
3536

@@ -64,32 +65,32 @@ public override Metadata GetMetadata()
6465
return new Metadata(this.Name);
6566
}
6667

67-
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue,
68-
EvaluationContext? context = null)
68+
public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(string flagKey, bool defaultValue,
69+
EvaluationContext? context = null, CancellationToken cancellationToken = default)
6970
{
7071
return Task.FromResult(new ResolutionDetails<bool>(flagKey, !defaultValue));
7172
}
7273

73-
public override Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue,
74-
EvaluationContext? context = null)
74+
public override Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue,
75+
EvaluationContext? context = null, CancellationToken cancellationToken = default)
7576
{
7677
return Task.FromResult(new ResolutionDetails<string>(flagKey, defaultValue));
7778
}
7879

79-
public override Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue,
80-
EvaluationContext? context = null)
80+
public override Task<ResolutionDetails<int>> ResolveIntegerValueAsync(string flagKey, int defaultValue,
81+
EvaluationContext? context = null, CancellationToken cancellationToken = default)
8182
{
8283
return Task.FromResult(new ResolutionDetails<int>(flagKey, defaultValue));
8384
}
8485

85-
public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue,
86-
EvaluationContext? context = null)
86+
public override Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue,
87+
EvaluationContext? context = null, CancellationToken cancellationToken = default)
8788
{
8889
return Task.FromResult(new ResolutionDetails<double>(flagKey, defaultValue));
8990
}
9091

91-
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
92-
EvaluationContext? context = null)
92+
public override Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue,
93+
EvaluationContext? context = null, CancellationToken cancellationToken = default)
9394
{
9495
return Task.FromResult(new ResolutionDetails<Value>(flagKey, defaultValue));
9596
}
@@ -104,16 +105,16 @@ public void SetStatus(ProviderStatus status)
104105
this._status = status;
105106
}
106107

107-
public override Task Initialize(EvaluationContext context)
108+
public override async Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default)
108109
{
109110
this._status = ProviderStatus.Ready;
110-
this.EventChannel.Writer.WriteAsync(new ProviderEventPayload { Type = ProviderEventTypes.ProviderReady, ProviderName = this.GetMetadata().Name });
111-
return base.Initialize(context);
111+
await this.EventChannel.Writer.WriteAsync(new ProviderEventPayload { Type = ProviderEventTypes.ProviderReady, ProviderName = this.GetMetadata().Name }, cancellationToken).ConfigureAwait(false);
112+
await base.InitializeAsync(context, cancellationToken).ConfigureAwait(false);
112113
}
113114

114-
internal void SendEvent(ProviderEventTypes eventType)
115+
internal ValueTask SendEventAsync(ProviderEventTypes eventType, CancellationToken cancellationToken = default)
115116
{
116-
this.EventChannel.Writer.WriteAsync(new ProviderEventPayload { Type = eventType, ProviderName = this.GetMetadata().Name });
117+
return this.EventChannel.Writer.WriteAsync(new ProviderEventPayload { Type = eventType, ProviderName = this.GetMetadata().Name }, cancellationToken);
117118
}
118119
}
119120
}

‎test/OpenFeature.Tests/TestUtilsTest.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Threading.Tasks;
34
using Xunit;
45

56
namespace OpenFeature.Tests
@@ -8,13 +9,13 @@ namespace OpenFeature.Tests
89
public class TestUtilsTest
910
{
1011
[Fact]
11-
public async void Should_Fail_If_Assertion_Fails()
12+
public async Task Should_Fail_If_Assertion_Fails()
1213
{
1314
await Assert.ThrowsAnyAsync<Exception>(() => Utils.AssertUntilAsync(_ => Assert.True(1.Equals(2)), 100, 10));
1415
}
1516

1617
[Fact]
17-
public async void Should_Pass_If_Assertion_Fails()
18+
public async Task Should_Pass_If_Assertion_Fails()
1819
{
1920
await Utils.AssertUntilAsync(_ => Assert.True(1.Equals(1)));
2021
}

0 commit comments

Comments
 (0)
Please sign in to comment.