Skip to content

Commit 26e7e5a

Browse files
committed
fix!: handle deserializing and writing empty security requirements microsoft#1426
make distinction between empty security requirements and no security requirements on an operation. empty security requirements are read as an empty list, no security requirements are read as null for OpenAPI v2/v3/v3.1. This is a breaking change, previously both cases were read as an empty list. also includes a change to OpenApiOperation.SerializeInternal so it can serialize these two cases separately. this required a new method OpenApiWriterExtensions.WriteOptionalOrEmptyCollection. includes unit tests, change to PublicApi.approved.txt to include the new method, and I removed a couple of unused usings and a typo in test name `SerializeDocWithSecuritySchemeWithInlineReferencesWorks`.
1 parent e4f5045 commit 26e7e5a

10 files changed

+155
-10
lines changed

src/Microsoft.OpenApi/Models/OpenApiOperation.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
213213
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
214214

215215
// security
216-
writer.WriteOptionalCollection(OpenApiConstants.Security, Security, callback);
217-
216+
writer.WriteOptionalOrEmptyCollection(OpenApiConstants.Security, Security, callback);
217+
218218
// servers
219219
writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback);
220220

src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
using Microsoft.OpenApi.Models.References;
1111
using Microsoft.OpenApi.Models.Interfaces;
1212
using System;
13-
using Microsoft.OpenApi.Interfaces;
13+
using System.Text.Json.Nodes;
1414

1515
namespace Microsoft.OpenApi.Reader.V2
1616
{
@@ -96,7 +96,10 @@ internal static partial class OpenApiV2Deserializer
9696
},
9797
{
9898
"security",
99-
(o, n, t) => o.Security = n.CreateList(LoadSecurityRequirement, t)
99+
(o, n, t) => { if (n.JsonNode is JsonArray)
100+
{
101+
o.Security = new List<OpenApiSecurityRequirement>(n.CreateList(LoadSecurityRequirement, t));
102+
} }
100103
},
101104
};
102105

src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Text.Json.Nodes;
78
using Microsoft.OpenApi.Extensions;
89
using Microsoft.OpenApi.Models;
910
using Microsoft.OpenApi.Models.References;
@@ -83,7 +84,13 @@ internal static partial class OpenApiV3Deserializer
8384
},
8485
{
8586
"security",
86-
(o, n, t) => o.Security = n.CreateList(LoadSecurityRequirement, t)
87+
(o, n, t) =>
88+
{
89+
if (n.JsonNode is JsonArray)
90+
{
91+
o.Security = n.CreateList(LoadSecurityRequirement, t);
92+
}
93+
}
8794
},
8895
{
8996
"servers",

src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text.Json.Nodes;
45
using Microsoft.OpenApi.Extensions;
56
using Microsoft.OpenApi.Models;
67
using Microsoft.OpenApi.Models.References;
@@ -96,8 +97,11 @@ internal static partial class OpenApiV31Deserializer
9697
},
9798
{
9899
"security", (o, n, t) =>
99-
{
100-
o.Security = n.CreateList(LoadSecurityRequirement, t);
100+
{
101+
if (n.JsonNode is JsonArray)
102+
{
103+
o.Security = new List<OpenApiSecurityRequirement>(n.CreateList(LoadSecurityRequirement, t));
104+
}
101105
}
102106
},
103107
{

src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs

+20
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,26 @@ public static void WriteOptionalCollection<T>(
217217
writer.WriteCollectionInternal(name, elements, action);
218218
}
219219
}
220+
221+
/// <summary>
222+
/// Write the optional or empty Open API object/element collection.
223+
/// </summary>
224+
/// <typeparam name="T">The Open API element type. <see cref="IOpenApiElement"/></typeparam>
225+
/// <param name="writer">The Open API writer.</param>
226+
/// <param name="name">The property name.</param>
227+
/// <param name="elements">The collection values.</param>
228+
/// <param name="action">The collection element writer action.</param>
229+
public static void WriteOptionalOrEmptyCollection<T>(
230+
this IOpenApiWriter writer,
231+
string name,
232+
IEnumerable<T>? elements,
233+
Action<IOpenApiWriter, T> action)
234+
{
235+
if (elements != null)
236+
{
237+
writer.WriteCollectionInternal(name, elements, action);
238+
}
239+
}
220240

221241
/// <summary>
222242
/// Write the required Open API object/element collection.

test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj

+8
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,13 @@
5656
</None>
5757

5858
<None Update="PublicApi\PublicApi.approved.txt" CopyToOutputDirectory="Always" />
59+
60+
<None Update="Models\Samples\docWithoutOperationSecurity.yaml">
61+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
62+
</None>
63+
64+
<None Update="Models\Samples\docWithEmptyOperationSecurity.yaml">
65+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
66+
</None>
5967
</ItemGroup>
6068
</Project>

test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs

+66-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
using System.Threading.Tasks;
1010
using Microsoft.OpenApi.Any;
1111
using Microsoft.OpenApi.Extensions;
12-
using Microsoft.OpenApi.Interfaces;
1312
using Microsoft.OpenApi.Models;
1413
using Microsoft.OpenApi.Models.Interfaces;
1514
using Microsoft.OpenApi.Models.References;
1615
using Microsoft.OpenApi.Writers;
17-
using Microsoft.VisualBasic;
1816
using VerifyXunit;
1917
using Xunit;
2018

@@ -2179,7 +2177,7 @@ public void SerializeAsThrowsIfVersionIsNotSupported()
21792177
}
21802178

21812179
[Fact]
2182-
public async Task SerializeDocWithSecuritySchemeWithInlineRefererencesWorks()
2180+
public async Task SerializeDocWithSecuritySchemeWithInlineReferencesWorks()
21832181
{
21842182
var expected = @"openapi: 3.0.4
21852183
info:
@@ -2220,5 +2218,70 @@ public async Task SerializeDocWithSecuritySchemeWithInlineRefererencesWorks()
22202218
var actual = stringWriter.ToString();
22212219
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
22222220
}
2221+
2222+
[Fact]
2223+
public async Task SerializeDocWithoutOperationSecurityWorks()
2224+
{
2225+
var expected = """
2226+
openapi: 3.0.4
2227+
info:
2228+
title: Repair Service
2229+
version: 1.0.0
2230+
servers:
2231+
- url: https://pluginrentu.azurewebsites.net/api
2232+
paths:
2233+
/repairs:
2234+
get:
2235+
summary: List all repairs
2236+
description: Returns a list of repairs with their details and images
2237+
operationId: listRepairs
2238+
responses:
2239+
'200':
2240+
description: A list of repairs
2241+
content:
2242+
application/json:
2243+
schema:
2244+
type: object
2245+
""";
2246+
2247+
var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithoutOperationSecurity.yaml", SettingsFixture.ReaderSettings)).Document;
2248+
var stringWriter = new StringWriter();
2249+
doc!.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }));
2250+
var actual = stringWriter.ToString();
2251+
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
2252+
}
2253+
2254+
[Fact]
2255+
public async Task SerializeDocWithEmptyOperationSecurityWorks()
2256+
{
2257+
var expected = """
2258+
openapi: 3.0.4
2259+
info:
2260+
title: Repair Service
2261+
version: 1.0.0
2262+
servers:
2263+
- url: https://pluginrentu.azurewebsites.net/api
2264+
paths:
2265+
/repairs:
2266+
get:
2267+
summary: List all repairs
2268+
description: Returns a list of repairs with their details and images
2269+
operationId: listRepairs
2270+
responses:
2271+
'200':
2272+
description: A list of repairs
2273+
content:
2274+
application/json:
2275+
schema:
2276+
type: object
2277+
security: [ ]
2278+
""";
2279+
2280+
var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithEmptyOperationSecurity.yaml", SettingsFixture.ReaderSettings)).Document;
2281+
var stringWriter = new StringWriter();
2282+
doc!.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }));
2283+
var actual = stringWriter.ToString();
2284+
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
2285+
}
22232286
}
22242287
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Repair Service
4+
version: 1.0.0
5+
servers:
6+
- url: https://pluginrentu.azurewebsites.net/api
7+
paths:
8+
/repairs:
9+
get:
10+
operationId: listRepairs
11+
summary: List all repairs
12+
description: Returns a list of repairs with their details and images
13+
responses:
14+
'200':
15+
description: A list of repairs
16+
content:
17+
application/json:
18+
schema:
19+
type: object
20+
security: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Repair Service
4+
version: 1.0.0
5+
servers:
6+
- url: https://pluginrentu.azurewebsites.net/api
7+
paths:
8+
/repairs:
9+
get:
10+
operationId: listRepairs
11+
summary: List all repairs
12+
description: Returns a list of repairs with their details and images
13+
responses:
14+
'200':
15+
description: A list of repairs
16+
content:
17+
application/json:
18+
schema:
19+
type: object

test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1975,6 +1975,7 @@ namespace Microsoft.OpenApi.Writers
19751975
public static void WriteOptionalMap<T>(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.Dictionary<string, T>? elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, string, T> action)
19761976
where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { }
19771977
public static void WriteOptionalObject<T>(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T? value, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, T> action) { }
1978+
public static void WriteOptionalOrEmptyCollection<T>(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable<T>? elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, T> action) { }
19781979
public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, string? value) { }
19791980
public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, bool value, bool defaultValue = false) { }
19801981
public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, bool? value, bool defaultValue = false) { }

0 commit comments

Comments
 (0)