Skip to content

Commit

Permalink
Merge pull request #57 from open-feature/breaking-changes
Browse files Browse the repository at this point in the history
BREAKING: add structure->value, object value constructor
  • Loading branch information
toddbaert authored Sep 13, 2022
2 parents 2768d4c + 9f8bdf5 commit 4a845fc
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 91 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);
}
}
55 changes: 41 additions & 14 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 @@ -69,91 +90,97 @@ public class Value
/// Determines if inner value is null
/// </summary>
/// <returns><see cref="bool">True if value is null</see></returns>
public bool IsNull() => this._innerValue is null;
public bool IsNull => this._innerValue is null;

/// <summary>
/// Determines if inner value is bool
/// </summary>
/// <returns><see cref="bool">True if value is bool</see></returns>
public bool IsBoolean() => this._innerValue is bool;
public bool IsBoolean => this._innerValue is bool;

/// <summary>
/// Determines if inner value is numeric
/// </summary>
/// <returns><see cref="bool">True if value is double</see></returns>
public bool IsNumber() => this._innerValue is double;
public bool IsNumber => this._innerValue is double;

/// <summary>
/// Determines if inner value is string
/// </summary>
/// <returns><see cref="bool">True if value is string</see></returns>
public bool IsString() => this._innerValue is string;
public bool IsString => this._innerValue is string;

/// <summary>
/// Determines if inner value is <see cref="Structure">Structure</see>
/// </summary>
/// <returns><see cref="bool">True if value is <see cref="Structure">Structure</see></see></returns>
public bool IsStructure() => this._innerValue is Structure;
public bool IsStructure => this._innerValue is Structure;

/// <summary>
/// Determines if inner value is list
/// </summary>
/// <returns><see cref="bool">True if value is list</see></returns>
public bool IsList() => this._innerValue is IList;
public bool IsList => this._innerValue is IList;

/// <summary>
/// Determines if inner value is DateTime
/// </summary>
/// <returns><see cref="bool">True if value is DateTime</see></returns>
public bool IsDateTime() => this._innerValue is DateTime;
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
/// </summary>
/// <returns>Value as int</returns>
public int? AsInteger() => this.IsNumber() ? (int?)Convert.ToInt32((double?)this._innerValue) : null;
public int? AsInteger => this.IsNumber ? (int?)Convert.ToInt32((double?)this._innerValue) : null;

/// <summary>
/// Returns the underlying bool value
/// Value will be null if it isn't a bool
/// </summary>
/// <returns>Value as bool</returns>
public bool? AsBoolean() => this.IsBoolean() ? (bool?)this._innerValue : null;
public bool? AsBoolean => this.IsBoolean ? (bool?)this._innerValue : null;

/// <summary>
/// Returns the underlying double value
/// Value will be null if it isn't a double
/// </summary>
/// <returns>Value as int</returns>
public double? AsDouble() => this.IsNumber() ? (double?)this._innerValue : null;
public double? AsDouble => this.IsNumber ? (double?)this._innerValue : null;

/// <summary>
/// Returns the underlying string value
/// Value will be null if it isn't a string
/// </summary>
/// <returns>Value as string</returns>
public string AsString() => this.IsString() ? (string)this._innerValue : null;
public string AsString => this.IsString ? (string)this._innerValue : null;

/// <summary>
/// Returns the underlying Structure value
/// Value will be null if it isn't a Structure
/// </summary>
/// <returns>Value as Structure</returns>
public Structure AsStructure() => this.IsStructure() ? (Structure)this._innerValue : null;
public Structure AsStructure => this.IsStructure ? (Structure)this._innerValue : null;

/// <summary>
/// Returns the underlying List value
/// Value will be null if it isn't a List
/// </summary>
/// <returns>Value as List</returns>
public IList<Value> AsList() => this.IsList() ? (IList<Value>)this._innerValue : null;
public IList<Value> AsList => this.IsList ? (IList<Value>)this._innerValue : null;

/// <summary>
/// Returns the underlying DateTime value
/// Value will be null if it isn't a DateTime
/// </summary>
/// <returns>Value as DateTime</returns>
public DateTime? AsDateTime() => this.IsDateTime() ? (DateTime?)this._innerValue : null;
public DateTime? AsDateTime => this.IsDateTime ? (DateTime?)this._innerValue : null;
}
}
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
18 changes: 9 additions & 9 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 Expand Up @@ -362,7 +362,7 @@ public void Should_Get_And_Set_Context()
var VAL = 1;
FeatureClient client = OpenFeature.Instance.GetClient();
client.SetContext(new EvaluationContext().Add(KEY, VAL));
Assert.Equal(VAL, client.GetContext().GetValue(KEY).AsInteger());
Assert.Equal(VAL, client.GetContext().GetValue(KEY).AsInteger);
}
}
}
Loading

0 comments on commit 4a845fc

Please sign in to comment.