Skip to content

Commit c571a3e

Browse files
authored
Merge branch 'dotnet:main' into rdg-optional-array
2 parents c61296e + db8893b commit c571a3e

File tree

10 files changed

+195
-25
lines changed

10 files changed

+195
-25
lines changed

eng/Version.Details.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@
339339
<Sha>0d51607fb791c51a14b552ed24fe3430c252148b</Sha>
340340
<SourceBuild RepoName="xdt" ManagedOnly="true" />
341341
</Dependency>
342-
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="9.0.0-alpha.1.24412.1">
342+
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="9.0.0-alpha.1.24413.1">
343343
<Uri>https://github.com/dotnet/source-build-reference-packages</Uri>
344-
<Sha>f49f79bb7ce6881d77800114617a0e59c38b0871</Sha>
344+
<Sha>1b838a42e4952b8fdf212cb1b43c5ce4d69f27b3</Sha>
345345
<SourceBuild RepoName="source-build-reference-packages" ManagedOnly="true" />
346346
</Dependency>
347347
<!-- Not updated automatically -->

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172
<!-- Packages from dotnet/source-build-externals -->
173173
<MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>9.0.0-alpha.1.24407.1</MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>
174174
<!-- Packages from dotnet/source-build-reference-packages -->
175-
<MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesVersion>9.0.0-alpha.1.24412.1</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesVersion>
175+
<MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesVersion>9.0.0-alpha.1.24413.1</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesVersion>
176176
<!-- Packages from dotnet/symreader -->
177177
<MicrosoftSourceBuildIntermediatesymreaderVersion>2.2.0-beta.24327.2</MicrosoftSourceBuildIntermediatesymreaderVersion>
178178
<!-- Packages from dotnet/winforms -->
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
namespace Microsoft.AspNetCore.OpenApi;
5+
6+
internal static class ComparerHelpers
7+
{
8+
internal static bool DictionaryEquals<TKey, TValue>(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y, IEqualityComparer<TValue> comparer)
9+
where TKey : notnull
10+
where TValue : notnull
11+
{
12+
if (x is Dictionary<TKey, TValue> xDictionary && y is Dictionary<TKey, TValue> yDictionary)
13+
{
14+
return DictionaryEquals(xDictionary, yDictionary, comparer);
15+
}
16+
17+
if (x.Keys.Count != y.Keys.Count)
18+
{
19+
return false;
20+
}
21+
22+
foreach (var key in x.Keys)
23+
{
24+
if (!y.TryGetValue(key, out var value) || !comparer.Equals(x[key], value))
25+
{
26+
return false;
27+
}
28+
}
29+
30+
return true;
31+
}
32+
33+
// Private method to avoid interface dispatch.
34+
private static bool DictionaryEquals<TKey, TValue>(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y, IEqualityComparer<TValue> comparer)
35+
where TKey : notnull
36+
where TValue : notnull
37+
{
38+
if (x.Keys.Count != y.Keys.Count)
39+
{
40+
return false;
41+
}
42+
43+
foreach (var key in x.Keys)
44+
{
45+
if (!y.TryGetValue(key, out var value) || !comparer.Equals(x[key], value))
46+
{
47+
return false;
48+
}
49+
}
50+
51+
return true;
52+
}
53+
54+
internal static bool ListEquals<T>(IList<T> x, IList<T> y, IEqualityComparer<T> comparer)
55+
{
56+
if (x is List<T> xList && y is List<T> yList)
57+
{
58+
return ListEquals(xList, yList, comparer);
59+
}
60+
61+
if (x.Count != y.Count)
62+
{
63+
return false;
64+
}
65+
66+
for (var i = 0; i < x.Count; i++)
67+
{
68+
if (!comparer.Equals(x[i], y[i]))
69+
{
70+
return false;
71+
}
72+
}
73+
74+
return true;
75+
}
76+
77+
// Private method to avoid interface dispatch.
78+
private static bool ListEquals<T>(List<T> x, List<T> y, IEqualityComparer<T> comparer)
79+
{
80+
if (x.Count != y.Count)
81+
{
82+
return false;
83+
}
84+
85+
for (var i = 0; i < x.Count; i++)
86+
{
87+
if (!comparer.Equals(x[i], y[i]))
88+
{
89+
return false;
90+
}
91+
}
92+
93+
return true;
94+
}
95+
}

src/OpenApi/src/Comparers/OpenApiAnyComparer.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
using System.Linq;
55
using Microsoft.OpenApi.Any;
6+
using Microsoft.OpenApi.Interfaces;
67

78
namespace Microsoft.AspNetCore.OpenApi;
89

9-
internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>
10+
internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>, IEqualityComparer<IOpenApiExtension>
1011
{
1112
public static OpenApiAnyComparer Instance { get; } = new OpenApiAnyComparer();
1213

@@ -29,8 +30,8 @@ public bool Equals(IOpenApiAny? x, IOpenApiAny? y)
2930
(x switch
3031
{
3132
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.TryGetValue(key, out var yValue) && Equals(objectX[key], yValue)),
33+
OpenApiArray arrayX => y is OpenApiArray arrayY && ComparerHelpers.ListEquals(arrayX, arrayY, Instance),
34+
OpenApiObject objectX => y is OpenApiObject objectY && ComparerHelpers.DictionaryEquals(objectX, objectY, Instance),
3435
OpenApiBinary binaryX => y is OpenApiBinary binaryY && binaryX.Value.SequenceEqual(binaryY.Value),
3536
OpenApiInteger integerX => y is OpenApiInteger integerY && integerX.Value == integerY.Value,
3637
OpenApiLong longX => y is OpenApiLong longY && longX.Value == longY.Value,
@@ -78,4 +79,39 @@ public int GetHashCode(IOpenApiAny obj)
7879

7980
return hashCode.ToHashCode();
8081
}
82+
83+
public bool Equals(IOpenApiExtension? x, IOpenApiExtension? y)
84+
{
85+
if (x is null && y is null)
86+
{
87+
return true;
88+
}
89+
90+
if (x is null || y is null)
91+
{
92+
return false;
93+
}
94+
95+
if (object.ReferenceEquals(x, y))
96+
{
97+
return true;
98+
}
99+
100+
if (x is IOpenApiAny openApiAnyX && y is IOpenApiAny openApiAnyY)
101+
{
102+
return Equals(openApiAnyX, openApiAnyY);
103+
}
104+
105+
return x.Equals(y);
106+
}
107+
108+
public int GetHashCode(IOpenApiExtension obj)
109+
{
110+
if (obj is IOpenApiAny any)
111+
{
112+
return GetHashCode(any);
113+
}
114+
115+
return obj.GetHashCode();
116+
}
81117
}

src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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;
54
using Microsoft.OpenApi.Models;
65

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

2827
return x.PropertyName == y.PropertyName &&
2928
x.Mapping.Count == y.Mapping.Count &&
30-
x.Mapping.Keys.All(key => y.Mapping.TryGetValue(key, out var yValue) && x.Mapping[key] == yValue);
29+
ComparerHelpers.DictionaryEquals(x.Mapping, y.Mapping, StringComparer.Ordinal);
3130
}
3231

3332
public int GetHashCode(OpenApiDiscriminator obj)

src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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;
54
using Microsoft.OpenApi.Models;
65

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

2827
return x.Description == y.Description &&
2928
x.Url == y.Url &&
30-
x.Extensions.Count == y.Extensions.Count
31-
&& x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && yValue == x.Extensions[k]);
29+
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance);
3230
}
3331

3432
public int GetHashCode(OpenApiExternalDocs obj)

src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
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;
5-
using Microsoft.OpenApi.Any;
64
using Microsoft.OpenApi.Models;
75

86
namespace Microsoft.AspNetCore.OpenApi;
@@ -31,22 +29,22 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
3129
x.Type == y.Type &&
3230
x.Format == y.Format &&
3331
SchemaIdEquals(x, y) &&
34-
x.Properties.Keys.All(k => y.Properties.TryGetValue(k, out var yValue) && Instance.Equals(x.Properties[k], yValue)) &&
32+
ComparerHelpers.DictionaryEquals(x.Properties, y.Properties, Instance) &&
3533
OpenApiDiscriminatorComparer.Instance.Equals(x.Discriminator, y.Discriminator) &&
3634
Instance.Equals(x.AdditionalProperties, y.AdditionalProperties) &&
3735
x.AdditionalPropertiesAllowed == y.AdditionalPropertiesAllowed &&
38-
x.AllOf.SequenceEqual(y.AllOf, Instance) &&
39-
x.AnyOf.SequenceEqual(y.AnyOf, Instance) &&
36+
ComparerHelpers.ListEquals(x.AllOf, y.AllOf, Instance) &&
37+
ComparerHelpers.ListEquals(x.AnyOf, y.AnyOf, Instance) &&
4038
x.Deprecated == y.Deprecated &&
4139
OpenApiAnyComparer.Instance.Equals(x.Default, y.Default) &&
4240
x.Description == y.Description &&
4341
OpenApiAnyComparer.Instance.Equals(x.Example, y.Example) &&
4442
x.ExclusiveMaximum == y.ExclusiveMaximum &&
4543
x.ExclusiveMinimum == y.ExclusiveMinimum &&
4644
x.Extensions.Count == y.Extensions.Count &&
47-
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)) &&
45+
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance) &&
4846
OpenApiExternalDocsComparer.Instance.Equals(x.ExternalDocs, y.ExternalDocs) &&
49-
x.Enum.SequenceEqual(y.Enum, OpenApiAnyComparer.Instance) &&
47+
ComparerHelpers.ListEquals(x.Enum, y.Enum, OpenApiAnyComparer.Instance) &&
5048
Instance.Equals(x.Items, y.Items) &&
5149
x.Title == y.Title &&
5250
x.Maximum == y.Maximum &&
@@ -58,7 +56,7 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
5856
x.MinLength == y.MinLength &&
5957
x.MinProperties == y.MinProperties &&
6058
x.MultipleOf == y.MultipleOf &&
61-
x.OneOf.SequenceEqual(y.OneOf, Instance) &&
59+
ComparerHelpers.ListEquals(x.OneOf, y.OneOf, Instance) &&
6260
Instance.Equals(x.Not, y.Not) &&
6361
x.Nullable == y.Nullable &&
6462
x.Pattern == y.Pattern &&

src/OpenApi/src/Comparers/OpenApiXmlComparer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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;
54
using Microsoft.OpenApi.Models;
65

76
namespace Microsoft.AspNetCore.OpenApi;
@@ -30,8 +29,7 @@ public bool Equals(OpenApiXml? x, OpenApiXml? y)
3029
x.Prefix == y.Prefix &&
3130
x.Attribute == y.Attribute &&
3231
x.Wrapped == y.Wrapped &&
33-
x.Extensions.Count == y.Extensions.Count
34-
&& x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && yValue == x.Extensions[k]);
32+
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance);
3533
}
3634

3735
public int GetHashCode(OpenApiXml obj)

src/OpenApi/src/Services/OpenApiDocumentService.cs

Lines changed: 23 additions & 3 deletions
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.Collections.Frozen;
45
using System.ComponentModel;
56
using System.ComponentModel.DataAnnotations;
67
using System.Diagnostics;
@@ -22,6 +23,7 @@
2223
using Microsoft.Extensions.DependencyInjection;
2324
using Microsoft.Extensions.Hosting;
2425
using Microsoft.Extensions.Options;
26+
using Microsoft.Net.Http.Headers;
2527
using Microsoft.OpenApi.Models;
2628

2729
namespace Microsoft.AspNetCore.OpenApi;
@@ -47,6 +49,8 @@ internal sealed class OpenApiDocumentService(
4749
private readonly Dictionary<string, OpenApiOperationTransformerContext> _operationTransformerContextCache = new();
4850
private static readonly ApiResponseType _defaultApiResponseType = new() { StatusCode = StatusCodes.Status200OK };
4951

52+
private static readonly FrozenSet<string> _disallowedHeaderParameters = new[] { HeaderNames.Accept, HeaderNames.Authorization, HeaderNames.ContentType }.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
53+
5054
internal bool TryGetCachedOperationTransformerContext(string descriptionId, [NotNullWhen(true)] out OpenApiOperationTransformerContext? context)
5155
=> _operationTransformerContextCache.TryGetValue(descriptionId, out context);
5256

@@ -393,9 +397,7 @@ private async Task<OpenApiResponse> GetResponseAsync(
393397
List<OpenApiParameter>? parameters = null;
394398
foreach (var parameter in description.ParameterDescriptions)
395399
{
396-
// Parameters that should be in the request body should not be
397-
// populated in the parameters list.
398-
if (parameter.IsRequestBodyParameter())
400+
if (ShouldIgnoreParameter(parameter))
399401
{
400402
continue;
401403
}
@@ -419,6 +421,24 @@ private async Task<OpenApiResponse> GetResponseAsync(
419421
parameters.Add(openApiParameter);
420422
}
421423
return parameters;
424+
425+
static bool ShouldIgnoreParameter(ApiParameterDescription parameter)
426+
{
427+
if (parameter.IsRequestBodyParameter())
428+
{
429+
// Parameters that should be in the request body should not be
430+
// populated in the parameters list.
431+
return true;
432+
}
433+
else if (parameter.Source == BindingSource.Header && _disallowedHeaderParameters.Contains(parameter.Name))
434+
{
435+
// OpenAPI 3.0 states certain headers are "not allowed" to be defined as parameters.
436+
// See https://github.com/dotnet/aspnetcore/issues/57305 for more context.
437+
return true;
438+
}
439+
440+
return false;
441+
}
422442
}
423443

424444
private static bool IsRequired(ApiParameterDescription parameter)

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Parameters.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,30 @@ await VerifyOpenApiDocument(builder, document =>
164164
Assert.Null(todosOperation.Parameters);
165165
});
166166
}
167+
168+
[Fact]
169+
public async Task GetOpenApiParameters_SkipsDisallowedHeaders()
170+
{
171+
// Arrange
172+
var builder = CreateBuilder();
173+
174+
// Act
175+
builder.MapGet("/api/accept", ([FromHeader(Name = "Accept")] string value) => { });
176+
builder.MapGet("/api/accept-lower", ([FromHeader(Name = "accept")] string value) => { });
177+
builder.MapGet("/api/authorization", ([FromHeader(Name = "Authorization")] string value) => { });
178+
builder.MapGet("/api/authorization-lower", ([FromHeader(Name = "authorization")] string value) => { });
179+
builder.MapGet("/api/content-type", ([FromHeader(Name = "Content-Type")] string value) => { });
180+
builder.MapGet("/api/content-type-lower", ([FromHeader(Name = "content-type")] string value) => { });
181+
182+
// Assert
183+
await VerifyOpenApiDocument(builder, document =>
184+
{
185+
Assert.Null(document.Paths["/api/accept"].Operations[OperationType.Get].Parameters);
186+
Assert.Null(document.Paths["/api/accept-lower"].Operations[OperationType.Get].Parameters);
187+
Assert.Null(document.Paths["/api/authorization"].Operations[OperationType.Get].Parameters);
188+
Assert.Null(document.Paths["/api/authorization-lower"].Operations[OperationType.Get].Parameters);
189+
Assert.Null(document.Paths["/api/content-type"].Operations[OperationType.Get].Parameters);
190+
Assert.Null(document.Paths["/api/content-type-lower"].Operations[OperationType.Get].Parameters);
191+
});
192+
}
167193
}

0 commit comments

Comments
 (0)