Skip to content

Commit f97d022

Browse files
committed
feat!: structure->value, object value constructor
Signed-off-by: Todd Baert <[email protected]>
1 parent 2768d4c commit f97d022

File tree

9 files changed

+100
-23
lines changed

9 files changed

+100
-23
lines changed

src/OpenFeature.SDK/FeatureProvider.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ public abstract Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKe
7272
EvaluationContext context = null);
7373

7474
/// <summary>
75-
/// Resolves a structure feature flag
75+
/// Resolves a structured feature flag
7676
/// </summary>
7777
/// <param name="flagKey">Feature flag key</param>
7878
/// <param name="defaultValue">Default value</param>
7979
/// <param name="context"><see cref="EvaluationContext"/></param>
8080
/// <returns><see cref="ResolutionDetails{T}"/></returns>
81-
public abstract Task<ResolutionDetails<Structure>> ResolveStructureValue(string flagKey, Structure defaultValue,
81+
public abstract Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
8282
EvaluationContext context = null);
8383
}
8484
}

src/OpenFeature.SDK/IFeatureClient.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal interface IFeatureClient
2121
Task<double> GetDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
2222
Task<FlagEvaluationDetails<double>> GetDoubleDetails(string flagKey, double defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
2323

24-
Task<Structure> GetObjectValue(string flagKey, Structure defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
25-
Task<FlagEvaluationDetails<Structure>> GetObjectDetails(string flagKey, Structure defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
24+
Task<Value> GetObjectValue(string flagKey, Value defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
25+
Task<FlagEvaluationDetails<Value>> GetObjectDetails(string flagKey, Value defaultValue, EvaluationContext context = null, FlagEvaluationOptions config = null);
2626
}
2727
}

src/OpenFeature.SDK/Model/Value.cs

+27
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@ public class Value
1717
/// </summary>
1818
public Value() => this._innerValue = null;
1919

20+
/// <summary>
21+
/// Creates a Value with the inner set to the object
22+
/// </summary>
23+
/// <param name="value"><see cref="Object">The object to set as the inner value</see></param>
24+
public Value(Object value)
25+
{
26+
// integer is a special case, convert those.
27+
this._innerValue = value is int ? Convert.ToDouble(value) : value;
28+
if (!(this.IsNull()
29+
|| this.IsBoolean()
30+
|| this.IsString()
31+
|| this.IsNumber()
32+
|| this.IsStructure()
33+
|| this.IsList()
34+
|| this.IsDateTime()))
35+
{
36+
throw new ArgumentException("Invalid value type: " + value.GetType());
37+
}
38+
}
39+
40+
2041
/// <summary>
2142
/// Creates a Value with the inner value to the inner value of the value param
2243
/// </summary>
@@ -107,6 +128,12 @@ public class Value
107128
/// <returns><see cref="bool">True if value is DateTime</see></returns>
108129
public bool IsDateTime() => this._innerValue is DateTime;
109130

131+
/// <summary>
132+
/// Returns the underlying inner value as an object. Returns null if the inner value is null.
133+
/// </summary>
134+
/// <returns>Value as object</returns>
135+
public object AsObject() => this._innerValue;
136+
110137
/// <summary>
111138
/// Returns the underlying int value
112139
/// Value will be null if it isn't a integer

src/OpenFeature.SDK/NoOpProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKe
3333
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
3434
}
3535

36-
public override Task<ResolutionDetails<Structure>> ResolveStructureValue(string flagKey, Structure defaultValue, EvaluationContext context = null)
36+
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null)
3737
{
3838
return Task.FromResult(NoOpResponse(flagKey, defaultValue));
3939
}

src/OpenFeature.SDK/OpenFeatureClient.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ await this.EvaluateFlag(this._featureProvider.ResolveDoubleValue, FlagValueType.
188188
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
189189
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
190190
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
191-
public async Task<Structure> GetObjectValue(string flagKey, Structure defaultValue, EvaluationContext context = null,
191+
public async Task<Value> GetObjectValue(string flagKey, Value defaultValue, EvaluationContext context = null,
192192
FlagEvaluationOptions config = null) =>
193193
(await this.GetObjectDetails(flagKey, defaultValue, context, config)).Value;
194194

@@ -200,7 +200,7 @@ public async Task<Structure> GetObjectValue(string flagKey, Structure defaultVal
200200
/// <param name="context"><see cref="EvaluationContext">Evaluation Context</see></param>
201201
/// <param name="config"><see cref="EvaluationContext">Flag Evaluation Options</see></param>
202202
/// <returns>Resolved flag details <see cref="FlagEvaluationDetails{T}"/></returns>
203-
public async Task<FlagEvaluationDetails<Structure>> GetObjectDetails(string flagKey, Structure defaultValue,
203+
public async Task<FlagEvaluationDetails<Value>> GetObjectDetails(string flagKey, Value defaultValue,
204204
EvaluationContext context = null, FlagEvaluationOptions config = null) =>
205205
await this.EvaluateFlag(this._featureProvider.ResolveStructureValue, FlagValueType.Object, flagKey,
206206
defaultValue, context, config);

test/OpenFeature.SDK.Tests/FeatureProviderTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public async Task Provider_Must_Resolve_Flag_Values()
3636
var defaultStringValue = fixture.Create<string>();
3737
var defaultIntegerValue = fixture.Create<int>();
3838
var defaultDoubleValue = fixture.Create<double>();
39-
var defaultStructureValue = fixture.Create<Structure>();
39+
var defaultStructureValue = fixture.Create<Value>();
4040
var provider = new NoOpFeatureProvider();
4141

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

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

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

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

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

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

9090
var provider = providerMock.Object;
9191

test/OpenFeature.SDK.Tests/OpenFeatureClientTests.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public async Task OpenFeatureClient_Should_Allow_Flag_Evaluation()
6868
var defaultStringValue = fixture.Create<string>();
6969
var defaultIntegerValue = fixture.Create<int>();
7070
var defaultDoubleValue = fixture.Create<double>();
71-
var defaultStructureValue = fixture.Create<Structure>();
71+
var defaultStructureValue = fixture.Create<Value>();
7272
var emptyFlagOptions = new FlagEvaluationOptions(new List<Hook>(), new Dictionary<string, object>());
7373

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

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

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

@@ -302,12 +302,12 @@ public async Task Should_Resolve_StructureValue()
302302
var clientName = fixture.Create<string>();
303303
var clientVersion = fixture.Create<string>();
304304
var flagName = fixture.Create<string>();
305-
var defaultValue = fixture.Create<Structure>();
305+
var defaultValue = fixture.Create<Value>();
306306

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

319-
(await client.GetObjectValue(flagName, defaultValue)).Should().Equal(defaultValue);
319+
(await client.GetObjectValue(flagName, defaultValue)).Should().Be(defaultValue);
320320

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

333333
var featureProviderMock = new Mock<FeatureProvider>(MockBehavior.Strict);
334334
featureProviderMock

test/OpenFeature.SDK.Tests/TestImplementations.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKe
7070
return Task.FromResult(new ResolutionDetails<double>(flagKey, defaultValue));
7171
}
7272

73-
public override Task<ResolutionDetails<Structure>> ResolveStructureValue(string flagKey, Structure defaultValue,
73+
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue,
7474
EvaluationContext context = null)
7575
{
76-
return Task.FromResult(new ResolutionDetails<Structure>(flagKey, defaultValue));
76+
return Task.FromResult(new ResolutionDetails<Value>(flagKey, defaultValue));
7777
}
7878
}
7979
}

test/OpenFeature.SDK.Tests/ValueTests.cs

+51-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,64 @@ namespace OpenFeature.SDK.Tests
77
{
88
public class ValueTests
99
{
10+
class Foo { }
11+
1012
[Fact]
1113
public void No_Arg_Should_Contain_Null()
1214
{
1315
Value value = new Value();
1416
Assert.True(value.IsNull());
1517
}
1618

19+
[Fact]
20+
public void Object_Arg_Should_Contain_Object()
21+
{
22+
try
23+
{
24+
// int is a special case, see Int_Object_Arg_Should_Contain_Object()
25+
IList<Object> list = new List<Object>(){
26+
true, "val", .5, new Structure(), new List<Value>(), DateTime.Now
27+
};
28+
29+
int i = 0;
30+
foreach (Object l in list)
31+
{
32+
Value value = new Value(l);
33+
Assert.Equal(list[i], value.AsObject());
34+
i++;
35+
}
36+
}
37+
catch (Exception)
38+
{
39+
Assert.True(false, "Expected no exception.");
40+
}
41+
}
42+
43+
[Fact]
44+
public void Int_Object_Arg_Should_Contain_Object()
45+
{
46+
try
47+
{
48+
int innerValue = 1;
49+
Value value = new Value(innerValue);
50+
Assert.True(value.IsNumber());
51+
Assert.Equal(innerValue, value.AsInteger());
52+
}
53+
catch (Exception)
54+
{
55+
Assert.True(false, "Expected no exception.");
56+
}
57+
}
58+
59+
[Fact]
60+
public void Invalid_Object_Should_Throw()
61+
{
62+
Assert.Throws<ArgumentException>(() =>
63+
{
64+
return new Value(new Foo());
65+
});
66+
}
67+
1768
[Fact]
1869
public void Bool_Arg_Should_Contain_Bool()
1970
{
@@ -48,7 +99,6 @@ public void String_Arg_Should_Contain_String()
4899
Assert.Equal(innerValue, value.AsString());
49100
}
50101

51-
52102
[Fact]
53103
public void DateTime_Arg_Should_Contain_DateTime()
54104
{

0 commit comments

Comments
 (0)