Skip to content

fix: handle deserializing and writing empty security requirements #1426 #2323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);

// security
writer.WriteOptionalCollection(OpenApiConstants.Security, Security, callback);

writer.WriteOptionalOrEmptyCollection(OpenApiConstants.Security, Security, callback);
// servers
writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Models.Interfaces;
using System;
using System.Text.Json.Nodes;

namespace Microsoft.OpenApi.Reader.V2
{
Expand Down Expand Up @@ -94,7 +95,10 @@ internal static partial class OpenApiV2Deserializer
},
{
"security",
(o, n, t) => o.Security = n.CreateList(LoadSecurityRequirement, t)
(o, n, t) => { if (n.JsonNode is JsonArray)
{
o.Security = n.CreateList(LoadSecurityRequirement, t);
} }
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.References;
Expand Down Expand Up @@ -83,7 +84,13 @@ internal static partial class OpenApiV3Deserializer
},
{
"security",
(o, n, t) => o.Security = n.CreateList(LoadSecurityRequirement, t)
(o, n, t) =>
{
if (n.JsonNode is JsonArray)
{
o.Security = n.CreateList(LoadSecurityRequirement, t);
}
}
},
{
"servers",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.References;
Expand Down Expand Up @@ -96,8 +97,11 @@ internal static partial class OpenApiV31Deserializer
},
{
"security", (o, n, t) =>
{
o.Security = n.CreateList(LoadSecurityRequirement, t);
{
if (n.JsonNode is JsonArray)
{
o.Security = n.CreateList(LoadSecurityRequirement, t);
}
}
},
{
Expand Down
20 changes: 20 additions & 0 deletions src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,26 @@ public static void WriteOptionalCollection<T>(
writer.WriteCollectionInternal(name, elements, action);
}
}

/// <summary>
/// Write the optional or empty Open API object/element collection.
/// </summary>
/// <typeparam name="T">The Open API element type. <see cref="IOpenApiElement"/></typeparam>
/// <param name="writer">The Open API writer.</param>
/// <param name="name">The property name.</param>
/// <param name="elements">The collection values.</param>
/// <param name="action">The collection element writer action.</param>
public static void WriteOptionalOrEmptyCollection<T>(
this IOpenApiWriter writer,
string name,
IEnumerable<T>? elements,
Action<IOpenApiWriter, T> action)
{
if (elements != null)
{
writer.WriteCollectionInternal(name, elements, action);
}
}

/// <summary>
/// Write the required Open API object/element collection.
Expand Down
8 changes: 8 additions & 0 deletions test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,13 @@
</None>

<None Update="PublicApi\PublicApi.approved.txt" CopyToOutputDirectory="Always" />

<None Update="Models\Samples\docWithoutOperationSecurity.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

<None Update="Models\Samples\docWithEmptyOperationSecurity.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
73 changes: 71 additions & 2 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Writers;
using Microsoft.VisualBasic;
using VerifyXunit;
using Xunit;

Expand Down Expand Up @@ -2177,7 +2176,7 @@ public void SerializeAsThrowsIfVersionIsNotSupported()
}

[Fact]
public async Task SerializeDocWithSecuritySchemeWithInlineRefererencesWorks()
public async Task SerializeDocWithSecuritySchemeWithInlineReferencesWorks()
{
var expected = @"openapi: 3.0.4
info:
Expand Down Expand Up @@ -2218,5 +2217,75 @@ public async Task SerializeDocWithSecuritySchemeWithInlineRefererencesWorks()
var actual = stringWriter.ToString();
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
}

[Fact]
public async Task SerializeDocWithoutOperationSecurityWorks()
{
var expected = """
openapi: 3.0.4
info:
title: Repair Service
version: 1.0.0
servers:
- url: https://pluginrentu.azurewebsites.net/api
paths:
/repairs:
get:
summary: List all repairs
description: Returns a list of repairs with their details and images
operationId: listRepairs
responses:
'200':
description: A list of repairs
content:
application/json:
schema:
type: object
""";

var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithoutOperationSecurity.yaml", SettingsFixture.ReaderSettings)).Document;
var stringWriter = new StringWriter();
doc!.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }));
var actual = stringWriter.ToString();
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
var actualOperation = doc.Paths["/repairs"]!.Operations![HttpMethod.Get];
Assert.Null(actualOperation.Security);
}

[Fact]
public async Task SerializeDocWithEmptyOperationSecurityWorks()
{
var expected = """
openapi: 3.0.4
info:
title: Repair Service
version: 1.0.0
servers:
- url: https://pluginrentu.azurewebsites.net/api
paths:
/repairs:
get:
summary: List all repairs
description: Returns a list of repairs with their details and images
operationId: listRepairs
responses:
'200':
description: A list of repairs
content:
application/json:
schema:
type: object
security: [ ]
""";

var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithEmptyOperationSecurity.yaml", SettingsFixture.ReaderSettings)).Document;
var stringWriter = new StringWriter();
doc!.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }));
var actual = stringWriter.ToString();
Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral());
var actualOperation = doc.Paths["/repairs"]!.Operations![HttpMethod.Get];
Assert.NotNull(actualOperation.Security);
Assert.Empty(actualOperation.Security);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
openapi: 3.0.0
info:
title: Repair Service
version: 1.0.0
servers:
- url: https://pluginrentu.azurewebsites.net/api
paths:
/repairs:
get:
operationId: listRepairs
summary: List all repairs
description: Returns a list of repairs with their details and images
responses:
'200':
description: A list of repairs
content:
application/json:
schema:
type: object
security: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
openapi: 3.0.0
info:
title: Repair Service
version: 1.0.0
servers:
- url: https://pluginrentu.azurewebsites.net/api
paths:
/repairs:
get:
operationId: listRepairs
summary: List all repairs
description: Returns a list of repairs with their details and images
responses:
'200':
description: A list of repairs
content:
application/json:
schema:
type: object
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,7 @@ namespace Microsoft.OpenApi.Writers
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)
where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { }
public static void WriteOptionalObject<T>(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T? value, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, T> action) { }
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) { }
public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, string? value) { }
public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, bool value, bool defaultValue = false) { }
public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, bool? value, bool defaultValue = false) { }
Expand Down