diff --git a/src/OpenApi/src/Comparers/ComparerHelpers.cs b/src/OpenApi/src/Comparers/ComparerHelpers.cs new file mode 100644 index 000000000000..b377fb7be49a --- /dev/null +++ b/src/OpenApi/src/Comparers/ComparerHelpers.cs @@ -0,0 +1,63 @@ +// 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(IDictionary x, IDictionary y, IEqualityComparer 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(IList x, IList y, IEqualityComparer 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; + } + + internal static bool ByteArrayEquals(byte[] x, byte[] y) + { + if (x.Length != y.Length) + { + return false; + } + + for (var i = 0; i < x.Length; i++) + { + if (!Equals(x[i], y[i])) + { + return false; + } + } + + return true; + } +} diff --git a/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs b/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs index 7990446ab26e..90ce955ba1f1 100644 --- a/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiAnyComparer.cs @@ -1,12 +1,12 @@ // 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.Interfaces; namespace Microsoft.AspNetCore.OpenApi; -internal sealed class OpenApiAnyComparer : IEqualityComparer +internal sealed class OpenApiAnyComparer : IEqualityComparer, IEqualityComparer { public static OpenApiAnyComparer Instance { get; } = new OpenApiAnyComparer(); @@ -29,9 +29,9 @@ 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.ContainsKey(key) && Equals(objectX[key], objectY[key])), - OpenApiBinary binaryX => y is OpenApiBinary binaryY && binaryX.Value.SequenceEqual(binaryY.Value), + 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 && ComparerHelpers.ByteArrayEquals(binaryX.Value, binaryY.Value), OpenApiInteger integerX => y is OpenApiInteger integerY && integerX.Value == integerY.Value, OpenApiLong longX => y is OpenApiLong longY && longX.Value == longY.Value, OpenApiDouble doubleX => y is OpenApiDouble doubleY && doubleX.Value == doubleY.Value, @@ -39,13 +39,38 @@ public bool Equals(IOpenApiAny? x, IOpenApiAny? y) OpenApiBoolean booleanX => y is OpenApiBoolean booleanY && booleanX.Value == booleanY.Value, OpenApiString stringX => y is OpenApiString stringY && stringX.Value == stringY.Value, OpenApiPassword passwordX => y is OpenApiPassword passwordY && passwordX.Value == passwordY.Value, - OpenApiByte byteX => y is OpenApiByte byteY && byteX.Value.SequenceEqual(byteY.Value), + OpenApiByte byteX => y is OpenApiByte byteY && ComparerHelpers.ByteArrayEquals(byteX.Value, byteY.Value), OpenApiDate dateX => y is OpenApiDate dateY && dateX.Value == dateY.Value, OpenApiDateTime dateTimeX => y is OpenApiDateTime dateTimeY && dateTimeX.Value == dateTimeY.Value, _ => x.Equals(y) }); } + 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 false; + } + public int GetHashCode(IOpenApiAny obj) { var hashCode = new HashCode(); @@ -78,4 +103,14 @@ public int GetHashCode(IOpenApiAny obj) return hashCode.ToHashCode(); } + + public int GetHashCode(IOpenApiExtension obj) + { + if (obj is IOpenApiAny any) + { + return GetHashCode(any); + } + + return obj.GetHashCode(); + } } diff --git a/src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs b/src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs index f81c13589b44..c4547b07d591 100644 --- a/src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs @@ -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; @@ -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.ContainsKey(key) && x.Mapping[key] == y.Mapping[key]); + ComparerHelpers.DictionaryEquals(x.Mapping, y.Mapping, StringComparer.Ordinal); } public int GetHashCode(OpenApiDiscriminator obj) diff --git a/src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs b/src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs index 493b5e154be3..1d724d74fc7c 100644 --- a/src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs @@ -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; @@ -27,8 +26,8 @@ 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.ContainsKey(k) && y.Extensions[k] == x.Extensions[k]); + x.Extensions.Count == y.Extensions.Count && + ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance); } public int GetHashCode(OpenApiExternalDocs obj) diff --git a/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs b/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs index daf42772d89c..257e2e798dd8 100644 --- a/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs @@ -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; @@ -28,8 +26,8 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y) return 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 && @@ -37,10 +35,10 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y) 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.ContainsKey(k) && x.Extensions[k] is IOpenApiAny anyX && y.Extensions[k] is IOpenApiAny anyY && OpenApiAnyComparer.Instance.Equals(anyX, anyY)) && + x.Extensions.Count == y.Extensions.Count && + 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) && x.Format == y.Format && Instance.Equals(x.Items, y.Items) && x.Title == y.Title && @@ -54,13 +52,13 @@ 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 && - x.Properties.Keys.All(k => y.Properties.ContainsKey(k) && Instance.Equals(x.Properties[k], y.Properties[k])) && + ComparerHelpers.DictionaryEquals(x.Properties, y.Properties, Instance) && x.ReadOnly == y.ReadOnly && - x.Required.Order().SequenceEqual(y.Required.Order()) && + RequiredEquals(x.Required, y.Required) && OpenApiReferenceComparer.Instance.Equals(x.Reference, y.Reference) && x.UniqueItems == y.UniqueItems && x.UnresolvedReference == y.UnresolvedReference && @@ -68,6 +66,16 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y) OpenApiXmlComparer.Instance.Equals(x.Xml, y.Xml); } + internal static bool RequiredEquals(ISet x, ISet y) + { + if (x.Count != y.Count) + { + return false; + } + + return x.SetEquals(y); + } + public int GetHashCode(OpenApiSchema obj) { var hashCode = new HashCode(); diff --git a/src/OpenApi/src/Comparers/OpenApiXmlComparer.cs b/src/OpenApi/src/Comparers/OpenApiXmlComparer.cs index 4ae4d9ea3ddd..547e5d17421a 100644 --- a/src/OpenApi/src/Comparers/OpenApiXmlComparer.cs +++ b/src/OpenApi/src/Comparers/OpenApiXmlComparer.cs @@ -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; @@ -30,8 +29,8 @@ 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.ContainsKey(k) && y.Extensions[k] == x.Extensions[k]); + x.Extensions.Count == y.Extensions.Count && + ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance); } public int GetHashCode(OpenApiXml obj)