Skip to content

Commit fd0a541

Browse files
authoredFeb 12, 2024
feat: Flag metadata (#223)
1 parent d792b32 commit fd0a541

File tree

6 files changed

+369
-4
lines changed

6 files changed

+369
-4
lines changed
 

‎src/OpenFeature/Model/BaseMetadata.cs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
5+
#nullable enable
6+
namespace OpenFeature.Model;
7+
8+
/// <summary>
9+
/// Represents the base class for metadata objects.
10+
/// </summary>
11+
public abstract class BaseMetadata
12+
{
13+
private readonly ImmutableDictionary<string, object> _metadata;
14+
15+
internal BaseMetadata(Dictionary<string, object> metadata)
16+
{
17+
this._metadata = metadata.ToImmutableDictionary();
18+
}
19+
20+
/// <summary>
21+
/// Gets the boolean value associated with the specified key.
22+
/// </summary>
23+
/// <param name="key">The key of the value to retrieve.</param>
24+
/// <returns>The boolean value associated with the key, or null if the key is not found.</returns>
25+
public virtual bool? GetBool(string key)
26+
{
27+
return this.GetValue<bool>(key);
28+
}
29+
30+
/// <summary>
31+
/// Gets the integer value associated with the specified key.
32+
/// </summary>
33+
/// <param name="key">The key of the value to retrieve.</param>
34+
/// <returns>The integer value associated with the key, or null if the key is not found.</returns>
35+
public virtual int? GetInt(string key)
36+
{
37+
return this.GetValue<int>(key);
38+
}
39+
40+
/// <summary>
41+
/// Gets the double value associated with the specified key.
42+
/// </summary>
43+
/// <param name="key">The key of the value to retrieve.</param>
44+
/// <returns>The double value associated with the key, or null if the key is not found.</returns>
45+
public virtual double? GetDouble(string key)
46+
{
47+
return this.GetValue<double>(key);
48+
}
49+
50+
/// <summary>
51+
/// Gets the string value associated with the specified key.
52+
/// </summary>
53+
/// <param name="key">The key of the value to retrieve.</param>
54+
/// <returns>The string value associated with the key, or null if the key is not found.</returns>
55+
public virtual string? GetString(string key)
56+
{
57+
var hasValue = this._metadata.TryGetValue(key, out var value);
58+
if (!hasValue)
59+
{
60+
return null;
61+
}
62+
63+
return value as string ?? null;
64+
}
65+
66+
private T? GetValue<T>(string key) where T : struct
67+
{
68+
var hasValue = this._metadata.TryGetValue(key, out var value);
69+
if (!hasValue)
70+
{
71+
return null;
72+
}
73+
74+
return value is T tValue ? tValue : null;
75+
}
76+
}

‎src/OpenFeature/Model/FlagEvaluationDetails.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace OpenFeature.Model
66
/// The contract returned to the caller that describes the result of the flag evaluation process.
77
/// </summary>
88
/// <typeparam name="T">Flag value type</typeparam>
9-
/// <seealso href="https://github.com/open-feature/spec/blob/v0.5.2/specification/types.md#evaluation-details"/>
9+
/// <seealso href="https://github.com/open-feature/spec/blob/v0.7.0/specification/types.md#evaluation-details"/>
1010
public sealed class FlagEvaluationDetails<T>
1111
{
1212
/// <summary>
@@ -45,6 +45,11 @@ public sealed class FlagEvaluationDetails<T>
4545
/// </summary>
4646
public string Variant { get; }
4747

48+
/// <summary>
49+
/// A structure which supports definition of arbitrary properties, with keys of type string, and values of type boolean, string, or number.
50+
/// </summary>
51+
public FlagMetadata FlagMetadata { get; }
52+
4853
/// <summary>
4954
/// Initializes a new instance of the <see cref="FlagEvaluationDetails{T}"/> class.
5055
/// </summary>
@@ -54,15 +59,17 @@ public sealed class FlagEvaluationDetails<T>
5459
/// <param name="reason">Reason</param>
5560
/// <param name="variant">Variant</param>
5661
/// <param name="errorMessage">Error message</param>
62+
/// <param name="flagMetadata">Flag metadata</param>
5763
public FlagEvaluationDetails(string flagKey, T value, ErrorType errorType, string reason, string variant,
58-
string errorMessage = null)
64+
string errorMessage = null, FlagMetadata flagMetadata = null)
5965
{
6066
this.Value = value;
6167
this.FlagKey = flagKey;
6268
this.ErrorType = errorType;
6369
this.Reason = reason;
6470
this.Variant = variant;
6571
this.ErrorMessage = errorMessage;
72+
this.FlagMetadata = flagMetadata;
6673
}
6774
}
6875
}

‎src/OpenFeature/Model/FlagMetadata.cs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
5+
#nullable enable
6+
namespace OpenFeature.Model;
7+
8+
/// <summary>
9+
/// Represents the metadata associated with a feature flag.
10+
/// </summary>
11+
/// <seealso href="https://github.com/open-feature/spec/blob/v0.7.0/specification/types.md#flag-metadata"/>
12+
public sealed class FlagMetadata : BaseMetadata
13+
{
14+
/// <summary>
15+
/// Constructor for the <see cref="BaseMetadata"/> class.
16+
/// </summary>
17+
public FlagMetadata() : this([])
18+
{
19+
}
20+
21+
/// <summary>
22+
/// Constructor for the <see cref="BaseMetadata"/> class.
23+
/// </summary>
24+
/// <param name="metadata">The dictionary containing the metadata.</param>
25+
public FlagMetadata(Dictionary<string, object> metadata) : base(metadata)
26+
{
27+
}
28+
}

‎src/OpenFeature/Model/ProviderEvents.cs

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class ProviderEventPayload
3636
/// <summary>
3737
/// Metadata information for the event.
3838
/// </summary>
39+
// TODO: This needs to be changed to a EventMetadata object
3940
public Dictionary<string, object> EventMetadata { get; set; }
4041
}
4142
}

‎src/OpenFeature/Model/ResolutionDetails.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace OpenFeature.Model
77
/// Describes the details of the feature flag being evaluated
88
/// </summary>
99
/// <typeparam name="T">Flag value type</typeparam>
10-
/// <seealso href="https://github.com/open-feature/spec/blob/v0.5.2/specification/types.md#resolution-details"/>
10+
/// <seealso href="https://github.com/open-feature/spec/blob/v0.7.0/specification/types.md#resolution-details"/>
1111
public sealed class ResolutionDetails<T>
1212
{
1313
/// <summary>
@@ -44,6 +44,11 @@ public sealed class ResolutionDetails<T>
4444
/// </summary>
4545
public string Variant { get; }
4646

47+
/// <summary>
48+
/// A structure which supports definition of arbitrary properties, with keys of type string, and values of type boolean, string, or number.
49+
/// </summary>
50+
public FlagMetadata FlagMetadata { get; }
51+
4752
/// <summary>
4853
/// Initializes a new instance of the <see cref="ResolutionDetails{T}"/> class.
4954
/// </summary>
@@ -53,15 +58,17 @@ public sealed class ResolutionDetails<T>
5358
/// <param name="reason">Reason</param>
5459
/// <param name="variant">Variant</param>
5560
/// <param name="errorMessage">Error message</param>
61+
/// <param name="flagMetadata">Flag metadata</param>
5662
public ResolutionDetails(string flagKey, T value, ErrorType errorType = ErrorType.None, string reason = null,
57-
string variant = null, string errorMessage = null)
63+
string variant = null, string errorMessage = null, FlagMetadata flagMetadata = null)
5864
{
5965
this.Value = value;
6066
this.FlagKey = flagKey;
6167
this.ErrorType = errorType;
6268
this.Reason = reason;
6369
this.Variant = variant;
6470
this.ErrorMessage = errorMessage;
71+
this.FlagMetadata = flagMetadata;
6572
}
6673
}
6774
}
+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using OpenFeature.Model;
4+
using OpenFeature.Tests.Internal;
5+
using Xunit;
6+
7+
#nullable enable
8+
namespace OpenFeature.Tests;
9+
10+
public class FlagMetadataTest
11+
{
12+
[Fact]
13+
[Specification("1.4.14",
14+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
15+
public void GetBool_Should_Return_Null_If_Key_Not_Found()
16+
{
17+
// Arrange
18+
var flagMetadata = new FlagMetadata();
19+
20+
// Act
21+
var result = flagMetadata.GetBool("nonexistentKey");
22+
23+
// Assert
24+
Assert.Null(result);
25+
}
26+
27+
[Fact]
28+
[Specification("1.4.14",
29+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
30+
[Specification("1.4.14.1", "Condition: `Flag metadata` MUST be immutable.")]
31+
public void GetBool_Should_Return_Value_If_Key_Found()
32+
{
33+
// Arrange
34+
var metadata = new Dictionary<string, object>
35+
{
36+
{
37+
"boolKey", true
38+
}
39+
};
40+
var flagMetadata = new FlagMetadata(metadata);
41+
42+
// Act
43+
var result = flagMetadata.GetBool("boolKey");
44+
45+
// Assert
46+
Assert.True(result);
47+
}
48+
49+
[Fact]
50+
[Specification("1.4.14",
51+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
52+
public void GetBool_Should_Throw_Value_Is_Invalid()
53+
{
54+
// Arrange
55+
var metadata = new Dictionary<string, object>
56+
{
57+
{
58+
"wrongKey", "11a"
59+
}
60+
};
61+
var flagMetadata = new FlagMetadata(metadata);
62+
63+
// Act
64+
var result = flagMetadata.GetBool("wrongKey");
65+
66+
// Assert
67+
Assert.Null(result);
68+
}
69+
70+
[Fact]
71+
[Specification("1.4.14",
72+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
73+
public void GetInt_Should_Return_Null_If_Key_Not_Found()
74+
{
75+
// Arrange
76+
var flagMetadata = new FlagMetadata();
77+
78+
// Act
79+
var result = flagMetadata.GetInt("nonexistentKey");
80+
81+
// Assert
82+
Assert.Null(result);
83+
}
84+
85+
[Fact]
86+
[Specification("1.4.14",
87+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
88+
[Specification("1.4.14.1", "Condition: `Flag metadata` MUST be immutable.")]
89+
public void GetInt_Should_Return_Value_If_Key_Found()
90+
{
91+
// Arrange
92+
var metadata = new Dictionary<string, object>
93+
{
94+
{
95+
"intKey", 1
96+
}
97+
};
98+
var flagMetadata = new FlagMetadata(metadata);
99+
100+
// Act
101+
var result = flagMetadata.GetInt("intKey");
102+
103+
// Assert
104+
Assert.NotNull(result);
105+
Assert.Equal(1, result);
106+
}
107+
108+
[Fact]
109+
[Specification("1.4.14",
110+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
111+
public void GetInt_Should_Throw_Value_Is_Invalid()
112+
{
113+
// Arrange
114+
var metadata = new Dictionary<string, object>
115+
{
116+
{
117+
"wrongKey", "11a"
118+
}
119+
};
120+
var flagMetadata = new FlagMetadata(metadata);
121+
122+
// Act
123+
var result = flagMetadata.GetInt("wrongKey");
124+
125+
// Assert
126+
Assert.Null(result);
127+
}
128+
129+
[Fact]
130+
[Specification("1.4.14",
131+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
132+
public void GetDouble_Should_Return_Null_If_Key_Not_Found()
133+
{
134+
// Arrange
135+
var flagMetadata = new FlagMetadata();
136+
137+
// Act
138+
var result = flagMetadata.GetDouble("nonexistentKey");
139+
140+
// Assert
141+
Assert.Null(result);
142+
}
143+
144+
[Fact]
145+
[Specification("1.4.14",
146+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
147+
[Specification("1.4.14.1", "Condition: `Flag metadata` MUST be immutable.")]
148+
public void GetDouble_Should_Return_Value_If_Key_Found()
149+
{
150+
// Arrange
151+
var metadata = new Dictionary<string, object>
152+
{
153+
{
154+
"doubleKey", 1.2
155+
}
156+
};
157+
var flagMetadata = new FlagMetadata(metadata);
158+
159+
// Act
160+
var result = flagMetadata.GetDouble("doubleKey");
161+
162+
// Assert
163+
Assert.NotNull(result);
164+
Assert.Equal(1.2, result);
165+
}
166+
167+
[Fact]
168+
[Specification("1.4.14",
169+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
170+
public void GetDouble_Should_Throw_Value_Is_Invalid()
171+
{
172+
// Arrange
173+
var metadata = new Dictionary<string, object>
174+
{
175+
{
176+
"wrongKey", "11a"
177+
}
178+
};
179+
var flagMetadata = new FlagMetadata(metadata);
180+
181+
// Act
182+
var result = flagMetadata.GetDouble("wrongKey");
183+
184+
// Assert
185+
Assert.Null(result);
186+
}
187+
188+
[Fact]
189+
[Specification("1.4.14",
190+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
191+
public void GetString_Should_Return_Null_If_Key_Not_Found()
192+
{
193+
// Arrange
194+
var flagMetadata = new FlagMetadata();
195+
196+
// Act
197+
var result = flagMetadata.GetString("nonexistentKey");
198+
199+
// Assert
200+
Assert.Null(result);
201+
}
202+
203+
[Fact]
204+
[Specification("1.4.14",
205+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
206+
[Specification("1.4.14.1", "Condition: `Flag metadata` MUST be immutable.")]
207+
public void GetString_Should_Return_Value_If_Key_Found()
208+
{
209+
// Arrange
210+
var metadata = new Dictionary<string, object>
211+
{
212+
{
213+
"stringKey", "11"
214+
}
215+
};
216+
var flagMetadata = new FlagMetadata(metadata);
217+
218+
// Act
219+
var result = flagMetadata.GetString("stringKey");
220+
221+
// Assert
222+
Assert.NotNull(result);
223+
Assert.Equal("11", result);
224+
}
225+
226+
[Fact]
227+
[Specification("1.4.14",
228+
"If the `flag metadata` field in the `flag resolution` structure returned by the configured `provider` is set, the `evaluation details` structure's `flag metadata` field MUST contain that value. Otherwise, it MUST contain an empty record.")]
229+
public void GetString_Should_Throw_Value_Is_Invalid()
230+
{
231+
// Arrange
232+
var metadata = new Dictionary<string, object>
233+
{
234+
{
235+
"wrongKey", new object()
236+
}
237+
};
238+
var flagMetadata = new FlagMetadata(metadata);
239+
240+
// Act
241+
var result = flagMetadata.GetString("wrongKey");
242+
243+
// Assert
244+
Assert.Null(result);
245+
}
246+
}

0 commit comments

Comments
 (0)
Please sign in to comment.