Skip to content

Commit

Permalink
feat: update ctx merge order, add client ctx
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <[email protected]>
  • Loading branch information
toddbaert committed Aug 10, 2022
1 parent 3ee2673 commit 9f3f879
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 4 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,10 @@ ASALocalRun/
/.sonarqube

/src/LastMajorVersionBinaries

# vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
26 changes: 26 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/test/OpenFeature.Tests/bin/Debug/net6.0/OpenFeature.Tests.dll",
"args": [],
"cwd": "${workspaceFolder}/test/OpenFeature.Tests",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/test/OpenFeature.Tests/OpenFeature.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/test/OpenFeature.Tests/OpenFeature.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/test/OpenFeature.Tests/OpenFeature.Tests.csproj"
],
"problemMatcher": "$msCompile"
}
]
}
5 changes: 3 additions & 2 deletions src/OpenFeature.SDK/OpenFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ private OpenFeature() { }
/// <param name="name">Name of client</param>
/// <param name="version">Version of client</param>
/// <param name="logger">Logger instance used by client</param>
/// <param name="context">Context given to this client</param>
/// <returns><see cref="FeatureClient"/></returns>
public FeatureClient GetClient(string name = null, string version = null, ILogger logger = null) =>
new FeatureClient(this._featureProvider, name, version, logger);
public FeatureClient GetClient(string name = null, string version = null, ILogger logger = null, EvaluationContext context = null) =>
new FeatureClient(this._featureProvider, name, version, logger, context);

/// <summary>
/// Appends list of hooks to global hooks list
Expand Down
13 changes: 12 additions & 1 deletion src/OpenFeature.SDK/OpenFeatureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ public sealed class FeatureClient : IFeatureClient
private readonly FeatureProvider _featureProvider;
private readonly List<Hook> _hooks = new List<Hook>();
private readonly ILogger _logger;
private readonly EvaluationContext _evaluationContext;

/// <summary>
/// Gets the client <see cref="EvaluationContext"/>
/// </summary>
/// <returns></returns>
public EvaluationContext GetContext() => this._evaluationContext;

/// <summary>
/// Initializes a new instance of the <see cref="FeatureClient"/> class.
Expand All @@ -28,12 +35,14 @@ public sealed class FeatureClient : IFeatureClient
/// <param name="name">Name of client <see cref="ClientMetadata"/></param>
/// <param name="version">Version of client <see cref="ClientMetadata"/></param>
/// <param name="logger">Logger used by client</param>
/// <param name="context">Context given to this client</param>
/// <exception cref="ArgumentNullException">Throws if any of the required parameters are null</exception>
public FeatureClient(FeatureProvider featureProvider, string name, string version, ILogger logger = null)
public FeatureClient(FeatureProvider featureProvider, string name, string version, ILogger logger = null, EvaluationContext context = null)
{
this._featureProvider = featureProvider ?? throw new ArgumentNullException(nameof(featureProvider));
this._metadata = new ClientMetadata(name, version);
this._logger = logger ?? new Logger<OpenFeature>(new NullLoggerFactory());
this._evaluationContext = context ?? new EvaluationContext();
}

/// <summary>
Expand Down Expand Up @@ -202,7 +211,9 @@ private async Task<FlagEvaluationDetails<T>> EvaluateFlag<T>(
context = new EvaluationContext();
}

// merge api, client, and invocation context.
var evaluationContext = OpenFeature.Instance.GetContext();
evaluationContext.Merge(this.GetContext());
evaluationContext.Merge(context);

var allHooks = new List<Hook>()
Expand Down
69 changes: 68 additions & 1 deletion test/OpenFeature.SDK.Tests/OpenFeatureHookTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ public void Hook_Context_Should_Have_Properties_And_Be_Immutable()
[Fact]
[Specification("4.1.4", "The evaluation context MUST be mutable only within the `before` hook.")]
[Specification("4.3.3", "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).")]
[Specification("4.3.4", "When `before` hooks have finished executing, any resulting `evaluation context` MUST be merged with the invocation `evaluation context` with the invocation `evaluation context` taking precedence in the case of any conflicts.")]
public async Task Evaluation_Context_Must_Be_Mutable_Before_Hook()
{
var evaluationContext = new EvaluationContext { ["test"] = "test" };
Expand All @@ -189,6 +188,74 @@ public async Task Evaluation_Context_Must_Be_Mutable_Before_Hook()
hook2.Verify(x => x.Before(It.Is<HookContext<bool>>(a => a.EvaluationContext.Get<string>("test") == "test"), It.IsAny<Dictionary<string, object>>()), Times.Once);
}

[Fact]
[Specification("4.3.4", "When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context in the following order: before-hook (highest precedence), invocation, client, api (lowest precedence).")]
public async Task Evaluation_Context_Must_Be_Merged_In_Correct_Order()
{
var propGlobal = "4.3.4global";
var propGlobalToOverwrite = "4.3.4globalToOverwrite";

var propClient = "4.3.4client";
var propClientToOverwrite = "4.3.4clientToOverwrite";

var propInvocation = "4.3.4invocation";
var propInvocationToOverwrite = "4.3.4invocationToOverwrite";

var propHook = "4.3.4hook";

// setup a cascade of overwriting properties
OpenFeature.Instance.SetContext(new EvaluationContext {
[propGlobal] = true,
[propGlobalToOverwrite] = false
});
var clientContext = new EvaluationContext {
[propClient] = true,
[propGlobalToOverwrite] = true,
[propClientToOverwrite] = false
};
var invocationContext = new EvaluationContext {
[propInvocation] = true,
[propClientToOverwrite] = true,
[propInvocationToOverwrite] = false,
};
var hookContext = new EvaluationContext {
[propHook] = true,
[propInvocationToOverwrite] = true,
};

var provider = new Mock<FeatureProvider>(MockBehavior.Strict);

provider.Setup(x => x.GetMetadata())
.Returns(new Metadata(null));

provider.Setup(x => x.GetProviderHooks())
.Returns(Array.Empty<Hook>());

provider.Setup(x => x.ResolveBooleanValue(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<EvaluationContext>(), null))
.ReturnsAsync(new ResolutionDetails<bool>("test", true));

OpenFeature.Instance.SetProvider(provider.Object);

var hook = new Mock<Hook>(MockBehavior.Strict);
hook.Setup(x => x.Before(It.IsAny<HookContext<It.IsAnyType>>(), It.IsAny<Dictionary<string, object>>()))
.ReturnsAsync(hookContext);


var client = OpenFeature.Instance.GetClient("test", "1.0.0", null, clientContext);
await client.GetBooleanValue("test", false, invocationContext, new FlagEvaluationOptions(new[] { hook.Object }, new Dictionary<string, object>()));

// after proper merging, all properties should equal true
provider.Verify(x => x.ResolveBooleanValue(It.IsAny<string>(), It.IsAny<bool>(), It.Is<EvaluationContext>(y =>
y.Get<bool>(propGlobal)
&& y.Get<bool>(propClient)
&& y.Get<bool>(propGlobalToOverwrite)
&& y.Get<bool>(propInvocation)
&& y.Get<bool>(propClientToOverwrite)
&& y.Get<bool>(propHook)
&& y.Get<bool>(propInvocationToOverwrite)
), It.IsAny<FlagEvaluationOptions>()), Times.Once);
}

[Fact]
[Specification("4.2.1", "`hook hints` MUST be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`..")]
[Specification("4.2.2.1", "Condition: `Hook hints` MUST be immutable.")]
Expand Down

0 comments on commit 9f3f879

Please sign in to comment.