From 00ec76d49749b8ee55482f37e458c3e2005462da Mon Sep 17 00:00:00 2001 From: Mahdi Golestan Date: Wed, 22 Jul 2026 15:53:50 +0330 Subject: [PATCH 1/3] feat: add SerializeAsJsonAsync overloads accepting OpenApiJsonWriterSettings --- .../OpenApiSerializableExtensions.cs | 65 ++++++++++++++++++- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 3 + .../OpenApiSerializableExtensionsTests.cs | 56 ++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index a1f2aca4d..d67493f8b 100755 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Globalization; @@ -21,12 +21,28 @@ public static class OpenApiSerializableExtensions /// The output stream. /// The Open API specification version. /// The cancellation token. +#pragma warning disable RS0027 // The settings overload below has the same parameter count but different types; no ambiguity exists. public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenApiSpecVersion specVersion, CancellationToken cancellationToken = default) +#pragma warning restore RS0027 where T : IOpenApiSerializable { return element.SerializeAsync(stream, specVersion, OpenApiConstants.Json, cancellationToken); } + /// + /// Serialize the to the Open API document (JSON) using the given stream, specification version and settings. + /// + /// the + /// The Open API element. + /// The output stream. + /// The Open API specification version. + /// Settings controlling JSON output, including for compact formatting. + public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenApiSpecVersion specVersion, OpenApiJsonWriterSettings settings) + where T : IOpenApiSerializable + { + return element.SerializeAsync(stream, specVersion, OpenApiConstants.Json, settings, CancellationToken.None); + } + /// /// Serializes the to the Open API document (YAML) using the given stream and specification version. /// @@ -104,7 +120,9 @@ public static Task SerializeAsync( /// The output writer. /// Version of the specification the output should conform to /// The cancellation token. +#pragma warning disable RS0027 // The settings-bearing SerializeAsync overloads below have the same parameter count but different types; no ambiguity exists. public static Task SerializeAsync(this T element, IOpenApiWriter writer, OpenApiSpecVersion specVersion, CancellationToken cancellationToken = default) +#pragma warning restore RS0027 where T : IOpenApiSerializable { Utils.CheckArgumentNull(element); @@ -142,15 +160,33 @@ public static Task SerializeAsync(this T element, IOpenApiWriter writer, Open /// The Open API element. /// The Open API specification version. /// The cancellation token. +#pragma warning disable RS0027 // The settings overload below has the same parameter count but different types; no ambiguity exists. public static Task SerializeAsJsonAsync( this T element, OpenApiSpecVersion specVersion, CancellationToken cancellationToken = default) +#pragma warning restore RS0027 where T : IOpenApiSerializable { return element.SerializeAsync(specVersion, OpenApiConstants.Json, cancellationToken); } + /// + /// Serializes the to the Open API document as a string in JSON format using the given settings. + /// + /// the + /// The Open API element. + /// The Open API specification version. + /// Settings controlling JSON output, including for compact formatting. + public static Task SerializeAsJsonAsync( + this T element, + OpenApiSpecVersion specVersion, + OpenApiJsonWriterSettings settings) + where T : IOpenApiSerializable + { + return element.SerializeAsync(specVersion, OpenApiConstants.Json, settings); + } + /// /// Serializes the to the Open API document as a string in YAML format. /// @@ -175,11 +211,13 @@ public static Task SerializeAsYamlAsync( /// The Open API specification version. /// Open API document format. /// The cancellation token. +#pragma warning disable RS0027 // The settings overload below has the same parameter count but different types; no ambiguity exists. public static async Task SerializeAsync( this T element, OpenApiSpecVersion specVersion, string format, CancellationToken cancellationToken = default) +#pragma warning restore RS0027 where T : IOpenApiSerializable { Utils.CheckArgumentNull(element); @@ -195,5 +233,30 @@ public static async Task SerializeAsync( return await streamReader.ReadToEndAsync().ConfigureAwait(false); #endif } + + /// + /// Serializes the to the Open API document as a string in the given format using the given settings. + /// + /// the + /// The Open API element. + /// The Open API specification version. + /// Open API document format. + /// Provide configuration settings for controlling writing output. + public static async Task SerializeAsync( + this T element, + OpenApiSpecVersion specVersion, + string format, + OpenApiWriterSettings? settings) + where T : IOpenApiSerializable + { + Utils.CheckArgumentNull(element); + + using var stream = new MemoryStream(); + await element.SerializeAsync(stream, specVersion, format, settings, CancellationToken.None).ConfigureAwait(false); + stream.Position = 0; + + using var streamReader = new StreamReader(stream); + return await streamReader.ReadToEndAsync().ConfigureAwait(false); + } } } diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 4699f67cc..99b9f0247 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings) -> System.Threading.Tasks.Task! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, System.IO.Stream! stream, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings) -> System.Threading.Tasks.Task! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, string! format, Microsoft.OpenApi.OpenApiWriterSettings? settings) -> System.Threading.Tasks.Task! diff --git a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs index a1e323ebf..f55fb7061 100644 --- a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs @@ -90,4 +90,60 @@ public async Task UsesTheTerseOutputInformationFromSettingsNoSettings() Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"description\": \"A sample parameter\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output); } + + [Fact] + public async Task SerializeAsJsonAsync_WithTerseSettings_WritesToStream() + { + var parameter = new OpenApiParameter + { + Name = "param1", + In = ParameterLocation.Query, + Schema = new OpenApiSchema { Type = JsonSchemaType.String } + }; + + var settings = new OpenApiJsonWriterSettings { Terse = true }; + + using var stream = new MemoryStream(); + await parameter.SerializeAsJsonAsync(stream, OpenApiSpecVersion.OpenApi3_1, settings); + + stream.Position = 0; + using var reader = new StreamReader(stream); + var output = await reader.ReadToEndAsync(); + + Assert.Equal("{\"name\":\"param1\",\"in\":\"query\",\"schema\":{\"type\":\"string\"}}", output); + } + + [Fact] + public async Task SerializeAsJsonAsync_WithTerseSettings_ReturnsCompactString() + { + var parameter = new OpenApiParameter + { + Name = "param1", + In = ParameterLocation.Query, + Schema = new OpenApiSchema { Type = JsonSchemaType.String } + }; + + var settings = new OpenApiJsonWriterSettings { Terse = true }; + + var output = await parameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, settings); + + Assert.Equal("{\"name\":\"param1\",\"in\":\"query\",\"schema\":{\"type\":\"string\"}}", output); + } + + [Fact] + public async Task SerializeAsync_WithSettings_ReturnsFormattedString() + { + var parameter = new OpenApiParameter + { + Name = "param1", + In = ParameterLocation.Query, + Schema = new OpenApiSchema { Type = JsonSchemaType.String } + }; + + var settings = new OpenApiJsonWriterSettings { Terse = false }; + + var output = await parameter.SerializeAsync(OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings); + + Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output); + } } From 3147529050d6413ced698f005ac7bb3b03cc4d00 Mon Sep 17 00:00:00 2001 From: Mahdi Golestan Date: Thu, 23 Jul 2026 09:30:53 +0330 Subject: [PATCH 2/3] fix: add cancellation token parameter to SerializeAsJsonAsync settings overload --- .../Extensions/OpenApiSerializableExtensions.cs | 5 +++-- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 2 +- .../Extensions/OpenApiSerializableExtensionsTests.cs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index d67493f8b..35ca87235 100755 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -37,10 +37,11 @@ public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenAp /// The output stream. /// The Open API specification version. /// Settings controlling JSON output, including for compact formatting. - public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenApiSpecVersion specVersion, OpenApiJsonWriterSettings settings) + /// The cancellation token. + public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenApiSpecVersion specVersion, OpenApiJsonWriterSettings settings, CancellationToken cancellationToken) where T : IOpenApiSerializable { - return element.SerializeAsync(stream, specVersion, OpenApiConstants.Json, settings, CancellationToken.None); + return element.SerializeAsync(stream, specVersion, OpenApiConstants.Json, settings, cancellationToken); } /// diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 99b9f0247..0cb3a6efb 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -2,5 +2,5 @@ const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings) -> System.Threading.Tasks.Task! -static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, System.IO.Stream! stream, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings) -> System.Threading.Tasks.Task! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, System.IO.Stream! stream, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, string! format, Microsoft.OpenApi.OpenApiWriterSettings? settings) -> System.Threading.Tasks.Task! diff --git a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs index f55fb7061..ab9beebd4 100644 --- a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -104,7 +105,7 @@ public async Task SerializeAsJsonAsync_WithTerseSettings_WritesToStream() var settings = new OpenApiJsonWriterSettings { Terse = true }; using var stream = new MemoryStream(); - await parameter.SerializeAsJsonAsync(stream, OpenApiSpecVersion.OpenApi3_1, settings); + await parameter.SerializeAsJsonAsync(stream, OpenApiSpecVersion.OpenApi3_1, settings, CancellationToken.None); stream.Position = 0; using var reader = new StreamReader(stream); From d8c2a80a67c69b4a54e6d94c46ee4e96eacf220b Mon Sep 17 00:00:00 2001 From: Mahdi Golestan Date: Sat, 25 Jul 2026 08:41:41 +0330 Subject: [PATCH 3/3] fix: add cancellation token parameter to SerializeAsJsonAsync settings overload --- .../OpenApiSerializableExtensions.cs | 26 +++++++++++++++---- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 6 ++--- .../OpenApiSerializableExtensionsTests.cs | 6 ++--- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index 35ca87235..d5f23f6f2 100755 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -38,7 +38,9 @@ public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenAp /// The Open API specification version. /// Settings controlling JSON output, including for compact formatting. /// The cancellation token. - public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenApiSpecVersion specVersion, OpenApiJsonWriterSettings settings, CancellationToken cancellationToken) +#pragma warning disable RS0026 // Intentional overload alongside the CancellationToken-only variant; types are unambiguous. + public static Task SerializeAsJsonAsync(this T element, Stream stream, OpenApiSpecVersion specVersion, OpenApiJsonWriterSettings settings, CancellationToken cancellationToken = default) +#pragma warning restore RS0026 where T : IOpenApiSerializable { return element.SerializeAsync(stream, specVersion, OpenApiConstants.Json, settings, cancellationToken); @@ -68,12 +70,14 @@ public static Task SerializeAsYamlAsync(this T element, Stream stream, OpenAp /// The Open API specification version. /// The output format (JSON or YAML). /// The cancellation token. +#pragma warning disable RS0027 // The settings overload below has the same parameter count but different types; no ambiguity exists. public static Task SerializeAsync( this T element, Stream stream, OpenApiSpecVersion specVersion, string format, CancellationToken cancellationToken = default) +#pragma warning restore RS0027 where T : IOpenApiSerializable { return element.SerializeAsync(stream, specVersion, format, null, cancellationToken); @@ -179,13 +183,17 @@ public static Task SerializeAsJsonAsync( /// The Open API element. /// The Open API specification version. /// Settings controlling JSON output, including for compact formatting. + /// The cancellation token. +#pragma warning disable RS0026 // Intentional overload alongside the CancellationToken-only variant; types are unambiguous. public static Task SerializeAsJsonAsync( this T element, OpenApiSpecVersion specVersion, - OpenApiJsonWriterSettings settings) + OpenApiJsonWriterSettings settings, + CancellationToken cancellationToken = default) +#pragma warning restore RS0026 where T : IOpenApiSerializable { - return element.SerializeAsync(specVersion, OpenApiConstants.Json, settings); + return element.SerializeAsync(specVersion, OpenApiConstants.Json, settings, cancellationToken); } /// @@ -243,21 +251,29 @@ public static async Task SerializeAsync( /// The Open API specification version. /// Open API document format. /// Provide configuration settings for controlling writing output. + /// The cancellation token. +#pragma warning disable RS0026 // Intentional overload alongside the CancellationToken-only variant; types are unambiguous. public static async Task SerializeAsync( this T element, OpenApiSpecVersion specVersion, string format, - OpenApiWriterSettings? settings) + OpenApiWriterSettings? settings, + CancellationToken cancellationToken = default) +#pragma warning restore RS0026 where T : IOpenApiSerializable { Utils.CheckArgumentNull(element); using var stream = new MemoryStream(); - await element.SerializeAsync(stream, specVersion, format, settings, CancellationToken.None).ConfigureAwait(false); + await element.SerializeAsync(stream, specVersion, format, settings, cancellationToken).ConfigureAwait(false); stream.Position = 0; using var streamReader = new StreamReader(stream); +#if NET7_0_OR_GREATER + return await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); +#else return await streamReader.ReadToEndAsync().ConfigureAwait(false); +#endif } } } diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 0cb3a6efb..fea22385f 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1,6 +1,6 @@ #nullable enable const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! -static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings) -> System.Threading.Tasks.Task! -static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, System.IO.Stream! stream, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, string! format, Microsoft.OpenApi.OpenApiWriterSettings? settings) -> System.Threading.Tasks.Task! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsJsonAsync(this T element, System.IO.Stream! stream, Microsoft.OpenApi.OpenApiSpecVersion specVersion, Microsoft.OpenApi.OpenApiJsonWriterSettings! settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +static Microsoft.OpenApi.OpenApiSerializableExtensions.SerializeAsync(this T element, Microsoft.OpenApi.OpenApiSpecVersion specVersion, string! format, Microsoft.OpenApi.OpenApiWriterSettings? settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! diff --git a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs index ab9beebd4..051f74e7c 100644 --- a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs @@ -105,7 +105,7 @@ public async Task SerializeAsJsonAsync_WithTerseSettings_WritesToStream() var settings = new OpenApiJsonWriterSettings { Terse = true }; using var stream = new MemoryStream(); - await parameter.SerializeAsJsonAsync(stream, OpenApiSpecVersion.OpenApi3_1, settings, CancellationToken.None); + await parameter.SerializeAsJsonAsync(stream, OpenApiSpecVersion.OpenApi3_1, settings, TestContext.Current.CancellationToken); stream.Position = 0; using var reader = new StreamReader(stream); @@ -126,7 +126,7 @@ public async Task SerializeAsJsonAsync_WithTerseSettings_ReturnsCompactString() var settings = new OpenApiJsonWriterSettings { Terse = true }; - var output = await parameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, settings); + var output = await parameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, settings, TestContext.Current.CancellationToken); Assert.Equal("{\"name\":\"param1\",\"in\":\"query\",\"schema\":{\"type\":\"string\"}}", output); } @@ -143,7 +143,7 @@ public async Task SerializeAsync_WithSettings_ReturnsFormattedString() var settings = new OpenApiJsonWriterSettings { Terse = false }; - var output = await parameter.SerializeAsync(OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings); + var output = await parameter.SerializeAsync(OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings, TestContext.Current.CancellationToken); Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output); }