Skip to content

Commit 883f4f3

Browse files
authored
chore: Add comparison to Value (#523)
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com> <!-- Please use this template for your pull request. --> <!-- Please use the sections that you need and delete other sections --> ## This PR <!-- add the description of the PR here --> This pull request introduces enhancements to the `Value` class in the `OpenFeature.Model` namespace, ensuring better equality handling, and updates dependencies to include `Microsoft.Bcl.HashCode`. The most significant changes include implementing equality comparison for `Value`, adding hash code generation, and updating project files to include the new dependency. ### Enhancements to `Value` class: * [`src/OpenFeature/Model/Value.cs`](diffhunk://#diff-336aacd3c42458899187108a2064648ae21e439b2b11e6ca7f25b7b7fef00609L9-R9): The `Value` class now implements `IEquatable<Value>` and includes methods for equality comparison (`Equals`, `==`, `!=`), hash code generation (`GetHashCode`), and internal helpers for comparing complex types like structures and lists. This ensures more robust and consistent equality checks. [[1]](diffhunk://#diff-336aacd3c42458899187108a2064648ae21e439b2b11e6ca7f25b7b7fef00609L9-R9) [[2]](diffhunk://#diff-336aacd3c42458899187108a2064648ae21e439b2b11e6ca7f25b7b7fef00609R187-R378) ### Dependency updates: * [`Directory.Packages.props`](diffhunk://#diff-5baf5f9e448ad54ab25a091adee0da05d4d228481c9200518fcb1b53a65d4156L13-R25): Added `Microsoft.Bcl.HashCode` as a dependency for hash code generation. Other package references were reformatted for consistency. * [`src/OpenFeature/OpenFeature.csproj`](diffhunk://#diff-711ea17cbdebe419375c7684c8c39a1423d2bebcf8976ddd7bdd78deaab65b21R11): Included `Microsoft.Bcl.HashCode` for specific target frameworks (`net462` and `netstandard2.0`). ### Notes <!-- any additional notes for this PR --> This implementation is necessary for the comparison in the MultiProvider. See: #488 (comment) --------- Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
1 parent 2e70072 commit 883f4f3

File tree

4 files changed

+763
-7
lines changed

4 files changed

+763
-7
lines changed

Directory.Packages.props

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010

1111
<ItemGroup Label="src">
1212
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="$(MicrosoftExtensionsVersion)" />
13-
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
14-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
15-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsVersion)" />
16-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsVersion)" />
13+
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
14+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions"
15+
Version="$(MicrosoftExtensionsVersion)" />
16+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions"
17+
Version="$(MicrosoftExtensionsVersion)" />
18+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions"
19+
Version="$(MicrosoftExtensionsVersion)" />
20+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection"
21+
Version="$(MicrosoftExtensionsVersion)" />
1722
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsVersion)" />
1823
<PackageVersion Include="System.Collections.Immutable" Version="$(MicrosoftExtensionsVersion)" />
19-
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="$(MicrosoftExtensionsVersion)" />
24+
<PackageVersion Include="System.Diagnostics.DiagnosticSource"
25+
Version="$(MicrosoftExtensionsVersion)" />
2026
<PackageVersion Include="System.Threading.Channels" Version="$(MicrosoftExtensionsVersion)" />
2127
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
2228
</ItemGroup>
@@ -42,4 +48,4 @@
4248
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
4349
</ItemGroup>
4450

45-
</Project>
51+
</Project>

src/OpenFeature/Model/Value.cs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace OpenFeature.Model;
66
/// Values serve as a return type for provider objects. Providers may deal in JSON, protobuf, XML or some other data-interchange format.
77
/// This intermediate representation provides a good medium of exchange.
88
/// </summary>
9-
public sealed class Value
9+
public sealed class Value : IEquatable<Value>
1010
{
1111
private readonly object? _innerValue;
1212

@@ -184,4 +184,196 @@ public Value(Object value)
184184
/// </summary>
185185
/// <returns>Value as DateTime</returns>
186186
public DateTime? AsDateTime => this.IsDateTime ? (DateTime?)this._innerValue : null;
187+
188+
/// <summary>
189+
/// Determines whether the specified <see cref="Value"/> is equal to the current <see cref="Value"/>.
190+
/// </summary>
191+
/// <param name="other">The <see cref="Value"/> to compare with the current <see cref="Value"/>.</param>
192+
/// <returns>true if the specified <see cref="Value"/> is equal to the current <see cref="Value"/>; otherwise, false.</returns>
193+
public bool Equals(Value? other)
194+
{
195+
if (other is null) return false;
196+
if (ReferenceEquals(this, other)) return true;
197+
198+
// Both are null
199+
if (this.IsNull && other.IsNull) return true;
200+
201+
// One is null, the other is not
202+
if (this.IsNull != other.IsNull) return false;
203+
204+
// Different types
205+
if (this.GetValueType() != other.GetValueType()) return false;
206+
207+
// Compare based on type
208+
return this.GetValueType() switch
209+
{
210+
ValueType.Boolean => this.AsBoolean == other.AsBoolean,
211+
ValueType.Number => this.AsDouble == other.AsDouble,
212+
ValueType.String => this.AsString == other.AsString,
213+
ValueType.DateTime => this.AsDateTime == other.AsDateTime,
214+
ValueType.Structure => this.StructureEquals(other),
215+
ValueType.List => this.ListEquals(other),
216+
_ => false
217+
};
218+
}
219+
220+
/// <summary>
221+
/// Determines whether the specified object is equal to the current <see cref="Value"/>.
222+
/// </summary>
223+
/// <param name="obj">The object to compare with the current <see cref="Value"/>.</param>
224+
/// <returns>true if the specified object is equal to the current <see cref="Value"/>; otherwise, false.</returns>
225+
public override bool Equals(object? obj) => this.Equals(obj as Value);
226+
227+
/// <summary>
228+
/// Returns the hash code for this <see cref="Value"/>.
229+
/// </summary>
230+
/// <returns>A hash code for the current <see cref="Value"/>.</returns>
231+
public override int GetHashCode()
232+
{
233+
if (this.IsNull) return 0;
234+
235+
return this.GetValueType() switch
236+
{
237+
ValueType.Boolean => this.AsBoolean!.GetHashCode(),
238+
ValueType.Number => this.AsDouble!.GetHashCode(),
239+
ValueType.String => this.AsString!.GetHashCode(),
240+
ValueType.DateTime => this.AsDateTime!.GetHashCode(),
241+
ValueType.Structure => this.GetStructureHashCode(),
242+
ValueType.List => this.GetListHashCode(),
243+
_ => 0
244+
};
245+
}
246+
247+
/// <summary>
248+
/// Determines whether two <see cref="Value"/> instances are equal.
249+
/// </summary>
250+
/// <param name="left">The first <see cref="Value"/> to compare.</param>
251+
/// <param name="right">The second <see cref="Value"/> to compare.</param>
252+
/// <returns>true if the values are equal; otherwise, false.</returns>
253+
public static bool operator ==(Value? left, Value? right)
254+
{
255+
if (left is null && right is null) return true;
256+
if (left is null || right is null) return false;
257+
return left.Equals(right);
258+
}
259+
260+
/// <summary>
261+
/// Determines whether two <see cref="Value"/> instances are not equal.
262+
/// </summary>
263+
/// <param name="left">The first <see cref="Value"/> to compare.</param>
264+
/// <param name="right">The second <see cref="Value"/> to compare.</param>
265+
/// <returns>true if the values are not equal; otherwise, false.</returns>
266+
public static bool operator !=(Value? left, Value? right) => !(left == right);
267+
268+
/// <summary>
269+
/// Gets the type of the current value.
270+
/// </summary>
271+
/// <returns>The <see cref="ValueType"/> of the current value.</returns>
272+
private ValueType GetValueType()
273+
{
274+
if (this.IsNull) return ValueType.Null;
275+
if (this.IsBoolean) return ValueType.Boolean;
276+
if (this.IsNumber) return ValueType.Number;
277+
if (this.IsString) return ValueType.String;
278+
if (this.IsDateTime) return ValueType.DateTime;
279+
if (this.IsStructure) return ValueType.Structure;
280+
if (this.IsList) return ValueType.List;
281+
return ValueType.Unknown;
282+
}
283+
284+
/// <summary>
285+
/// Compares two Structure values for equality.
286+
/// </summary>
287+
/// <param name="other">The other <see cref="Value"/> to compare.</param>
288+
/// <returns>true if the structures are equal; otherwise, false.</returns>
289+
private bool StructureEquals(Value other)
290+
{
291+
var thisStructure = this.AsStructure!;
292+
var otherStructure = other.AsStructure!;
293+
294+
if (thisStructure.Count != otherStructure.Count) return false;
295+
296+
foreach (var kvp in thisStructure)
297+
{
298+
if (!otherStructure.TryGetValue(kvp.Key, out var otherValue) || !kvp.Value.Equals(otherValue))
299+
{
300+
return false;
301+
}
302+
}
303+
304+
return true;
305+
}
306+
307+
/// <summary>
308+
/// Compares two List values for equality.
309+
/// </summary>
310+
/// <param name="other">The other <see cref="Value"/> to compare.</param>
311+
/// <returns>true if the lists are equal; otherwise, false.</returns>
312+
private bool ListEquals(Value other)
313+
{
314+
var thisList = this.AsList!;
315+
var otherList = other.AsList!;
316+
317+
if (thisList.Count != otherList.Count) return false;
318+
319+
for (int i = 0; i < thisList.Count; i++)
320+
{
321+
if (!thisList[i].Equals(otherList[i]))
322+
{
323+
return false;
324+
}
325+
}
326+
327+
return true;
328+
}
329+
330+
/// <summary>
331+
/// Gets the hash code for a Structure value.
332+
/// </summary>
333+
/// <returns>The hash code of the structure.</returns>
334+
private int GetStructureHashCode()
335+
{
336+
var structure = this.AsStructure!;
337+
var hash = new HashCode();
338+
339+
foreach (var kvp in structure)
340+
{
341+
hash.Add(kvp.Key);
342+
hash.Add(kvp.Value);
343+
}
344+
345+
return hash.ToHashCode();
346+
}
347+
348+
/// <summary>
349+
/// Gets the hash code for a List value.
350+
/// </summary>
351+
/// <returns>The hash code of the list.</returns>
352+
private int GetListHashCode()
353+
{
354+
var list = this.AsList!;
355+
var hash = new HashCode();
356+
357+
foreach (var item in list)
358+
{
359+
hash.Add(item);
360+
}
361+
362+
return hash.ToHashCode();
363+
}
364+
365+
/// <summary>
366+
/// Represents the different types that a <see cref="Value"/> can contain.
367+
/// </summary>
368+
private enum ValueType
369+
{
370+
Null,
371+
Boolean,
372+
Number,
373+
String,
374+
DateTime,
375+
Structure,
376+
List,
377+
Unknown
378+
}
187379
}

src/OpenFeature/OpenFeature.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0'" />
11+
<PackageReference Include="Microsoft.Bcl.HashCode" Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0'" />
1112
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
1213
<PackageReference Include="System.Diagnostics.DiagnosticSource" Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0'" />
1314
</ItemGroup>

0 commit comments

Comments
 (0)