Skip to content

Commit

Permalink
feat!: structure->value, object value constructor
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <[email protected]>
  • Loading branch information
toddbaert committed Sep 9, 2022
1 parent 2768d4c commit f97d022
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 23 deletions.
4 changes: 2 additions & 2 deletions src/OpenFeature.SDK/FeatureProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ public abstract Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKe
EvaluationContext context = null);

/// <summary>
/// Resolves a structure feature flag
/// Resolves a structured feature flag
/// </summary>
/// <param name="flagKey">Feature flag key</param>
/// <param name="defaultValue">Default value</param>
/// <param name="context"><see cref="EvaluationContext"/></param>
/// <returns><see cref="ResolutionDetails{T}"/></returns>
public abstract Task<ResolutionDetails<Structure>> ResolveStructureValue(string flagKey, Structure defaultValue,
public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
EvaluationContext context = null);
}
}
4 changes: 2 additions & 2 deletions src/OpenFeature.SDK/IFeatureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal interface IFeatureClient
Task<double> GetDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
Task<FlagEvaluationDetails<double>> GetDoubleDetails(string flagKey, double defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);

Task<Structure> GetObjectValue(string flagKey, Structure defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
Task<FlagEvaluationDetails<Structure>> GetObjectDetails(string flagKey, Structure defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
Task<Value> GetObjectValue(string flagKey, Value defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
Task<FlagEvaluationDetails<Value>> GetObjectDetails(string flagKey, Value defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
}
}
27 changes: 27 additions & 0 deletions src/OpenFeature.SDK/Model/Value.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ public class Value
/// </summary>
public Value() => this._innerValue = null;

/// <summary>
/// Creates a Value with the inner set to the object
/// </summary>
/// <param name="value"><see cref="Object">The object to set as the inner value</see></param>
public Value(Object value)
{
// integer is a special case, convert those.
this._innerValue = value is int ? Convert.ToDouble(value) : value;
if (!(this.IsNull()
|| this.IsBoolean()
|| this.IsString()
|| this.IsNumber()
|| this.IsStructure()
|| this.IsList()
|| this.IsDateTime()))
{
throw new ArgumentException("Invalid value type: " + value.GetType());
}
}


/// <summary>
/// Creates a Value with the inner value to the inner value of the value param
/// </summary>
Expand Down Expand Up @@ -107,6 +128,12 @@ public class Value
/// <returns><see cref="bool">True if value is DateTime</see></returns>
public bool IsDateTime() => this._innerValue is DateTime;

/// <summary>
/// Returns the underlying inner value as an object. Returns null if the inner value is null.
/// </summary>
/// <returns>Value as object</returns>
public object AsObject() => this._innerValue;

/// <summary>
/// Returns the underlying int value
/// Value will be null if it isn't a integer
Expand Down
2 changes: 1 addition & 1 deletion src/OpenFeature.SDK/NoOpProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKe
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
}

public override Task<ResolutionDetails<Structure>> ResolveStructureValue(string flagKey, Structure defaultValue, EvaluationContext context = null)
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null)
{
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
}
Expand Down
4 changes: 2 additions & 2 deletions src/OpenFeature.SDK/OpenFeatureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ await this.EvaluateFlag(this._featureProvider.ResolveDoubleValue, FlagValueType.
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
public async Task<Structure> GetObjectValue(string flagKey, Structure defaultValue, EvaluationContext context = null,
public async Task<Value> GetObjectValue(string flagKey, Value defaultValue, EvaluationContext context = null,
FlagEvaluationOptions config = null) =>
(await this.GetObjectDetails(flagKey, defaultValue, context, config)).Value;

Expand All @@ -200,7 +200,7 @@ public async Task<Structure> GetObjectValue(string flagKey, Structure defaultVal
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
public async Task<FlagEvaluationDetails<Structure>> GetObjectDetails(string flagKey, Structure defaultValue,
public async Task<FlagEvaluationDetails<Value>> GetObjectDetails(string flagKey, Value defaultValue,
EvaluationContext context = null, FlagEvaluationOptions config = null) =>
await this.EvaluateFlag(this._featureProvider.ResolveStructureValue, FlagValueType.Object, flagKey,
defaultValue, context, config);
Expand Down
10 changes: 5 additions & 5 deletions test/OpenFeature.SDK.Tests/FeatureProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task Provider_Must_Resolve_Flag_Values()
var defaultStringValue = fixture.Create<string>();
var defaultIntegerValue = fixture.Create<int>();
var defaultDoubleValue = fixture.Create<double>();
var defaultStructureValue = fixture.Create<Structure>();
var defaultStructureValue = fixture.Create<Value>();
var provider = new NoOpFeatureProvider();

var boolResolutionDetails = new ResolutionDetails<bool>(flagName, defaultBoolValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
Expand All @@ -51,7 +51,7 @@ public async Task Provider_Must_Resolve_Flag_Values()
var stringResolutionDetails = new ResolutionDetails<string>(flagName, defaultStringValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
(await provider.ResolveStringValue(flagName, defaultStringValue)).Should().BeEquivalentTo(stringResolutionDetails);

var structureResolutionDetails = new ResolutionDetails<Structure>(flagName, defaultStructureValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
var structureResolutionDetails = new ResolutionDetails<Value>(flagName, defaultStructureValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
(await provider.ResolveStructureValue(flagName, defaultStructureValue)).Should().BeEquivalentTo(structureResolutionDetails);
}

Expand All @@ -66,7 +66,7 @@ public async Task Provider_Must_ErrorType()
var defaultStringValue = fixture.Create<string>();
var defaultIntegerValue = fixture.Create<int>();
var defaultDoubleValue = fixture.Create<double>();
var defaultStructureValue = fixture.Create<Structure>();
var defaultStructureValue = fixture.Create<Value>();
var providerMock = new Mock<FeatureProvider>(MockBehavior.Strict);

providerMock.Setup(x => x.ResolveBooleanValue(flagName, defaultBoolValue, It.IsAny<EvaluationContext>()))
Expand All @@ -82,10 +82,10 @@ public async Task Provider_Must_ErrorType()
.ReturnsAsync(new ResolutionDetails<string>(flagName, defaultStringValue, ErrorType.TypeMismatch, NoOpProvider.ReasonNoOp, NoOpProvider.Variant));

providerMock.Setup(x => x.ResolveStructureValue(flagName, defaultStructureValue, It.IsAny<EvaluationContext>()))
.ReturnsAsync(new ResolutionDetails<Structure>(flagName, defaultStructureValue, ErrorType.FlagNotFound, NoOpProvider.ReasonNoOp, NoOpProvider.Variant));
.ReturnsAsync(new ResolutionDetails<Value>(flagName, defaultStructureValue, ErrorType.FlagNotFound, NoOpProvider.ReasonNoOp, NoOpProvider.Variant));

providerMock.Setup(x => x.ResolveStructureValue(flagName2, defaultStructureValue, It.IsAny<EvaluationContext>()))
.ReturnsAsync(new ResolutionDetails<Structure>(flagName, defaultStructureValue, ErrorType.ProviderNotReady, NoOpProvider.ReasonNoOp, NoOpProvider.Variant));
.ReturnsAsync(new ResolutionDetails<Value>(flagName, defaultStructureValue, ErrorType.ProviderNotReady, NoOpProvider.ReasonNoOp, NoOpProvider.Variant));

var provider = providerMock.Object;

Expand Down
16 changes: 8 additions & 8 deletions test/OpenFeature.SDK.Tests/OpenFeatureClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task OpenFeatureClient_Should_Allow_Flag_Evaluation()
var defaultStringValue = fixture.Create<string>();
var defaultIntegerValue = fixture.Create<int>();
var defaultDoubleValue = fixture.Create<double>();
var defaultStructureValue = fixture.Create<Structure>();
var defaultStructureValue = fixture.Create<Value>();
var emptyFlagOptions = new FlagEvaluationOptions(new List<Hook>(), new Dictionary<string, object>());

OpenFeature.Instance.SetProvider(new NoOpFeatureProvider());
Expand Down Expand Up @@ -114,7 +114,7 @@ public async Task OpenFeatureClient_Should_Allow_Details_Flag_Evaluation()
var defaultStringValue = fixture.Create<string>();
var defaultIntegerValue = fixture.Create<int>();
var defaultDoubleValue = fixture.Create<double>();
var defaultStructureValue = fixture.Create<Structure>();
var defaultStructureValue = fixture.Create<Value>();
var emptyFlagOptions = new FlagEvaluationOptions(new List<Hook>(), new Dictionary<string, object>());

OpenFeature.Instance.SetProvider(new NoOpFeatureProvider());
Expand All @@ -140,7 +140,7 @@ public async Task OpenFeatureClient_Should_Allow_Details_Flag_Evaluation()
(await client.GetStringDetails(flagName, defaultStringValue, new EvaluationContext())).Should().BeEquivalentTo(stringFlagEvaluationDetails);
(await client.GetStringDetails(flagName, defaultStringValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(stringFlagEvaluationDetails);

var structureFlagEvaluationDetails = new FlagEvaluationDetails<Structure>(flagName, defaultStructureValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
var structureFlagEvaluationDetails = new FlagEvaluationDetails<Value>(flagName, defaultStructureValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant);
(await client.GetObjectDetails(flagName, defaultStructureValue)).Should().BeEquivalentTo(structureFlagEvaluationDetails);
(await client.GetObjectDetails(flagName, defaultStructureValue, new EvaluationContext())).Should().BeEquivalentTo(structureFlagEvaluationDetails);
(await client.GetObjectDetails(flagName, defaultStructureValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(structureFlagEvaluationDetails);
Expand All @@ -159,7 +159,7 @@ public async Task OpenFeatureClient_Should_Return_DefaultValue_When_Type_Mismatc
var clientName = fixture.Create<string>();
var clientVersion = fixture.Create<string>();
var flagName = fixture.Create<string>();
var defaultValue = fixture.Create<Structure>();
var defaultValue = fixture.Create<Value>();
var mockedFeatureProvider = new Mock<FeatureProvider>(MockBehavior.Strict);
var mockedLogger = new Mock<ILogger<OpenFeature>>(MockBehavior.Default);

Expand Down Expand Up @@ -302,12 +302,12 @@ public async Task Should_Resolve_StructureValue()
var clientName = fixture.Create<string>();
var clientVersion = fixture.Create<string>();
var flagName = fixture.Create<string>();
var defaultValue = fixture.Create<Structure>();
var defaultValue = fixture.Create<Value>();

var featureProviderMock = new Mock<FeatureProvider>(MockBehavior.Strict);
featureProviderMock
.Setup(x => x.ResolveStructureValue(flagName, defaultValue, It.IsAny<EvaluationContext>()))
.ReturnsAsync(new ResolutionDetails<Structure>(flagName, defaultValue));
.ReturnsAsync(new ResolutionDetails<Value>(flagName, defaultValue));
featureProviderMock.Setup(x => x.GetMetadata())
.Returns(new Metadata(fixture.Create<string>()));
featureProviderMock.Setup(x => x.GetProviderHooks())
Expand All @@ -316,7 +316,7 @@ public async Task Should_Resolve_StructureValue()
OpenFeature.Instance.SetProvider(featureProviderMock.Object);
var client = OpenFeature.Instance.GetClient(clientName, clientVersion);

(await client.GetObjectValue(flagName, defaultValue)).Should().Equal(defaultValue);
(await client.GetObjectValue(flagName, defaultValue)).Should().Be(defaultValue);

featureProviderMock.Verify(x => x.ResolveStructureValue(flagName, defaultValue, It.IsAny<EvaluationContext>()), Times.Once);
}
Expand All @@ -328,7 +328,7 @@ public async Task When_Exception_Occurs_During_Evaluation_Should_Return_Error()
var clientName = fixture.Create<string>();
var clientVersion = fixture.Create<string>();
var flagName = fixture.Create<string>();
var defaultValue = fixture.Create<Structure>();
var defaultValue = fixture.Create<Value>();

var featureProviderMock = new Mock<FeatureProvider>(MockBehavior.Strict);
featureProviderMock
Expand Down
4 changes: 2 additions & 2 deletions test/OpenFeature.SDK.Tests/TestImplementations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKe
return Task.FromResult(new ResolutionDetails<double>(flagKey, defaultValue));
}

public override Task<ResolutionDetails<Structure>> ResolveStructureValue(string flagKey, Structure defaultValue,
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
EvaluationContext context = null)
{
return Task.FromResult(new ResolutionDetails<Structure>(flagKey, defaultValue));
return Task.FromResult(new ResolutionDetails<Value>(flagKey, defaultValue));
}
}
}
52 changes: 51 additions & 1 deletion test/OpenFeature.SDK.Tests/ValueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,64 @@ namespace OpenFeature.SDK.Tests
{
public class ValueTests
{
class Foo { }

[Fact]
public void No_Arg_Should_Contain_Null()
{
Value value = new Value();
Assert.True(value.IsNull());
}

[Fact]
public void Object_Arg_Should_Contain_Object()
{
try
{
// int is a special case, see Int_Object_Arg_Should_Contain_Object()
IList<Object> list = new List<Object>(){
true, "val", .5, new Structure(), new List<Value>(), DateTime.Now
};

int i = 0;
foreach (Object l in list)
{
Value value = new Value(l);
Assert.Equal(list[i], value.AsObject());
i++;
}
}
catch (Exception)
{
Assert.True(false, "Expected no exception.");
}
}

[Fact]
public void Int_Object_Arg_Should_Contain_Object()
{
try
{
int innerValue = 1;
Value value = new Value(innerValue);
Assert.True(value.IsNumber());
Assert.Equal(innerValue, value.AsInteger());
}
catch (Exception)
{
Assert.True(false, "Expected no exception.");
}
}

[Fact]
public void Invalid_Object_Should_Throw()
{
Assert.Throws<ArgumentException>(() =>
{
return new Value(new Foo());
});
}

[Fact]
public void Bool_Arg_Should_Contain_Bool()
{
Expand Down Expand Up @@ -48,7 +99,6 @@ public void String_Arg_Should_Contain_String()
Assert.Equal(innerValue, value.AsString());
}


[Fact]
public void DateTime_Arg_Should_Contain_DateTime()
{
Expand Down

0 comments on commit f97d022

Please sign in to comment.