Skip to content

Commit 2546e83

Browse files
committed
Add support for reference-based schemas in OpenAPI document
1 parent 8efe5b0 commit 2546e83

31 files changed

+1405
-276
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Globalization;
5+
using BenchmarkDotNet.Attributes;
6+
using Microsoft.OpenApi.Any;
7+
using Microsoft.OpenApi.Interfaces;
8+
using Microsoft.OpenApi.Models;
9+
10+
namespace Microsoft.AspNetCore.OpenApi.Microbenchmarks;
11+
12+
public class OpenApiSchemaComparerBenchmark
13+
{
14+
[Params(1, 10, 100)]
15+
public int ElementCount { get; set; }
16+
17+
private OpenApiSchema _schema;
18+
19+
[GlobalSetup(Target = nameof(OpenApiSchema_GetHashCode))]
20+
public void OpenApiSchema_Setup()
21+
{
22+
_schema = new OpenApiSchema
23+
{
24+
AdditionalProperties = GenerateInnerSchema(),
25+
AdditionalPropertiesAllowed = true,
26+
AllOf = Enumerable.Range(0, ElementCount).Select(_ => GenerateInnerSchema()).ToList(),
27+
AnyOf = Enumerable.Range(0, ElementCount).Select(_ => GenerateInnerSchema()).ToList(),
28+
Deprecated = true,
29+
Default = new OpenApiString("default"),
30+
Description = "description",
31+
Discriminator = new OpenApiDiscriminator(),
32+
Example = new OpenApiString("example"),
33+
ExclusiveMaximum = true,
34+
ExclusiveMinimum = true,
35+
Extensions = new Dictionary<string, IOpenApiExtension>
36+
{
37+
["key"] = new OpenApiString("value")
38+
},
39+
ExternalDocs = new OpenApiExternalDocs(),
40+
Enum = Enumerable.Range(0, ElementCount).Select(_ => (IOpenApiAny)new OpenApiString("enum")).ToList(),
41+
OneOf = Enumerable.Range(0, ElementCount).Select(_ => GenerateInnerSchema()).ToList(),
42+
};
43+
44+
static OpenApiSchema GenerateInnerSchema() => new OpenApiSchema
45+
{
46+
Properties = Enumerable.Range(0, 10).ToDictionary(i => i.ToString(CultureInfo.InvariantCulture), _ => new OpenApiSchema()),
47+
Deprecated = true,
48+
Default = new OpenApiString("default"),
49+
Description = "description",
50+
Example = new OpenApiString("example"),
51+
Extensions = new Dictionary<string, IOpenApiExtension>
52+
{
53+
["key"] = new OpenApiString("value")
54+
},
55+
};
56+
}
57+
58+
[Benchmark]
59+
public void OpenApiSchema_GetHashCode()
60+
{
61+
OpenApiSchemaComparer.Instance.GetHashCode(_schema);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.OpenApi.Any;
5+
6+
namespace Microsoft.AspNetCore.OpenApi;
7+
8+
internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>
9+
{
10+
public static OpenApiAnyComparer Instance { get; } = new OpenApiAnyComparer();
11+
12+
public bool Equals(IOpenApiAny? x, IOpenApiAny? y)
13+
{
14+
if (x is null && y is null)
15+
{
16+
return true;
17+
}
18+
if (x is null || y is null)
19+
{
20+
return false;
21+
}
22+
23+
return GetHashCode(x) == GetHashCode(y);
24+
}
25+
26+
public int GetHashCode(IOpenApiAny obj)
27+
{
28+
var hashCode = new HashCode();
29+
hashCode.Add(obj.AnyType);
30+
if (obj is IOpenApiPrimitive primitive)
31+
{
32+
hashCode.Add(primitive.PrimitiveType);
33+
}
34+
if (obj is OpenApiArray array)
35+
{
36+
foreach (var item in array)
37+
{
38+
hashCode.Add(item, Instance);
39+
}
40+
}
41+
if (obj is OpenApiObject openApiObject)
42+
{
43+
foreach (var item in openApiObject)
44+
{
45+
hashCode.Add(item.Key);
46+
hashCode.Add(item.Value, Instance);
47+
}
48+
}
49+
hashCode.Add<object?>(obj switch {
50+
OpenApiBinary binary => binary.Value,
51+
OpenApiInteger integer => integer.Value,
52+
OpenApiLong @long => @long.Value,
53+
OpenApiDouble @double => @double.Value,
54+
OpenApiFloat @float => @float.Value,
55+
OpenApiBoolean boolean => boolean.Value,
56+
OpenApiString @string => @string.Value,
57+
OpenApiPassword password => password.Value,
58+
OpenApiByte @byte => @byte.Value,
59+
OpenApiDate date => date.Value,
60+
OpenApiDateTime dateTime => dateTime.Value,
61+
_ => null
62+
});
63+
64+
return hashCode.ToHashCode();
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.OpenApi.Models;
5+
6+
namespace Microsoft.AspNetCore.OpenApi;
7+
8+
internal sealed class OpenApiDiscriminatorComparer : IEqualityComparer<OpenApiDiscriminator>
9+
{
10+
public static OpenApiDiscriminatorComparer Instance { get; } = new OpenApiDiscriminatorComparer();
11+
12+
public bool Equals(OpenApiDiscriminator? x, OpenApiDiscriminator? y)
13+
{
14+
if (x is null && y is null)
15+
{
16+
return true;
17+
}
18+
if (x is null || y is null)
19+
{
20+
return false;
21+
}
22+
23+
return GetHashCode(x) == GetHashCode(y);
24+
}
25+
26+
public int GetHashCode(OpenApiDiscriminator obj)
27+
{
28+
var hashCode = new HashCode();
29+
hashCode.Add(obj.PropertyName);
30+
foreach (var item in obj.Mapping)
31+
{
32+
hashCode.Add(item.Key);
33+
hashCode.Add(item.Value);
34+
}
35+
return hashCode.ToHashCode();
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.OpenApi.Any;
5+
using Microsoft.OpenApi.Models;
6+
7+
namespace Microsoft.AspNetCore.OpenApi;
8+
9+
internal sealed class OpenApiExternalDocsComparer : IEqualityComparer<OpenApiExternalDocs>
10+
{
11+
public static OpenApiExternalDocsComparer Instance { get; } = new OpenApiExternalDocsComparer();
12+
13+
public bool Equals(OpenApiExternalDocs? x, OpenApiExternalDocs? y)
14+
{
15+
if (x is null && y is null)
16+
{
17+
return true;
18+
}
19+
20+
if (x is null || y is null)
21+
{
22+
return false;
23+
}
24+
25+
return GetHashCode(x) == GetHashCode(y);
26+
}
27+
28+
public int GetHashCode(OpenApiExternalDocs obj)
29+
{
30+
var hashCode = new HashCode();
31+
hashCode.Add(obj.Description);
32+
hashCode.Add(obj.Url);
33+
foreach (var item in obj.Extensions)
34+
{
35+
hashCode.Add(item.Key, StringComparer.Ordinal);
36+
if (item.Value is IOpenApiAny openApiAny)
37+
{
38+
hashCode.Add(openApiAny, OpenApiAnyComparer.Instance);
39+
}
40+
else
41+
{
42+
hashCode.Add(item.Value);
43+
}
44+
}
45+
return hashCode.ToHashCode();
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.OpenApi.Models;
5+
6+
namespace Microsoft.AspNetCore.OpenApi;
7+
8+
internal sealed class OpenApiReferenceComparer : IEqualityComparer<OpenApiReference>
9+
{
10+
public static OpenApiReferenceComparer Instance { get; } = new OpenApiReferenceComparer();
11+
12+
public bool Equals(OpenApiReference? x, OpenApiReference? y)
13+
{
14+
if (x is null && y is null)
15+
{
16+
return true;
17+
}
18+
if (x is null || y is null)
19+
{
20+
return false;
21+
}
22+
23+
return GetHashCode(x) == GetHashCode(y);
24+
}
25+
26+
public int GetHashCode(OpenApiReference obj)
27+
{
28+
var hashCode = new HashCode();
29+
hashCode.Add(obj.ExternalResource);
30+
if (obj.HostDocument is not null)
31+
{
32+
// Microsoft.OpenApi provides a HashCode property for
33+
// the OpenAPI document that we can use to uniquely identify
34+
// the host document that is referenced here.
35+
hashCode.Add(obj.HostDocument.HashCode);
36+
};
37+
hashCode.Add(obj.Id);
38+
if (obj.Type is not null)
39+
{
40+
hashCode.Add(obj.Type);
41+
}
42+
return hashCode.ToHashCode();
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using Microsoft.OpenApi.Any;
6+
using Microsoft.OpenApi.Models;
7+
8+
namespace Microsoft.AspNetCore.OpenApi;
9+
10+
internal sealed class OpenApiSchemaComparer : IEqualityComparer<OpenApiSchema>
11+
{
12+
public static OpenApiSchemaComparer Instance { get; } = new OpenApiSchemaComparer();
13+
14+
public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
15+
{
16+
if (x is null && y is null)
17+
{
18+
return true;
19+
}
20+
if (x is null || y is null)
21+
{
22+
return false;
23+
}
24+
25+
return GetHashCode(x) == GetHashCode(y);
26+
}
27+
28+
public int GetHashCode(OpenApiSchema obj)
29+
{
30+
var hashCode = new HashCode();
31+
hashCode.Add(obj.AdditionalProperties, Instance);
32+
hashCode.Add(obj.AdditionalPropertiesAllowed);
33+
foreach (var item in obj.AllOf)
34+
{
35+
hashCode.Add(item, Instance);
36+
}
37+
foreach (var item in obj.AnyOf)
38+
{
39+
hashCode.Add(item, Instance);
40+
}
41+
hashCode.Add(obj.Deprecated);
42+
hashCode.Add(obj.Default, OpenApiAnyComparer.Instance);
43+
hashCode.Add(obj.Description);
44+
hashCode.Add(obj.Discriminator, OpenApiDiscriminatorComparer.Instance);
45+
hashCode.Add(obj.Example, OpenApiAnyComparer.Instance);
46+
hashCode.Add(obj.ExclusiveMaximum);
47+
hashCode.Add(obj.ExclusiveMinimum);
48+
foreach ((var key, var value) in obj.Extensions)
49+
{
50+
hashCode.Add(key);
51+
if (value is IOpenApiAny any)
52+
{
53+
hashCode.Add(any, OpenApiAnyComparer.Instance);
54+
}
55+
else
56+
{
57+
hashCode.Add(value);
58+
}
59+
}
60+
hashCode.Add(obj.ExternalDocs, OpenApiExternalDocsComparer.Instance);
61+
foreach (var item in obj.Enum)
62+
{
63+
hashCode.Add(item, OpenApiAnyComparer.Instance);
64+
}
65+
hashCode.Add(obj.Format);
66+
hashCode.Add(obj.Items, Instance);
67+
hashCode.Add(obj.Title);
68+
hashCode.Add(obj.Type);
69+
hashCode.Add(obj.Maximum);
70+
hashCode.Add(obj.MaxItems);
71+
hashCode.Add(obj.MaxLength);
72+
hashCode.Add(obj.MaxProperties);
73+
hashCode.Add(obj.Minimum);
74+
hashCode.Add(obj.MinItems);
75+
hashCode.Add(obj.MinLength);
76+
hashCode.Add(obj.MinProperties);
77+
hashCode.Add(obj.MultipleOf);
78+
foreach (var item in obj.OneOf)
79+
{
80+
hashCode.Add(item, Instance);
81+
}
82+
hashCode.Add(obj.Not, Instance);
83+
hashCode.Add(obj.Nullable);
84+
hashCode.Add(obj.Pattern);
85+
foreach ((var key, var value) in obj.Properties)
86+
{
87+
hashCode.Add(key);
88+
hashCode.Add(value, Instance);
89+
}
90+
hashCode.Add(obj.ReadOnly);
91+
foreach (var item in obj.Required.Order())
92+
{
93+
hashCode.Add(item);
94+
}
95+
hashCode.Add(obj.Reference, OpenApiReferenceComparer.Instance);
96+
hashCode.Add(obj.UniqueItems);
97+
hashCode.Add(obj.UnresolvedReference);
98+
hashCode.Add(obj.WriteOnly);
99+
hashCode.Add(obj.Xml, OpenApiXmlComparer.Instance);
100+
return hashCode.ToHashCode();
101+
}
102+
}

src/OpenApi/src/Helpers/OpenApiTagComparer.cs renamed to src/OpenApi/src/Comparers/OpenApiTagComparer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.OpenApi;
99
/// This comparer is used to maintain a globally unique list of tags encountered
1010
/// in a particular OpenAPI document.
1111
/// </summary>
12-
internal class OpenApiTagComparer : IEqualityComparer<OpenApiTag>
12+
internal sealed class OpenApiTagComparer : IEqualityComparer<OpenApiTag>
1313
{
1414
public static OpenApiTagComparer Instance { get; } = new OpenApiTagComparer();
1515

0 commit comments

Comments
 (0)