Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/OpenApi/src/Comparers/ComparerHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.OpenApi;

internal static class ComparerHelpers
{
internal static bool DictionaryEquals<TKey, TValue>(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y, IEqualityComparer<TValue> comparer)
where TKey : notnull
where TValue : notnull
{
if (x is Dictionary<TKey, TValue> xDictionary && y is Dictionary<TKey, TValue> yDictionary)
{
return DictionaryEquals(xDictionary, yDictionary, comparer);
}

if (x.Keys.Count != y.Keys.Count)
{
return false;
}

foreach (var key in x.Keys)
{
if (!y.TryGetValue(key, out var value) || !comparer.Equals(x[key], value))
{
return false;
}
}

return true;
}

// Private method to avoid interface dispatch.
private static bool DictionaryEquals<TKey, TValue>(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y, IEqualityComparer<TValue> comparer)
where TKey : notnull
where TValue : notnull
{
if (x.Keys.Count != y.Keys.Count)
{
return false;
}

foreach (var key in x.Keys)
{
if (!y.TryGetValue(key, out var value) || !comparer.Equals(x[key], value))
{
return false;
}
}

return true;
}

internal static bool ListEquals<T>(IList<T> x, IList<T> y, IEqualityComparer<T> comparer)
{
if (x is List<T> xList && y is List<T> yList)
{
return ListEquals(xList, yList, comparer);
}

if (x.Count != y.Count)
{
return false;
}

for (var i = 0; i < x.Count; i++)
{
if (!comparer.Equals(x[i], y[i]))
{
return false;
}
}

return true;
}

// Private method to avoid interface dispatch.
private static bool ListEquals<T>(List<T> x, List<T> y, IEqualityComparer<T> comparer)
{
if (x.Count != y.Count)
{
return false;
}

for (var i = 0; i < x.Count; i++)
{
if (!comparer.Equals(x[i], y[i]))
{
return false;
}
}

return true;
}
}
42 changes: 39 additions & 3 deletions src/OpenApi/src/Comparers/OpenApiAnyComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;

namespace Microsoft.AspNetCore.OpenApi;

internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>
internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>, IEqualityComparer<IOpenApiExtension>
{
public static OpenApiAnyComparer Instance { get; } = new OpenApiAnyComparer();

Expand All @@ -29,8 +30,8 @@ public bool Equals(IOpenApiAny? x, IOpenApiAny? y)
(x switch
{
OpenApiNull _ => y is OpenApiNull,
OpenApiArray arrayX => y is OpenApiArray arrayY && arrayX.SequenceEqual(arrayY, Instance),
OpenApiObject objectX => y is OpenApiObject objectY && objectX.Keys.Count == objectY.Keys.Count && objectX.Keys.All(key => objectY.TryGetValue(key, out var yValue) && Equals(objectX[key], yValue)),
OpenApiArray arrayX => y is OpenApiArray arrayY && ComparerHelpers.ListEquals(arrayX, arrayY, Instance),
OpenApiObject objectX => y is OpenApiObject objectY && ComparerHelpers.DictionaryEquals(objectX, objectY, Instance),
OpenApiBinary binaryX => y is OpenApiBinary binaryY && binaryX.Value.SequenceEqual(binaryY.Value),
OpenApiInteger integerX => y is OpenApiInteger integerY && integerX.Value == integerY.Value,
OpenApiLong longX => y is OpenApiLong longY && longX.Value == longY.Value,
Expand Down Expand Up @@ -78,4 +79,39 @@ public int GetHashCode(IOpenApiAny obj)

return hashCode.ToHashCode();
}

public bool Equals(IOpenApiExtension? x, IOpenApiExtension? y)
{
if (x is null && y is null)
{
return true;
}

if (x is null || y is null)
{
return false;
}

if (object.ReferenceEquals(x, y))
{
return true;
}

if (x is IOpenApiAny openApiAnyX && y is IOpenApiAny openApiAnyY)
{
return Equals(openApiAnyX, openApiAnyY);
}

return x.Equals(y);
}

public int GetHashCode(IOpenApiExtension obj)
{
if (obj is IOpenApiAny any)
{
return GetHashCode(any);
}

return obj.GetHashCode();
}
}
3 changes: 1 addition & 2 deletions src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.OpenApi.Models;

namespace Microsoft.AspNetCore.OpenApi;
Expand All @@ -27,7 +26,7 @@ public bool Equals(OpenApiDiscriminator? x, OpenApiDiscriminator? y)

return x.PropertyName == y.PropertyName &&
x.Mapping.Count == y.Mapping.Count &&
x.Mapping.Keys.All(key => y.Mapping.TryGetValue(key, out var yValue) && x.Mapping[key] == yValue);
ComparerHelpers.DictionaryEquals(x.Mapping, y.Mapping, StringComparer.Ordinal);
}

public int GetHashCode(OpenApiDiscriminator obj)
Expand Down
4 changes: 1 addition & 3 deletions src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.OpenApi.Models;

namespace Microsoft.AspNetCore.OpenApi;
Expand All @@ -27,8 +26,7 @@ public bool Equals(OpenApiExternalDocs? x, OpenApiExternalDocs? y)

return x.Description == y.Description &&
x.Url == y.Url &&
x.Extensions.Count == y.Extensions.Count
&& x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && yValue == x.Extensions[k]);
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance);
}

public int GetHashCode(OpenApiExternalDocs obj)
Expand Down
14 changes: 6 additions & 8 deletions src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;

namespace Microsoft.AspNetCore.OpenApi;
Expand Down Expand Up @@ -31,22 +29,22 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
x.Type == y.Type &&
x.Format == y.Format &&
SchemaIdEquals(x, y) &&
x.Properties.Keys.All(k => y.Properties.TryGetValue(k, out var yValue) && Instance.Equals(x.Properties[k], yValue)) &&
ComparerHelpers.DictionaryEquals(x.Properties, y.Properties, Instance) &&
OpenApiDiscriminatorComparer.Instance.Equals(x.Discriminator, y.Discriminator) &&
Instance.Equals(x.AdditionalProperties, y.AdditionalProperties) &&
x.AdditionalPropertiesAllowed == y.AdditionalPropertiesAllowed &&
x.AllOf.SequenceEqual(y.AllOf, Instance) &&
x.AnyOf.SequenceEqual(y.AnyOf, Instance) &&
ComparerHelpers.ListEquals(x.AllOf, y.AllOf, Instance) &&
ComparerHelpers.ListEquals(x.AnyOf, y.AnyOf, Instance) &&
x.Deprecated == y.Deprecated &&
OpenApiAnyComparer.Instance.Equals(x.Default, y.Default) &&
x.Description == y.Description &&
OpenApiAnyComparer.Instance.Equals(x.Example, y.Example) &&
x.ExclusiveMaximum == y.ExclusiveMaximum &&
x.ExclusiveMinimum == y.ExclusiveMinimum &&
x.Extensions.Count == y.Extensions.Count &&
x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && x.Extensions[k] is IOpenApiAny anyX && yValue is IOpenApiAny anyY && OpenApiAnyComparer.Instance.Equals(anyX, anyY)) &&
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance) &&
OpenApiExternalDocsComparer.Instance.Equals(x.ExternalDocs, y.ExternalDocs) &&
x.Enum.SequenceEqual(y.Enum, OpenApiAnyComparer.Instance) &&
ComparerHelpers.ListEquals(x.Enum, y.Enum, OpenApiAnyComparer.Instance) &&
Instance.Equals(x.Items, y.Items) &&
x.Title == y.Title &&
x.Maximum == y.Maximum &&
Expand All @@ -58,7 +56,7 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
x.MinLength == y.MinLength &&
x.MinProperties == y.MinProperties &&
x.MultipleOf == y.MultipleOf &&
x.OneOf.SequenceEqual(y.OneOf, Instance) &&
ComparerHelpers.ListEquals(x.OneOf, y.OneOf, Instance) &&
Instance.Equals(x.Not, y.Not) &&
x.Nullable == y.Nullable &&
x.Pattern == y.Pattern &&
Expand Down
4 changes: 1 addition & 3 deletions src/OpenApi/src/Comparers/OpenApiXmlComparer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.OpenApi.Models;

namespace Microsoft.AspNetCore.OpenApi;
Expand Down Expand Up @@ -30,8 +29,7 @@ public bool Equals(OpenApiXml? x, OpenApiXml? y)
x.Prefix == y.Prefix &&
x.Attribute == y.Attribute &&
x.Wrapped == y.Wrapped &&
x.Extensions.Count == y.Extensions.Count
&& x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && yValue == x.Extensions[k]);
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance);
}

public int GetHashCode(OpenApiXml obj)
Expand Down