Skip to content

Commit 623fa19

Browse files
committed
Harden OpenApi comparer implementations
1 parent 7372bb8 commit 623fa19

12 files changed

+111
-150
lines changed

src/OpenApi/src/Comparers/OpenApiAnyComparer.cs

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Linq;
45
using Microsoft.OpenApi.Any;
56

67
namespace Microsoft.AspNetCore.OpenApi;
@@ -24,7 +25,25 @@ public bool Equals(IOpenApiAny? x, IOpenApiAny? y)
2425
return true;
2526
}
2627

27-
return GetHashCode(x) == GetHashCode(y);
28+
return x.AnyType == y.AnyType &&
29+
(x switch
30+
{
31+
OpenApiNull _ => y is OpenApiNull,
32+
OpenApiArray arrayX => y is OpenApiArray arrayY && arrayX.SequenceEqual(arrayY, Instance),
33+
OpenApiObject objectX => y is OpenApiObject objectY && objectX.Keys.Count == objectY.Keys.Count && objectX.Keys.All(key => objectY.ContainsKey(key) && Equals(objectX[key], objectY[key])),
34+
OpenApiBinary binaryX => y is OpenApiBinary binaryY && binaryX.Value.SequenceEqual(binaryY.Value),
35+
OpenApiInteger integerX => y is OpenApiInteger integerY && integerX.Value == integerY.Value,
36+
OpenApiLong longX => y is OpenApiLong longY && longX.Value == longY.Value,
37+
OpenApiDouble doubleX => y is OpenApiDouble doubleY && doubleX.Value == doubleY.Value,
38+
OpenApiFloat floatX => y is OpenApiFloat floatY && floatX.Value == floatY.Value,
39+
OpenApiBoolean booleanX => y is OpenApiBoolean booleanY && booleanX.Value == booleanY.Value,
40+
OpenApiString stringX => y is OpenApiString stringY && stringX.Value == stringY.Value,
41+
OpenApiPassword passwordX => y is OpenApiPassword passwordY && passwordX.Value == passwordY.Value,
42+
OpenApiByte byteX => y is OpenApiByte byteY && byteX.Value.SequenceEqual(byteY.Value),
43+
OpenApiDate dateX => y is OpenApiDate dateY && dateX.Value == dateY.Value,
44+
OpenApiDateTime dateTimeX => y is OpenApiDateTime dateTimeY && dateTimeX.Value == dateTimeY.Value,
45+
_ => x.Equals(y)
46+
});
2847
}
2948

3049
public int GetHashCode(IOpenApiAny obj)
@@ -35,22 +54,8 @@ public int GetHashCode(IOpenApiAny obj)
3554
{
3655
hashCode.Add(primitive.PrimitiveType);
3756
}
38-
if (obj is OpenApiArray array)
39-
{
40-
foreach (var item in array)
41-
{
42-
hashCode.Add(item, Instance);
43-
}
44-
}
45-
if (obj is OpenApiObject openApiObject)
57+
hashCode.Add<object?>(obj switch
4658
{
47-
foreach (var item in openApiObject)
48-
{
49-
hashCode.Add(item.Key);
50-
hashCode.Add(item.Value, Instance);
51-
}
52-
}
53-
hashCode.Add<object?>(obj switch {
5459
OpenApiBinary binary => binary.Value,
5560
OpenApiInteger integer => integer.Value,
5661
OpenApiLong @long => @long.Value,

src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Linq;
45
using Microsoft.OpenApi.Models;
56

67
namespace Microsoft.AspNetCore.OpenApi;
@@ -24,18 +25,16 @@ public bool Equals(OpenApiDiscriminator? x, OpenApiDiscriminator? y)
2425
return true;
2526
}
2627

27-
return GetHashCode(x) == GetHashCode(y);
28+
return x.PropertyName == y.PropertyName &&
29+
x.Mapping.Count == y.Mapping.Count &&
30+
x.Mapping.Keys.All(key => y.Mapping.ContainsKey(key) && x.Mapping[key] == y.Mapping[key]);
2831
}
2932

3033
public int GetHashCode(OpenApiDiscriminator obj)
3134
{
3235
var hashCode = new HashCode();
3336
hashCode.Add(obj.PropertyName);
34-
foreach (var item in obj.Mapping)
35-
{
36-
hashCode.Add(item.Key);
37-
hashCode.Add(item.Value);
38-
}
37+
hashCode.Add(obj.Mapping.Count);
3938
return hashCode.ToHashCode();
4039
}
4140
}

src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs

+6-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Microsoft.OpenApi.Any;
4+
using System.Linq;
55
using Microsoft.OpenApi.Models;
66

77
namespace Microsoft.AspNetCore.OpenApi;
@@ -25,26 +25,18 @@ public bool Equals(OpenApiExternalDocs? x, OpenApiExternalDocs? y)
2525
return true;
2626
}
2727

28-
return GetHashCode(x) == GetHashCode(y);
28+
return x.Description == y.Description &&
29+
x.Url == y.Url &&
30+
x.Extensions.Count == y.Extensions.Count
31+
&& x.Extensions.Keys.All(k => y.Extensions.ContainsKey(k) && y.Extensions[k] == x.Extensions[k]);
2932
}
3033

3134
public int GetHashCode(OpenApiExternalDocs obj)
3235
{
3336
var hashCode = new HashCode();
3437
hashCode.Add(obj.Description);
3538
hashCode.Add(obj.Url);
36-
foreach (var item in obj.Extensions)
37-
{
38-
hashCode.Add(item.Key, StringComparer.Ordinal);
39-
if (item.Value is IOpenApiAny openApiAny)
40-
{
41-
hashCode.Add(openApiAny, OpenApiAnyComparer.Instance);
42-
}
43-
else
44-
{
45-
hashCode.Add(item.Value);
46-
}
47-
}
39+
hashCode.Add(obj.Extensions.Count);
4840
return hashCode.ToHashCode();
4941
}
5042
}

src/OpenApi/src/Comparers/OpenApiReferenceComparer.cs

+4-8
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,16 @@ public bool Equals(OpenApiReference? x, OpenApiReference? y)
2424
return true;
2525
}
2626

27-
return GetHashCode(x) == GetHashCode(y);
27+
return x.ExternalResource == y.ExternalResource &&
28+
x.HostDocument?.HashCode == y.HostDocument?.HashCode &&
29+
x.Id == y.Id &&
30+
x.Type == y.Type;
2831
}
2932

3033
public int GetHashCode(OpenApiReference obj)
3134
{
3235
var hashCode = new HashCode();
3336
hashCode.Add(obj.ExternalResource);
34-
if (obj.HostDocument is not null)
35-
{
36-
// Microsoft.OpenApi provides a HashCode property for
37-
// the OpenAPI document that we can use to uniquely identify
38-
// the host document that is referenced here.
39-
hashCode.Add(obj.HostDocument.HashCode);
40-
};
4137
hashCode.Add(obj.Id);
4238
if (obj.Type is not null)
4339
{

src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs

+47-38
Original file line numberDiff line numberDiff line change
@@ -26,46 +26,65 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
2626
return true;
2727
}
2828

29-
return GetHashCode(x) == GetHashCode(y);
29+
return Instance.Equals(x.AdditionalProperties, y.AdditionalProperties) &&
30+
x.AdditionalPropertiesAllowed == y.AdditionalPropertiesAllowed &&
31+
x.AllOf.SequenceEqual(y.AllOf, Instance) &&
32+
x.AnyOf.SequenceEqual(y.AnyOf, Instance) &&
33+
x.Deprecated == y.Deprecated &&
34+
OpenApiAnyComparer.Instance.Equals(x.Default, y.Default) &&
35+
x.Description == y.Description &&
36+
OpenApiDiscriminatorComparer.Instance.Equals(x.Discriminator, y.Discriminator) &&
37+
OpenApiAnyComparer.Instance.Equals(x.Example, y.Example) &&
38+
x.ExclusiveMaximum == y.ExclusiveMaximum &&
39+
x.ExclusiveMinimum == y.ExclusiveMinimum &&
40+
x.Extensions.Count == y.Extensions.Count
41+
&& 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)) &&
42+
OpenApiExternalDocsComparer.Instance.Equals(x.ExternalDocs, y.ExternalDocs) &&
43+
x.Enum.SequenceEqual(y.Enum, OpenApiAnyComparer.Instance) &&
44+
x.Format == y.Format &&
45+
Instance.Equals(x.Items, y.Items) &&
46+
x.Title == y.Title &&
47+
x.Type == y.Type &&
48+
x.Maximum == y.Maximum &&
49+
x.MaxItems == y.MaxItems &&
50+
x.MaxLength == y.MaxLength &&
51+
x.MaxProperties == y.MaxProperties &&
52+
x.Minimum == y.Minimum &&
53+
x.MinItems == y.MinItems &&
54+
x.MinLength == y.MinLength &&
55+
x.MinProperties == y.MinProperties &&
56+
x.MultipleOf == y.MultipleOf &&
57+
x.OneOf.SequenceEqual(y.OneOf, Instance) &&
58+
Instance.Equals(x.Not, y.Not) &&
59+
x.Nullable == y.Nullable &&
60+
x.Pattern == y.Pattern &&
61+
x.Properties.Keys.All(k => y.Properties.ContainsKey(k) && Instance.Equals(x.Properties[k], y.Properties[k])) &&
62+
x.ReadOnly == y.ReadOnly &&
63+
x.Required.Order().SequenceEqual(y.Required.Order()) &&
64+
OpenApiReferenceComparer.Instance.Equals(x.Reference, y.Reference) &&
65+
x.UniqueItems == y.UniqueItems &&
66+
x.UnresolvedReference == y.UnresolvedReference &&
67+
x.WriteOnly == y.WriteOnly &&
68+
OpenApiXmlComparer.Instance.Equals(x.Xml, y.Xml);
3069
}
3170

3271
public int GetHashCode(OpenApiSchema obj)
3372
{
3473
var hashCode = new HashCode();
3574
hashCode.Add(obj.AdditionalProperties, Instance);
3675
hashCode.Add(obj.AdditionalPropertiesAllowed);
37-
foreach (var item in obj.AllOf)
38-
{
39-
hashCode.Add(item, Instance);
40-
}
41-
foreach (var item in obj.AnyOf)
42-
{
43-
hashCode.Add(item, Instance);
44-
}
76+
hashCode.Add(obj.AllOf.Count);
77+
hashCode.Add(obj.AnyOf.Count);
4578
hashCode.Add(obj.Deprecated);
4679
hashCode.Add(obj.Default, OpenApiAnyComparer.Instance);
4780
hashCode.Add(obj.Description);
4881
hashCode.Add(obj.Discriminator, OpenApiDiscriminatorComparer.Instance);
4982
hashCode.Add(obj.Example, OpenApiAnyComparer.Instance);
5083
hashCode.Add(obj.ExclusiveMaximum);
5184
hashCode.Add(obj.ExclusiveMinimum);
52-
foreach ((var key, var value) in obj.Extensions)
53-
{
54-
hashCode.Add(key);
55-
if (value is IOpenApiAny any)
56-
{
57-
hashCode.Add(any, OpenApiAnyComparer.Instance);
58-
}
59-
else
60-
{
61-
hashCode.Add(value);
62-
}
63-
}
85+
hashCode.Add(obj.Extensions.Count);
6486
hashCode.Add(obj.ExternalDocs, OpenApiExternalDocsComparer.Instance);
65-
foreach (var item in obj.Enum)
66-
{
67-
hashCode.Add(item, OpenApiAnyComparer.Instance);
68-
}
87+
hashCode.Add(obj.Enum.Count);
6988
hashCode.Add(obj.Format);
7089
hashCode.Add(obj.Items, Instance);
7190
hashCode.Add(obj.Title);
@@ -79,23 +98,13 @@ public int GetHashCode(OpenApiSchema obj)
7998
hashCode.Add(obj.MinLength);
8099
hashCode.Add(obj.MinProperties);
81100
hashCode.Add(obj.MultipleOf);
82-
foreach (var item in obj.OneOf)
83-
{
84-
hashCode.Add(item, Instance);
85-
}
101+
hashCode.Add(obj.OneOf.Count);
86102
hashCode.Add(obj.Not, Instance);
87103
hashCode.Add(obj.Nullable);
88104
hashCode.Add(obj.Pattern);
89-
foreach ((var key, var value) in obj.Properties)
90-
{
91-
hashCode.Add(key);
92-
hashCode.Add(value, Instance);
93-
}
105+
hashCode.Add(obj.Properties.Count);
94106
hashCode.Add(obj.ReadOnly);
95-
foreach (var item in obj.Required.Order())
96-
{
97-
hashCode.Add(item);
98-
}
107+
hashCode.Add(obj.Required.Count);
99108
hashCode.Add(obj.Reference, OpenApiReferenceComparer.Instance);
100109
hashCode.Add(obj.UniqueItems);
101110
hashCode.Add(obj.UnresolvedReference);

src/OpenApi/src/Comparers/OpenApiXmlComparer.cs

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Microsoft.OpenApi.Any;
4+
using System.Linq;
55
using Microsoft.OpenApi.Models;
66

77
namespace Microsoft.AspNetCore.OpenApi;
@@ -25,7 +25,13 @@ public bool Equals(OpenApiXml? x, OpenApiXml? y)
2525
return true;
2626
}
2727

28-
return GetHashCode(x) == GetHashCode(y);
28+
return x.Name == y.Name &&
29+
x.Namespace == y.Namespace &&
30+
x.Prefix == y.Prefix &&
31+
x.Attribute == y.Attribute &&
32+
x.Wrapped == y.Wrapped &&
33+
x.Extensions.Count == y.Extensions.Count
34+
&& x.Extensions.Keys.All(k => y.Extensions.ContainsKey(k) && y.Extensions[k] == x.Extensions[k]);
2935
}
3036

3137
public int GetHashCode(OpenApiXml obj)
@@ -36,17 +42,7 @@ public int GetHashCode(OpenApiXml obj)
3642
hashCode.Add(obj.Prefix);
3743
hashCode.Add(obj.Attribute);
3844
hashCode.Add(obj.Wrapped);
39-
foreach (var item in obj.Extensions)
40-
{
41-
hashCode.Add(item.Key);
42-
if (item.Value is IOpenApiAny any)
43-
{
44-
hashCode.Add(any, OpenApiAnyComparer.Instance);
45-
}
46-
{
47-
hashCode.Add(item.Value);
48-
}
49-
}
45+
hashCode.Add(obj.Extensions.Count);
5046
return hashCode.ToHashCode();
5147
}
5248
}

src/OpenApi/test/Comparers/OpenApiAnyComparerTests.cs

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text;
45
using Microsoft.AspNetCore.OpenApi;
56
using Microsoft.OpenApi.Any;
67

@@ -9,10 +10,15 @@ public class OpenApiAnyComparerTests
910
public static object[][] Data => [
1011
[new OpenApiNull(), new OpenApiNull(), true],
1112
[new OpenApiNull(), new OpenApiBoolean(true), false],
13+
[new OpenApiByte(1), new OpenApiByte(1), true],
14+
[new OpenApiByte(1), new OpenApiByte(2), false],
15+
[new OpenApiBinary(Encoding.UTF8.GetBytes("test")), new OpenApiBinary(Encoding.UTF8.GetBytes("test")), true],
16+
[new OpenApiBinary(Encoding.UTF8.GetBytes("test2")), new OpenApiBinary(Encoding.UTF8.GetBytes("test")), false],
1217
[new OpenApiBoolean(true), new OpenApiBoolean(true), true],
1318
[new OpenApiBoolean(true), new OpenApiBoolean(false), false],
1419
[new OpenApiInteger(1), new OpenApiInteger(1), true],
1520
[new OpenApiInteger(1), new OpenApiInteger(2), false],
21+
[new OpenApiInteger(1), new OpenApiLong(1), false],
1622
[new OpenApiLong(1), new OpenApiLong(1), true],
1723
[new OpenApiLong(1), new OpenApiLong(2), false],
1824
[new OpenApiFloat(1.1f), new OpenApiFloat(1.1f), true],
@@ -38,13 +44,6 @@ public class OpenApiAnyComparerTests
3844

3945
[Theory]
4046
[MemberData(nameof(Data))]
41-
public void ProducesCorrectHashCodeForAny(IOpenApiAny any, IOpenApiAny anotherAny, bool isEqual)
42-
{
43-
// Act
44-
var hash = OpenApiAnyComparer.Instance.GetHashCode(any);
45-
var anotherHash = OpenApiAnyComparer.Instance.GetHashCode(anotherAny);
46-
47-
// Assert
48-
Assert.Equal(isEqual, hash.Equals(anotherHash));
49-
}
47+
public void ProducesCorrectEqualityForOpenApiAny(IOpenApiAny any, IOpenApiAny anotherAny, bool isEqual)
48+
=> Assert.Equal(isEqual, OpenApiAnyComparer.Instance.Equals(any, anotherAny));
5049
}

src/OpenApi/test/Comparers/OpenApiDiscriminatorComparerTests.cs

+2-9
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ public class OpenApiDiscriminatorComparerTests
1818

1919
[Theory]
2020
[MemberData(nameof(Data))]
21-
public void ProducesCorrectHashCodeForDiscriminator(OpenApiDiscriminator discriminator, OpenApiDiscriminator anotherDiscriminator, bool isEqual)
22-
{
23-
// Act
24-
var hash = OpenApiDiscriminatorComparer.Instance.GetHashCode(discriminator);
25-
var anotherHash = OpenApiDiscriminatorComparer.Instance.GetHashCode(anotherDiscriminator);
26-
27-
// Assert
28-
Assert.Equal(isEqual, hash.Equals(anotherHash));
29-
}
21+
public void ProducesCorrectEqualityForOpenApiDiscriminator(OpenApiDiscriminator discriminator, OpenApiDiscriminator anotherDiscriminator, bool isEqual)
22+
=> Assert.Equal(isEqual, OpenApiDiscriminatorComparer.Instance.Equals(discriminator, anotherDiscriminator));
3023
}

src/OpenApi/test/Comparers/OpenApiExternalDocsComparerTests.cs

+3-9
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,11 @@ public class OpenApiExternalDocsComparerTests
1313
[new OpenApiExternalDocs { Description = "description" }, new OpenApiExternalDocs { Description = "description" }, true],
1414
[new OpenApiExternalDocs { Description = "description" }, new OpenApiExternalDocs { Description = "description", Url = new Uri("http://localhost") }, false],
1515
[new OpenApiExternalDocs { Description = "description", Url = new Uri("http://localhost") }, new OpenApiExternalDocs { Description = "description", Url = new Uri("http://localhost") }, true],
16+
[new OpenApiExternalDocs { Description = "description", Url = new Uri("http://localhost") }, new OpenApiExternalDocs { Description = "description", Url = new Uri("http://localhost") }, true],
1617
];
1718

1819
[Theory]
1920
[MemberData(nameof(Data))]
20-
public void ProducesCorrectHashCodeForAny(OpenApiExternalDocs externalDocs, OpenApiExternalDocs anotherExternalDocs, bool isEqual)
21-
{
22-
// Act
23-
var hash = OpenApiExternalDocsComparer.Instance.GetHashCode(externalDocs);
24-
var anotherHash = OpenApiExternalDocsComparer.Instance.GetHashCode(anotherExternalDocs);
25-
26-
// Assert
27-
Assert.Equal(isEqual, hash.Equals(anotherHash));
28-
}
21+
public void ProducesCorrectEqualityForOpenApiExternalDocs(OpenApiExternalDocs externalDocs, OpenApiExternalDocs anotherExternalDocs, bool isEqual)
22+
=> Assert.Equal(isEqual, OpenApiExternalDocsComparer.Instance.Equals(externalDocs, anotherExternalDocs));
2923
}

0 commit comments

Comments
 (0)