From 0b31edd1a604b8eab463c33acdb408c5869a84be Mon Sep 17 00:00:00 2001 From: Abdullah Alaqeel Date: Thu, 16 Jul 2026 16:35:45 +0300 Subject: [PATCH 01/53] Merge pull request #2921 from aqeelat/fix/schema-reference-sibling-semantics docs(schema): apply additive JSON Schema semantics to $ref sibling getters --- .../Models/References/OpenApiSchemaReference.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index 7f206d686..6a9455775 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -9,7 +9,11 @@ namespace Microsoft.OpenApi { #pragma warning disable CS0618 /// - /// Schema reference object + /// Schema reference object. + /// Convenience getters return $ref-sibling keyword values authored on + /// before falling back to resolved values from . + /// These getters are object-model conveniences and do not represent JSON Schema + /// evaluation semantics. /// public class OpenApiSchemaReference : BaseOpenApiReferenceHolder, IOpenApiSchema, IOpenApiSchemaMissingProperties, IOpenApiSchemaWithUnevaluatedProperties, IOpenApiExtensible { From 1984e31bae49a72f09967a0a4fec227a3b906711 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:22:50 +0000 Subject: [PATCH 02/53] chore(deps): bump actions/setup-dotnet from 5 to 6 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5 to 6. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-cd.yml | 12 ++++++------ .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/sonarcloud.yml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index d91c08ad5..4230531b0 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -18,12 +18,12 @@ jobs: GITHUB_RUN_NUMBER: ${{ github.run_number }} steps: - name: Setup .NET 8 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 8.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.x @@ -83,12 +83,12 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET 8 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 8.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.x @@ -105,12 +105,12 @@ jobs: uses: actions/checkout@v7 - name: Setup .NET 8 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 8.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.x diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4f5ad9d47..cf00a08d9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,12 +23,12 @@ jobs: uses: actions/checkout@v7 - name: Setup .NET 8 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 8.0.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index abac4ef81..e315778e8 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -40,11 +40,11 @@ jobs: distribution: 'adopt' java-version: 17 - name: Setup .NET 8 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 8.0.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x - uses: actions/checkout@v7 From eac3d669fd49909bf3736aad600c90e60dd4059e Mon Sep 17 00:00:00 2001 From: "Gavin Barron (from Dev Box)" Date: Fri, 17 Jul 2026 12:59:12 -0700 Subject: [PATCH 03/53] Route NuGet restore through the CFS central package feed Adds NuGetAuthenticate@1 and a generated nuget.config pointing at the GraphDeveloperExperiences_Public Azure Artifacts feed so the CI build restores packages from the authenticated central feed (network isolation / CFS compliance). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9bd6fe40-0d69-4e3f-927b-8628c0ab6d34 --- .azure-pipelines/ci-build.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 8bef85221..fb7413240 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -79,6 +79,20 @@ extends: checkLatest: true # Build the Product project + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to Azure Artifacts' + + - pwsh: | + @" + + + + + + + + "@ | Set-Content -Path "$(Build.SourcesDirectory)/nuget.config" -Encoding UTF8 + displayName: 'Create nuget.config (central feed)' - task: DotNetCoreCLI@2 displayName: 'build' inputs: From 60971ba87ced4162e471ebfc1b878776d09cc62f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 08:47:45 -0400 Subject: [PATCH 04/53] feat: serialize license identifier as extension for earlier versions --- .../Models/OpenApiConstants.cs | 5 +++ .../Models/OpenApiLicense.cs | 2 ++ src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 1 + .../Models/OpenApiLicenseTests.cs | 35 +++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 11f3926c5..9467b022f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -235,6 +235,11 @@ public static class OpenApiConstants /// public const string Identifier = "identifier"; + /// + /// Field: x-oai-license-identifier + /// + public const string OaiLicenseIdentifier = "x-oai-license-identifier"; + /// /// Field: Namespace /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs index 6645cc8d3..4c9a32945 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs @@ -73,6 +73,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) public virtual void SerializeAsV3(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); + writer.WriteProperty(OpenApiConstants.OaiLicenseIdentifier, Identifier); writer.WriteEndObject(); } @@ -82,6 +83,7 @@ public virtual void SerializeAsV3(IOpenApiWriter writer) public virtual void SerializeAsV2(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi2_0); + writer.WriteProperty(OpenApiConstants.OaiLicenseIdentifier, Identifier); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7dc5c5811..e763311ca 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs index 58cdae2ce..d81dce9b4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs @@ -148,6 +148,25 @@ public async Task SerializeLicenseWithIdentifierAsJsonWorks() Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); } + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + public async Task SerializeLicenseWithIdentifierAsJsonWorksForV3AndV2(OpenApiSpecVersion version) + { + // Arrange + var expected = + @"{ + ""name"": ""Apache 2.0"", + ""x-oai-license-identifier"": ""Apache-2.0"" +}"; + + // Act + var actual = await LicenseWithIdentifier.SerializeAsJsonAsync(version); + + // Assert + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); + } + [Fact] public async Task SerializeLicenseWithIdentifierAsYamlWorks() { @@ -161,5 +180,21 @@ public async Task SerializeLicenseWithIdentifierAsYamlWorks() // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + public async Task SerializeLicenseWithIdentifierAsYamlWorksForV3AndV2(OpenApiSpecVersion version) + { + // Arrange + var expected = @"name: Apache 2.0 +x-oai-license-identifier: Apache-2.0"; + + // Act + var actual = await LicenseWithIdentifier.SerializeAsYamlAsync(version); + + // Assert + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); + } } } From edf3c6789aafcc6156e193ee6f3bc23d886e8052 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 09:32:14 -0400 Subject: [PATCH 05/53] fix: serialize examples as extension in v2/v3 --- .../Models/OpenApiConstants.cs | 5 ++ src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 3 ++ src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 1 + .../Models/OpenApiSchemaTests.cs | 54 +++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 11f3926c5..bf95bf9c4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -805,6 +805,11 @@ public static class OpenApiConstants /// public const string ExamplesExtension = "x-examples"; + /// + /// Extension: x-jsonschema-examples + /// + public const string JsonSchemaExamplesExtension = "x-jsonschema-examples"; + /// /// Field: version3_0_0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 2ae310294..015fb176a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -746,6 +746,7 @@ private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action nodeWriter.WriteAny(s)); } internal void WriteAsItemsProperties(IOpenApiWriter writer) @@ -990,6 +991,8 @@ private void SerializeAsV2( writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, (w, s) => s.SerializeAsV2(w)); } + writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); + // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7dc5c5811..17373d699 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index b4b188617..5a31f2e51 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1752,6 +1752,60 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + public async Task SerializeExamplesAsExtensionInEarlierVersions(OpenApiSpecVersion version) + { + var expected = """ + { + "x-jsonschema-examples": [ + "example value", + 42 + ] + } + """; + var schema = new OpenApiSchema + { + Examples = + [ + JsonValue.Create("example value")!, + JsonValue.Create(42)! + ] + }; + + var actual = await schema.SerializeAsJsonAsync(version); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + public async Task SerializeExamplesAsJsonSchemaKeywordInV31AndLater(OpenApiSpecVersion version) + { + var expected = """ + { + "examples": [ + "example value", + 42 + ] + } + """; + var schema = new OpenApiSchema + { + Examples = + [ + JsonValue.Create("example value")!, + JsonValue.Create(42)! + ] + }; + + var actual = await schema.SerializeAsJsonAsync(version); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] From 2441224df7c6196f8b0c008c7c61cd2b992eed3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:49:21 +0000 Subject: [PATCH 06/53] Initial plan From 5d8ec3a32d49059b68d00cf2ee267a6537c1a678 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:56:05 +0000 Subject: [PATCH 07/53] fix(schema): serialize compatibility examples from examples list --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 31 ++++++++++++++++--- .../Models/OpenApiSchemaTests.cs | 27 ++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 015fb176a..ebaa49897 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -620,7 +620,10 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); // example - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + writer.WriteOptionalObject( + OpenApiConstants.Example, + version < OpenApiSpecVersion.OpenApi3_1 ? GetCompatibilityExample() : Example, + (w, e) => w.WriteAny(e)); // deprecated writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); @@ -746,7 +749,7 @@ private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action nodeWriter.WriteAny(s)); + writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s)); } internal void WriteAsItemsProperties(IOpenApiWriter writer) @@ -960,7 +963,7 @@ private void SerializeAsV2( writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w)); // example - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + writer.WriteOptionalObject(OpenApiConstants.Example, GetCompatibilityExample(), (w, e) => w.WriteAny(e)); // x-nullable extension SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0); @@ -991,7 +994,7 @@ private void SerializeAsV2( writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, (w, s) => s.SerializeAsV2(w)); } - writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); + writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s)); // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); @@ -1027,6 +1030,26 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion return false; } + private JsonNode? GetCompatibilityExample() + { + return Example ?? Examples?.FirstOrDefault(); + } + + private IEnumerable? GetCompatibilityExamplesExtension() + { + if (Examples is null || Examples.Count == 0) + { + return null; + } + + if (Example is not null) + { + return Examples; + } + + return Examples.Count > 1 ? Examples.Skip(1) : null; + } + private static bool IsPowerOfTwo(int x) { return x != 0 && (x & (x - 1)) == 0; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 5a31f2e51..be253c621 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1755,12 +1755,35 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeExamplesAsExtensionInEarlierVersions(OpenApiSpecVersion version) + public async Task SerializeSingleExampleAsExamplePropertyInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) { var expected = """ { + "example": "example value" + } + """; + var schema = new OpenApiSchema + { + Examples = + [ + JsonValue.Create("example value")! + ] + }; + + var actual = await schema.SerializeAsJsonAsync(version); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + public async Task SerializeMultipleExamplesAsExampleAndExtensionInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) + { + var expected = """ + { + "example": "example value", "x-jsonschema-examples": [ - "example value", 42 ] } From f0e39010fa3416d06c9a41328342e3bf446a0f7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:00:31 +0000 Subject: [PATCH 08/53] test(schema): shorten compatibility example test names --- test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index be253c621..6cb47b0d7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1755,7 +1755,7 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeSingleExampleAsExamplePropertyInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) + public async Task SerializeSingleExampleAsExampleInV2V3WhenExampleUnset(OpenApiSpecVersion version) { var expected = """ { @@ -1778,7 +1778,7 @@ public async Task SerializeSingleExampleAsExamplePropertyInEarlierVersionsWhenEx [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeMultipleExamplesAsExampleAndExtensionInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) + public async Task SerializeMultipleExamplesInV2V3WhenExampleUnset(OpenApiSpecVersion version) { var expected = """ { From 9387b01188c4d2297eaaaa08f06c41c83179f688 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 12:52:10 -0400 Subject: [PATCH 09/53] fix: deserialize metadata url from extension in earlier version Signed-off-by: Vincent Biret --- .../Models/OpenApiConstants.cs | 5 ++++ .../Models/OpenApiSecurityScheme.cs | 2 +- .../V2/OpenApiSecuritySchemeDeserializer.cs | 16 ++++++++++- .../V3/OpenApiSecuritySchemeDeserializer.cs | 10 ++++++- .../V31/OpenApiSecuritySchemeDeserializer.cs | 10 ++++++- .../V2Tests/OpenApiSecuritySchemeTests.cs | 24 ++++++++++++++++ ...ecuritySchemeReferenceDeserializerTests.cs | 27 ++++++++++++++++++ .../V3Tests/OpenApiSecuritySchemeTests.cs | 28 +++++++++++++++++++ .../Models/OpenApiSecuritySchemeTests.cs | 2 +- 9 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index bf95bf9c4..ef53d37af 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -770,6 +770,11 @@ public static class OpenApiConstants /// public const string OAuth2MetadataUrl = "oauth2MetadataUrl"; + /// + /// Extension: x-oai-oauth2-metadata-url + /// + internal const string OAuth2MetadataUrlExtension = "x-oai-oauth2-metadata-url"; + /// /// Field: OpenIdConnectUrl /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 35f491e5b..225910968 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -133,7 +133,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version } else { - writer.WriteProperty("x-oauth2-metadata-url", OAuth2MetadataUrl?.ToString()); + writer.WriteProperty(OpenApiConstants.OAuth2MetadataUrlExtension, OAuth2MetadataUrl?.ToString()); } writer.WriteOptionalObject(OpenApiConstants.Flows, Flows, callback); break; diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs index ae62bd170..3daf7cf89 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs @@ -99,7 +99,21 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _securitySchemePatternFields = new() { - {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _, c) => o.AddExtension(p, LoadExtension(p, n, c))} + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _, c) => + { + if (p.Equals(OpenApiConstants.OAuth2MetadataUrlExtension, StringComparison.OrdinalIgnoreCase)) + { + var metadataUrl = n.GetScalarValue(); + if (metadataUrl != null) + { + o.OAuth2MetadataUrl = new(metadataUrl, UriKind.RelativeOrAbsolute); + } + } + else + { + o.AddExtension(p, LoadExtension(p, n, c)); + } + }} }; public static IOpenApiSecurityScheme LoadSecurityScheme(JsonNode node, OpenApiDocument hostDocument, ParsingContext context) diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs index 4cb13d995..3f9d8be83 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs @@ -76,7 +76,15 @@ internal static partial class OpenApiV3Deserializer { {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _, c) => { - if (p.Equals("x-oai-deprecated", StringComparison.OrdinalIgnoreCase)) + if (p.Equals(OpenApiConstants.OAuth2MetadataUrlExtension, StringComparison.OrdinalIgnoreCase)) + { + var metadataUrl = n.GetScalarValue(); + if (metadataUrl != null) + { + o.OAuth2MetadataUrl = new(metadataUrl, UriKind.RelativeOrAbsolute); + } + } + else if (p.Equals("x-oai-deprecated", StringComparison.OrdinalIgnoreCase)) { var deprecated = n.GetScalarValue(); if (deprecated != null) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs index 4e02a2bf6..fac4b87c1 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs @@ -83,7 +83,15 @@ internal static partial class OpenApiV31Deserializer { {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _, c) => { - if (p.Equals("x-oai-deprecated", StringComparison.OrdinalIgnoreCase)) + if (p.Equals(OpenApiConstants.OAuth2MetadataUrlExtension, StringComparison.OrdinalIgnoreCase)) + { + var metadataUrl = n.GetScalarValue(); + if (metadataUrl != null) + { + o.OAuth2MetadataUrl = new(metadataUrl, UriKind.RelativeOrAbsolute); + } + } + else if (p.Equals("x-oai-deprecated", StringComparison.OrdinalIgnoreCase)) { var deprecated = n.GetScalarValue(); if (deprecated != null) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs index e108774f1..14787a086 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V2; using Microsoft.OpenApi.YamlReader; @@ -209,6 +210,29 @@ public void ParseOAuth2AccessCodeSecuritySchemeShouldSucceed() }, securityScheme); } + [Fact] + public void ParseOAuth2SecuritySchemeWithMetadataUrlExtensionShouldSucceed() + { + var json = + """ + { + "type": "oauth2", + "flow": "application", + "tokenUrl": "https://idp.example.com/oauth/token", + "scopes": { + "scope:one": "Scope one" + }, + "x-oai-oauth2-metadata-url": "https://idp.example.com/.well-known/oauth-authorization-server" + } + """; + + var securityScheme = Assert.IsType( + OpenApiV2Deserializer.LoadSecurityScheme(JsonNode.Parse(json)!, new(), new ParsingContext(new()))); + + Assert.Equal(new Uri("https://idp.example.com/.well-known/oauth-authorization-server"), securityScheme.OAuth2MetadataUrl); + Assert.True(securityScheme.Extensions is null || !securityScheme.Extensions.ContainsKey(OpenApiConstants.OAuth2MetadataUrlExtension)); + } + static YamlDocument LoadYamlDocument(Stream input) { using var reader = new StreamReader(input); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs index 7767148a5..a456d7b98 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs @@ -1,3 +1,4 @@ +using System; using System.Text.Json.Nodes; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V31; @@ -67,4 +68,30 @@ public void ShouldDeserializeSecuritySchemeWithXOaiDeprecatedExtension() Assert.Equal(ParameterLocation.Header, resultScheme.In); Assert.True(resultScheme.Deprecated); } + + [Fact] + public void ShouldDeserializeSecuritySchemeWithOAuth2MetadataUrlExtension() + { + var json = + """ + { + "type": "oauth2", + "x-oai-oauth2-metadata-url": "https://idp.example.com/.well-known/oauth-authorization-server", + "flows": { + "clientCredentials": { + "tokenUrl": "https://idp.example.com/oauth/token", + "scopes": { + "scope:one": "Scope one" + } + } + } + } + """; + + var result = OpenApiV31Deserializer.LoadSecurityScheme(JsonNode.Parse(json), new OpenApiDocument(), new ParsingContext(new())); + + var resultScheme = Assert.IsType(result); + Assert.Equal(new Uri("https://idp.example.com/.well-known/oauth-authorization-server"), resultScheme.OAuth2MetadataUrl); + Assert.True(resultScheme.Extensions is null || !resultScheme.Extensions.ContainsKey(OpenApiConstants.OAuth2MetadataUrlExtension)); + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs index f2591e99a..734b80a31 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs @@ -3,8 +3,10 @@ using System; using System.IO; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V3; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -122,5 +124,31 @@ public async Task ParseOAuth2SecuritySchemeWithDeviceAuthorizationUrlShouldSucce Assert.NotNull(securityScheme.Flows.AuthorizationCode.Scopes); Assert.Equal(2, securityScheme.Flows.AuthorizationCode.Scopes.Count); } + + [Fact] + public void ParseOAuth2SecuritySchemeWithMetadataUrlExtensionShouldSucceed() + { + var json = + """ + { + "type": "oauth2", + "x-oai-oauth2-metadata-url": "https://idp.example.com/.well-known/oauth-authorization-server", + "flows": { + "clientCredentials": { + "tokenUrl": "https://idp.example.com/oauth/token", + "scopes": { + "scope:one": "Scope one" + } + } + } + } + """; + + var securityScheme = Assert.IsType( + OpenApiV3Deserializer.LoadSecurityScheme(JsonNode.Parse(json)!, new(), new ParsingContext(new()))); + + Assert.Equal(new Uri("https://idp.example.com/.well-known/oauth-authorization-server"), securityScheme.OAuth2MetadataUrl); + Assert.True(securityScheme.Extensions is null || !securityScheme.Extensions.ContainsKey(OpenApiConstants.OAuth2MetadataUrlExtension)); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index 335d2d9b2..31fe34ff4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -461,7 +461,7 @@ public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV31JsonOmitsMetad { "type": "oauth2", "description": "description1", - "x-oauth2-metadata-url": "https://idp.example.com/.well-known/oauth-authorization-server", + "x-oai-oauth2-metadata-url": "https://idp.example.com/.well-known/oauth-authorization-server", "flows": { "clientCredentials": { "tokenUrl": "https://idp.example.com/oauth/token", From e5a108004a6c4637d4dd7dc27a417a2d3e29d70b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 13:06:40 -0400 Subject: [PATCH 10/53] feat: adds deserialization of the example extension Signed-off-by: Vincent Biret --- .../Reader/V2/OpenApiSchemaDeserializer.cs | 4 ++ .../Reader/V3/OpenApiSchemaDeserializer.cs | 4 ++ .../Models/OpenApiSchemaTests.cs | 70 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs index af7f33d9c..25d36eff1 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -267,6 +267,10 @@ internal static partial class OpenApiV2Deserializer "example", (o, n, _, _) => o.Example = n }, + { + OpenApiConstants.JsonSchemaExamplesExtension, + (o, n, _, c) => o.Examples = n.CreateListOfAny(c) + }, { OpenApiConstants.PatternPropertiesExtension, (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 48dde9d5e..9b85fcf51 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -280,6 +280,10 @@ internal static partial class OpenApiV3Deserializer "example", (o, n, _, _) => o.Example = n }, + { + OpenApiConstants.JsonSchemaExamplesExtension, + (o, n, _, c) => o.Examples = n.CreateListOfAny(c) + }, { "deprecated", (o, n, _, _) => diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 6cb47b0d7..e566d5cb3 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1920,6 +1920,76 @@ public void DeserializePatternPropertiesExtensionInV3AssignsPatternPropertiesPro Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey("x-jsonschema-patternProperties")); } + [Fact] + public void DeserializeExamplesExtensionInV2AssignsExamplesProperty() + { + var jsonContent = """ + { + "swagger": "2.0", + "info": { "title": "Test", "version": "1.0" }, + "paths": {}, + "definitions": { + "TestSchema": { + "type": "string", + "example": "primary example", + "x-jsonschema-examples": [ + "secondary example", + 42 + ] + } + } + } + """; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schema = readResult.Document.Components.Schemas["TestSchema"]; + Assert.Equal("primary example", schema.Example?.GetValue()); + Assert.NotNull(schema.Examples); + Assert.Collection( + schema.Examples, + example => Assert.Equal("secondary example", example.GetValue()), + example => Assert.Equal(42, example.GetValue())); + Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension)); + } + + [Fact] + public void DeserializeExamplesExtensionInV3AssignsExamplesProperty() + { + var jsonContent = """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0" }, + "paths": {}, + "components": { + "schemas": { + "TestSchema": { + "type": "string", + "example": "primary example", + "x-jsonschema-examples": [ + "secondary example", + 42 + ] + } + } + } + } + """; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schema = readResult.Document.Components.Schemas["TestSchema"]; + Assert.Equal("primary example", schema.Example?.GetValue()); + Assert.NotNull(schema.Examples); + Assert.Collection( + schema.Examples, + example => Assert.Equal("secondary example", example.GetValue()), + example => Assert.Equal(42, example.GetValue())); + Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension)); + } + [Fact] public void DeserializeContainsExtensionsInV3AssignsContainsProperties() { From 5fefd0aab440192e9eb82b73f219510642eddd9e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Jul 2026 17:59:34 +0000 Subject: [PATCH 11/53] chore: promote shipped APIs --- src/Microsoft.OpenApi/PublicAPI.Shipped.txt | 2 ++ src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt index ceb7cd32a..ba435ff4e 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt @@ -2285,3 +2285,5 @@ override Microsoft.OpenApi.JsonSchemaReference.SerializeAsV32(Microsoft.OpenApi. override Microsoft.OpenApi.OpenApiSchemaReference.Target.get -> Microsoft.OpenApi.IOpenApiSchema? Microsoft.OpenApi.OpenApiWorkspace.GetDynamicAnchorCandidates(Microsoft.OpenApi.OpenApiDocument! hostDocument, string! anchorName) -> System.Collections.Generic.IReadOnlyList! static Microsoft.OpenApi.OpenApiWorkspace.ResolveDynamicAnchorInContext(Microsoft.OpenApi.IOpenApiSchema? contextSchema, string! anchorName) -> Microsoft.OpenApi.IOpenApiSchema? +const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! +const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 4699f67cc..7dc5c5811 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1,3 +1 @@ #nullable enable -const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! -const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! From 953c22aa3b0fba6fecc0e3df21186fb368632ba3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 12:28:28 -0400 Subject: [PATCH 12/53] fix: marks deprecated properties from the specification as obsolete chore: adds missing suppressions due to merge Signed-off-by: Vincent Biret --- .../Models/Interfaces/IOpenApiParameter.cs | 4 +++- src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs | 1 + src/Microsoft.OpenApi/Models/JsonSchemaReference.cs | 3 +++ src/Microsoft.OpenApi/Models/OpenApiParameter.cs | 2 ++ src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 8 ++++++++ src/Microsoft.OpenApi/Models/OpenApiXml.cs | 4 ++-- .../Models/References/OpenApiParameterReference.cs | 3 +++ .../Models/References/OpenApiSchemaReference.cs | 1 + .../Reader/V2/OpenApiParameterDeserializer.cs | 2 ++ .../Reader/V2/OpenApiSchemaDeserializer.cs | 2 ++ .../Reader/V3/OpenApiParameterDeserializer.cs | 2 ++ .../Reader/V3/OpenApiSchemaDeserializer.cs | 2 ++ .../Reader/V31/OpenApiParameterDeserializer.cs | 2 ++ .../Reader/V31/OpenApiSchemaDeserializer.cs | 2 ++ .../Reader/V32/OpenApiParameterDeserializer.cs | 2 ++ .../Reader/V32/OpenApiSchemaDeserializer.cs | 2 ++ .../Validations/Rules/OpenApiNonDefaultRules.cs | 1 + .../V2Tests/OpenApiSchemaTests.cs | 2 ++ .../V31Tests/OpenApiDocumentTests.cs | 2 ++ .../V31Tests/OpenApiSchemaTests.cs | 2 ++ .../V32Tests/OpenApiDocumentTests.cs | 2 ++ .../V32Tests/OpenApiSchemaTests.cs | 2 ++ .../V3Tests/OpenApiSchemaTests.cs | 2 ++ test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs | 6 ++++++ .../Validations/OpenApiSchemaValidationTests.cs | 2 ++ 25 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiParameter.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiParameter.cs index 5669a0bb3..586043d79 100644 --- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiParameter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text.Json.Nodes; namespace Microsoft.OpenApi; @@ -42,6 +43,7 @@ public interface IOpenApiParameter : IOpenApiDescribedElement, IOpenApiReadOnlyE /// If style is used, and if behavior is n/a (cannot be serialized), /// the value of allowEmptyValue SHALL be ignored. /// + [Obsolete("Use of AllowEmptyValue is not recommended and it is likely to be removed in a later revision.")] public bool AllowEmptyValue { get; } /// diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs index 7cb85f100..476b2a853 100644 --- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs @@ -243,6 +243,7 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte /// You must use the method to check whether Default was assigned a null value in the document. /// Assign to use get null as a serialized value. /// + [Obsolete("Use Examples instead.")] public JsonNode? Example { get; } /// diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 51fec64c6..c495ddbea 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -8,6 +8,8 @@ namespace Microsoft.OpenApi; +#pragma warning disable CS0618 + /// /// Schema reference information that includes metadata annotations from JSON Schema 2020-12. /// This class extends OpenApiReference to provide schema-specific metadata override capabilities. @@ -267,6 +269,7 @@ public string? Const /// A free-form property to include an example of an instance for this schema. /// Follow OpenAPI Schema Object definition. /// + [Obsolete("Use Examples instead.")] public JsonNode? Example { get; set; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index d6b060a78..24c8db173 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -8,6 +8,7 @@ namespace Microsoft.OpenApi { +#pragma warning disable CS0618 /// /// Parameter Object. /// @@ -32,6 +33,7 @@ public class OpenApiParameter : IOpenApiExtensible, IOpenApiParameter public bool Deprecated { get; set; } /// + [Obsolete("Use of AllowEmptyValue is not recommended and it is likely to be removed in a later revision.")] public bool AllowEmptyValue { get; set; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index ebaa49897..2aceda191 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -251,6 +251,7 @@ public string? Minimum public OpenApiDiscriminator? Discriminator { get; set; } /// + [Obsolete("Use Examples instead.")] public JsonNode? Example { get; set; } /// @@ -963,7 +964,10 @@ private void SerializeAsV2( writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w)); // example +#pragma warning disable CS0618 writer.WriteOptionalObject(OpenApiConstants.Example, GetCompatibilityExample(), (w, e) => w.WriteAny(e)); + writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); +#pragma warning restore CS0618 // x-nullable extension SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0); @@ -1032,7 +1036,9 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion private JsonNode? GetCompatibilityExample() { +#pragma warning disable CS0618 // Type or member is obsolete return Example ?? Examples?.FirstOrDefault(); +#pragma warning restore CS0618 // Type or member is obsolete } private IEnumerable? GetCompatibilityExamplesExtension() @@ -1042,10 +1048,12 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion return null; } +#pragma warning disable CS0618 // Type or member is obsolete if (Example is not null) { return Examples; } +#pragma warning restore CS0618 // Type or member is obsolete return Examples.Count > 1 ? Examples.Skip(1) : null; } diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index 82d7c2180..6e4e88962 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -30,7 +30,7 @@ public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible /// Declares whether the property definition translates to an attribute instead of an element. /// Default value is false. /// - [Obsolete("Use NodeType property instead. This property will be removed in a future version.")] + [Obsolete("Use NodeType set to OpenApiXmlNodeType.Attribute instead. This property will be removed in a future version.")] internal bool Attribute { get @@ -47,7 +47,7 @@ internal bool Attribute /// Signifies whether the array is wrapped. /// Default value is false. /// - [Obsolete("Use NodeType property instead. This property will be removed in a future version.")] + [Obsolete("Use NodeType set to OpenApiXmlNodeType.Element instead. This property will be removed in a future version.")] internal bool Wrapped { get diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs index 3c9670ac5..a4e3ea4de 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Text.Json.Nodes; namespace Microsoft.OpenApi { +#pragma warning disable CS0618 /// /// Parameter Object Reference. /// @@ -50,6 +52,7 @@ public string? Description public bool Deprecated { get => Target?.Deprecated ?? default; } /// + [Obsolete("Use of AllowEmptyValue is not recommended and it is likely to be removed in a later revision.")] public bool AllowEmptyValue { get => Target?.AllowEmptyValue ?? default; } /// diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index 6a9455775..532060345 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -167,6 +167,7 @@ public bool WriteOnly /// public OpenApiDiscriminator? Discriminator { get => Reference.Discriminator ?? Target?.Discriminator; set => Reference.Discriminator = value; } /// + [Obsolete("Use Examples instead.")] public JsonNode? Example { get => Reference.Example ?? Target?.Example; set => Reference.Example = value; } /// public IList? Examples diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs index e020aedc1..5fb2ec88a 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Globalization; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V2 { /// diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs index 25d36eff1..9e181fdcd 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -8,6 +8,8 @@ using System.Text.Json; using System.Text.Json.Nodes; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V2 { /// diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs index 1f589e2ff..435515658 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs @@ -5,6 +5,8 @@ using System; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V3 { /// diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 9b85fcf51..55293c7c5 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -9,6 +9,8 @@ using System.Text.Json; using System.Text.Json.Nodes; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V3 { /// diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs index 2755e5324..177342fb4 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs @@ -1,6 +1,8 @@ using System; using System.Text.Json.Nodes; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V31 { /// diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index d09df2101..52ca0912b 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -8,6 +8,8 @@ using System.Text.Json; using System.Text.Json.Nodes; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V31; internal static partial class OpenApiV31Deserializer diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs index 5eadec1c9..4a8300766 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs @@ -1,6 +1,8 @@ using System; using System.Text.Json.Nodes; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V32 { /// diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index c7f1d50b2..b038fa6e5 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -8,6 +8,8 @@ using System.Text.Json; using System.Text.Json.Nodes; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Reader.V32; internal static partial class OpenApiV32Deserializer diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs index 2bc019667..b5a398cb1 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs @@ -7,6 +7,7 @@ namespace Microsoft.OpenApi { +#pragma warning disable CS0618 /// /// Defines a non-default set of rules for validating examples in header, media type and parameter objects against the schema /// diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs index 5822d2239..5afea3cb4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs @@ -12,6 +12,8 @@ using Microsoft.OpenApi.Tests; using Xunit; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Readers.Tests.V2Tests { [Collection("DefaultSettings")] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index f764f7e60..d5f3565c8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -10,6 +10,8 @@ using System; using System.Net.Http; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Readers.Tests.V31Tests { public class OpenApiDocumentTests diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index 8f3314db2..fe4c74ff5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -14,6 +14,8 @@ using Microsoft.OpenApi.Tests; using Xunit; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Readers.Tests.V31Tests { public class OpenApiSchemaTests diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs index 79f2d990f..1c374a8d1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs @@ -10,6 +10,8 @@ using System; using System.Net.Http; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Readers.Tests.V32Tests { public class OpenApiDocumentTests diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs index ff1619802..166d15978 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -13,6 +13,8 @@ using Xunit; using System; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Readers.Tests.V32Tests { public class OpenApiSchemaTests diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index cda5c4c72..fab39f674 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -15,6 +15,8 @@ using SharpYaml.Serialization; using Xunit; +#pragma warning disable CS0618 + namespace Microsoft.OpenApi.Readers.Tests.V3Tests { [Collection("DefaultSettings")] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index e566d5cb3..db4de6355 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -623,6 +623,7 @@ public static TheoryData SchemaExamples() [Theory] [MemberData(nameof(SchemaExamples))] +#pragma warning disable CS0618 public void CloningSchemaExamplesWorks(JsonNode example) { // Arrange @@ -639,6 +640,7 @@ public void CloningSchemaExamplesWorks(JsonNode example) .IgnoringCyclicReferences() .Excluding(x => x.Options)); } +#pragma warning restore CS0618 [Fact] public void CloningSchemaExtensionsWorks() @@ -1945,7 +1947,9 @@ public void DeserializeExamplesExtensionInV2AssignsExamplesProperty() Assert.Empty(readResult.Diagnostic.Errors); var schema = readResult.Document.Components.Schemas["TestSchema"]; +#pragma warning disable CS0618 // Type or member is obsolete Assert.Equal("primary example", schema.Example?.GetValue()); +#pragma warning restore CS0618 // Type or member is obsolete Assert.NotNull(schema.Examples); Assert.Collection( schema.Examples, @@ -1981,7 +1985,9 @@ public void DeserializeExamplesExtensionInV3AssignsExamplesProperty() Assert.Empty(readResult.Diagnostic.Errors); var schema = readResult.Document.Components.Schemas["TestSchema"]; +#pragma warning disable CS0618 // Type or member is obsolete Assert.Equal("primary example", schema.Example?.GetValue()); +#pragma warning restore CS0618 // Type or member is obsolete Assert.NotNull(schema.Examples); Assert.Collection( schema.Examples, diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 0cb229534..8a5751666 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -36,6 +36,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() } [Fact] +#pragma warning disable CS0618 public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange @@ -58,6 +59,7 @@ public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchem // Assert Assert.True(result); } +#pragma warning restore CS0618 [Fact] public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() From 8f4a60bd8954964544e89a4a34f1d349e51afcb9 Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Tue, 21 Jul 2026 02:32:08 -0700 Subject: [PATCH 13/53] chore(deps): Fix System.Security.Cryptography.Xml vulnerabilities (#2983) Bump System.Security.Cryptography.Xml from 10.0.5 to 10.0.6 to address CVE-2026-33116 and CVE-2026-26171. Copilot-Session: b925a358-5d34-4537-8327-fe1fa580d903 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.OpenApi.Workbench.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj index 89cf78d37..f44ca0e8c 100644 --- a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj +++ b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj @@ -26,6 +26,7 @@ all + From 2c753650f439a5dea0e826f8cb6eeab43dfa09a0 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 22 Jul 2026 12:40:22 -0400 Subject: [PATCH 14/53] ci(dependabot): add cooldown default delay --- .github/dependabot.yml | 72 +++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 49c969ced..db2d8ebfb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,35 +1,41 @@ version: 2 updates: - - package-ecosystem: "github-actions" - # default location of `.github/workflows` - directory: "/" - open-pull-requests-limit: 10 - schedule: - interval: "daily" - - package-ecosystem: "nuget" - # location of package manifests - directory: "/" - open-pull-requests-limit: 10 - schedule: - interval: "daily" - groups: - MicrosoftExtensions: - patterns: - - "Microsoft.Extensions.*" - coverlet: - patterns: - - "coverlet.*" - - package-ecosystem: dotnet-sdk - directory: / - schedule: - interval: "daily" - ignore: - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - package-ecosystem: "devcontainers" - directory: "/" - open-pull-requests-limit: 10 - schedule: - interval: "daily" +- package-ecosystem: github-actions + directory: / + open-pull-requests-limit: 10 + schedule: + interval: daily + cooldown: + default-days: 7 +- package-ecosystem: nuget + directory: / + open-pull-requests-limit: 10 + schedule: + interval: daily + groups: + MicrosoftExtensions: + patterns: + - Microsoft.Extensions.* + coverlet: + patterns: + - coverlet.* + cooldown: + default-days: 7 +- package-ecosystem: dotnet-sdk + directory: / + schedule: + interval: daily + ignore: + - dependency-name: '*' + update-types: + - version-update:semver-major + - version-update:semver-minor + cooldown: + default-days: 7 +- package-ecosystem: devcontainers + directory: / + open-pull-requests-limit: 10 + schedule: + interval: daily + cooldown: + default-days: 7 From 4905fc82a5e4cb8bf9fb977c52774cf0b42b6801 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:54:43 +0000 Subject: [PATCH 15/53] chore(deps): bump github/codeql-action from 4 to 4.37.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4...v4.37.0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index cf00a08d9..847838ec6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Initialize CodeQL id: init_codeql - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.0 with: queries: security-and-quality @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis id: analyze_codeql - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.37.0 # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file From 39f510f4a2ad939503b94940e067f46c81df2cf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:54:50 +0000 Subject: [PATCH 16/53] chore(deps): bump actions/upload-code-coverage from 1 to 1.3.0 Bumps [actions/upload-code-coverage](https://github.com/actions/upload-code-coverage) from 1 to 1.3.0. - [Commits](https://github.com/actions/upload-code-coverage/compare/v1...v1.3.0) --- updated-dependencies: - dependency-name: actions/upload-code-coverage dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 4230531b0..a86906cca 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -64,7 +64,7 @@ jobs: - name: Upload coverage report if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') || (github.event_name != 'pull_request' && github.ref_name == github.event.repository.default_branch) - uses: actions/upload-code-coverage@v1 + uses: actions/upload-code-coverage@v1.3.0 with: file: ./reports/coverage/Cobertura.xml language: CSharp From 76ee72e7ef66c8e8ecfb8da84b58572f42919d4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:22:31 +0000 Subject: [PATCH 17/53] chore(deps): bump actions/upload-code-coverage from 1.3.0 to 1.4.0 Bumps [actions/upload-code-coverage](https://github.com/actions/upload-code-coverage) from 1.3.0 to 1.4.0. - [Commits](https://github.com/actions/upload-code-coverage/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: actions/upload-code-coverage dependency-version: 1.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a86906cca..f62ca9563 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -64,7 +64,7 @@ jobs: - name: Upload coverage report if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') || (github.event_name != 'pull_request' && github.ref_name == github.event.repository.default_branch) - uses: actions/upload-code-coverage@v1.3.0 + uses: actions/upload-code-coverage@v1.4.0 with: file: ./reports/coverage/Cobertura.xml language: CSharp From de388c92d0b17518dd9f64df1bfb852781a836e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:23:51 +0000 Subject: [PATCH 18/53] chore(deps): bump github/codeql-action from 4.37.0 to 4.37.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.37.0...v4.37.1) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 847838ec6..72e2480b5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Initialize CodeQL id: init_codeql - uses: github/codeql-action/init@v4.37.0 + uses: github/codeql-action/init@v4.37.1 with: queries: security-and-quality @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis id: analyze_codeql - uses: github/codeql-action/analyze@v4.37.0 + uses: github/codeql-action/analyze@v4.37.1 # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file From f64aff7cd54bf815865d267764cd4b04f7802466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:22:37 +0000 Subject: [PATCH 19/53] chore(deps): bump actions/upload-code-coverage from 1.4.0 to 1.4.1 Bumps [actions/upload-code-coverage](https://github.com/actions/upload-code-coverage) from 1.4.0 to 1.4.1. - [Commits](https://github.com/actions/upload-code-coverage/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: actions/upload-code-coverage dependency-version: 1.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f62ca9563..d274b00c2 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -64,7 +64,7 @@ jobs: - name: Upload coverage report if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') || (github.event_name != 'pull_request' && github.ref_name == github.event.repository.default_branch) - uses: actions/upload-code-coverage@v1.4.0 + uses: actions/upload-code-coverage@v1.4.1 with: file: ./reports/coverage/Cobertura.xml language: CSharp From a3104da78da2081ea0e528e60c849a561e19b3ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:23:47 +0000 Subject: [PATCH 20/53] chore(deps): bump github/codeql-action from 4.37.1 to 4.37.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.1 to 4.37.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.37.1...v4.37.2) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 72e2480b5..641f172db 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Initialize CodeQL id: init_codeql - uses: github/codeql-action/init@v4.37.1 + uses: github/codeql-action/init@v4.37.2 with: queries: security-and-quality @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis id: analyze_codeql - uses: github/codeql-action/analyze@v4.37.1 + uses: github/codeql-action/analyze@v4.37.2 # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file From 13960d90a9803c0da6aa41089a4588a5c31b45cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 21:23:32 +0000 Subject: [PATCH 21/53] chore(deps): bump github/codeql-action from 4.37.2 to 4.37.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.2 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.37.2...v4.37.3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 641f172db..9a7add063 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Initialize CodeQL id: init_codeql - uses: github/codeql-action/init@v4.37.2 + uses: github/codeql-action/init@v4.37.3 with: queries: security-and-quality @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis id: analyze_codeql - uses: github/codeql-action/analyze@v4.37.2 + uses: github/codeql-action/analyze@v4.37.3 # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file From f9a0fb1e4859969777c2bbea7a93459e9c976e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:23:43 +0000 Subject: [PATCH 22/53] chore(deps): bump actions/setup-java from 5 to 5.6.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 5.6.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5...v5.6.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e315778e8..7c4c1f9e4 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -35,7 +35,7 @@ jobs: runs-on: windows-latest steps: - name: Set up JDK 17 - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.6.0 with: distribution: 'adopt' java-version: 17 From 655c2c843732faba3687f8fd64568ba509b0065a Mon Sep 17 00:00:00 2001 From: Youssef Fahmy Date: Mon, 3 Aug 2026 21:07:22 +0200 Subject: [PATCH 23/53] fix: better nullability round-tripping fixes a bug in deserialization where a null in enum array would be dropped emits "nullable": true when we detect a null type, even if we haven't serialized a type (i.e, we allow emitting a redundant nullable property) detects the pattern of `enum: [ null ]`, and deserialize it `JsonSchemaType.Null`. This makes round-tripping behavior better. --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 23 +- .../Reader/JsonNodeHelper.cs | 8 +- .../Reader/V3/OpenApiSchemaDeserializer.cs | 7 + .../Models/OpenApiSchemaTests.cs | 24 +- .../OpenApiSchemaV30CompatibilityTests.cs | 236 ++++++++++++++++++ 5 files changed, 277 insertions(+), 21 deletions(-) create mode 100644 test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 2aceda191..2a6c8cb2f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -550,7 +550,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version } // type - var serializedTypeProperty = TrySerializeTypeProperty(writer, version); + SerializeTypeProperty(writer, version); // allOf writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback); @@ -595,13 +595,13 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); // nullable - if (version == OpenApiSpecVersion.OpenApi3_0 && serializedTypeProperty) + if (version == OpenApiSpecVersion.OpenApi3_0) { // https://spec.openapis.org/oas/v3.0.4.html#fixed-fields-20 // This keyword only takes effect if type is explicitly defined within the same Schema Object. // - // If the user explicitly set IsNullable to true, we serialize it even if redundant. - // But if **we** are inferring it (from oneOf/anyOf), we don't serialize it when it's redundant. + // We don't care to avoid an unnecessary serialization. + // So, we attempt to serialize it regardless of whether or not a type property was serialized. SerializeNullable(writer, version); } @@ -838,7 +838,7 @@ private void SerializeAsV2( writer.WriteStartObject(); // type - TrySerializeTypeProperty(writer, OpenApiSpecVersion.OpenApi2_0); + SerializeTypeProperty(writer, OpenApiSpecVersion.OpenApi2_0); // description writer.WriteProperty(OpenApiConstants.Description, Description); @@ -1006,14 +1006,13 @@ private void SerializeAsV2( writer.WriteEndObject(); } - private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version, JsonSchemaType? inferredType = null) + private void SerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version) { - // Use original type or inferred type when the explicit type is not set - var typeToUse = Type ?? inferredType; + var typeToUse = Type; if (typeToUse is null) { - return false; + return; } switch (version) @@ -1023,15 +1022,15 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion if (typeWithoutNull != 0 && !HasMultipleTypes(typeWithoutNull)) { writer.WriteProperty(OpenApiConstants.Type, typeWithoutNull.ToFirstIdentifier()); - return true; + return; } break; default: WriteUnifiedSchemaType(typeToUse.Value, writer); - return true; + return; } - return false; + return; } private JsonNode? GetCompatibilityExample() diff --git a/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs b/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs index 684e72c3b..db285f078 100644 --- a/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs +++ b/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs @@ -42,7 +42,13 @@ public static List CreateListOfAny(this JsonNode? node, ParsingContext throw new OpenApiReaderException("Cannot create a list from this type of node.", context); } - return jsonArray.OfType().ToList(); + var list = new List(jsonArray.Count); + foreach (var element in jsonArray) + { + list.Add(element ?? JsonNullSentinel.JsonNull); + } + + return list; } public static List CreateSimpleList(this JsonNode? node, Func map, OpenApiDocument? openApiDocument, ParsingContext context) diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 55293c7c5..bf0b7bca4 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -413,6 +413,13 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum } } + if (schema.Type is null && schema.Enum is { Count: 1 } && + schema.Enum[0].IsJsonNullSentinel()) + { + schema.Enum = null; + schema.Type = JsonSchemaType.Null; + } + return schema; } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index db4de6355..424a2c22d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -965,7 +965,8 @@ public async Task SerializeOneOfWithNullAsV3ShouldUseNullableAsync() { "enum": [ null - ] + ], + "nullable": true }, { "maxLength": 10, @@ -1009,7 +1010,8 @@ public async Task SerializeOneOfWithNullAndMultipleSchemasAsV3ShouldMarkItAsNull { "enum": [ null - ] + ], + "nullable": true }, { "type": "string" @@ -1061,7 +1063,8 @@ public async Task SerializeAnyOfWithNullAsV3ShouldUseNullableAsync() { "enum": [ null - ] + ], + "nullable": true }, { "type": "object", @@ -1108,7 +1111,8 @@ public async Task SerializeAnyOfWithNullAndMultipleSchemasAsV3ShouldApplyNullabl { "enum": [ null - ] + ], + "nullable": true }, { "minLength": 1, @@ -1153,7 +1157,8 @@ public async Task SerializeOneOfWithOnlyNullAsV3ShouldJustBeNullableAsync() { "enum": [ null - ] + ], + "nullable": true } ] } @@ -1256,7 +1261,8 @@ public async Task SerializeOneOfWithNullAndRefAsV3ShouldUseNullableAsync() { "enum": [ null - ] + ], + "nullable": true }, { "$ref": "#/components/schemas/Pet" @@ -2050,7 +2056,8 @@ public async Task SerializeNullableEnumWith3_0() { "enum": [ null - ] + ], + "nullable": true }, { "enum": [ @@ -2099,7 +2106,8 @@ public async Task SerializeNullableTypeWith3_0() { "enum": [ null - ] + ], + "nullable": true } """; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs new file mode 100644 index 000000000..4dc0e025b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models +{ + /// + /// Tests for the transformations applied when an that uses + /// JSON Schema DRAFT 2020-12 / OpenAPI 3.2 features is serialized down to OpenAPI 3.0 (which lacks those features), + /// and the corresponding behavior when such documents are deserialized back. + /// + [Collection("DefaultSettings")] + public class OpenApiSchemaV30CompatibilityTests + { + private static IOpenApiSchema ParseSchemaFromV30Document(string schemaJson) + { + var jsonContent = $$""" + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0" }, + "paths": {}, + "components": { + "schemas": { + "TestSchema": {{schemaJson}} + } + } + } + """; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + Assert.Empty(readResult.Diagnostic.Errors); + return readResult.Document.Components.Schemas["TestSchema"]; + } + + [Fact] + public async Task NullableEnumShouldRoundTripCorrectly() + { + var schema = new OpenApiSchema + { + Enum = + [ + JsonValue.Create(1), + JsonValue.Create(2), + JsonValue.Create(3), + JsonNullSentinel.JsonNull, + ] + }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = + """ + { + "enum": [ + 1, + 2, + 3, + null + ] + } + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = ParseSchemaFromV30Document(actual); + + Assert.Equal(4, deserializedSchema.Enum.Count); + Assert.Equal(1, deserializedSchema.Enum[0].GetValue()); + Assert.Equal(2, deserializedSchema.Enum[1].GetValue()); + Assert.Equal(3, deserializedSchema.Enum[2].GetValue()); + Assert.True(deserializedSchema.Enum[3].IsJsonNullSentinel()); + } + + [Fact] + public async Task TypeNullAloneAsV3ShouldRoundTripCorrectly() + { + var schema = new OpenApiSchema { Type = JsonSchemaType.Null }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = + """ + { + "enum": [ + null + ], + "nullable": true + } + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = ParseSchemaFromV30Document(actual); + + Assert.Equal(JsonSchemaType.Null, deserializedSchema.Type); + Assert.Null(deserializedSchema.Enum); + } + + [Fact] + public async Task NullableTypeAsV3ShouldRoundTripCorrectly() + { + var schema = new OpenApiSchema { Type = JsonSchemaType.String | JsonSchemaType.Null }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = + """ + { + "type": "string", + "nullable": true + } + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = ParseSchemaFromV30Document(actual); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, deserializedSchema.Type); + } + + [Fact] + public async Task SerializeMultipleNonNullTypesAsV3OmitsType() + { + // Current behavior isn't good. It loses the information about multiple types. + var schema = new OpenApiSchema { Type = JsonSchemaType.String | JsonSchemaType.Integer }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = """ + { + } + """; + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = ParseSchemaFromV30Document(actual); + Assert.Null(deserializedSchema.Type); + } + + [Fact] + public async Task SerializeMultipleNonNullTypesWithNullAsV3OmitsTypeButKeepsNullable() + { + // Current behavior isn't good. It loses the information about multiple types. + var schema = new OpenApiSchema + { + Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null + }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = """ + { + "nullable": true + } + """; + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = ParseSchemaFromV30Document(actual); + Assert.Null(deserializedSchema.Type); + } + + [Fact] + public async Task SerializeConstAsV3EmitsSingleValueEnum() + { + var schema = new OpenApiSchema { Type = JsonSchemaType.String, Const = "foo" }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var node = JsonNode.Parse(actual)!.AsObject(); + Assert.False(node.ContainsKey("const")); + Assert.True(node["enum"] is JsonArray enumArray + && enumArray.Count == 1 + && enumArray[0]!.ToString() == "foo"); + + var deserializedSchema = ParseSchemaFromV30Document(actual); + Assert.Null(deserializedSchema.Const); + Assert.NotNull(deserializedSchema.Enum); + Assert.Single(deserializedSchema.Enum); + Assert.Equal("foo", deserializedSchema.Enum[0]!.ToString()); + } + + [Fact] + public async Task SerializeExclusiveMaximumAsV3EmitsMaximumWithBooleanFlag() + { + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Integer, + ExclusiveMaximum = "5" + }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = + """ + { + "type": "integer", + "maximum": 5, + "exclusiveMaximum": true + } + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = (OpenApiSchema)ParseSchemaFromV30Document(actual); + Assert.True(deserializedSchema.IsExclusiveMaximum); + Assert.Null(deserializedSchema.Maximum); + Assert.Equal("5", deserializedSchema.ExclusiveMaximum); + } + + [Fact] + public async Task SerializeExclusiveMinimumAsV3EmitsMinimumWithBooleanFlag() + { + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Integer, + ExclusiveMinimum = "1" + }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + var expected = + """ + { + "type": "integer", + "minimum": 1, + "exclusiveMinimum": true + } + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + + var deserializedSchema = (OpenApiSchema)ParseSchemaFromV30Document(actual); + Assert.True(deserializedSchema.IsExclusiveMinimum); + Assert.Null(deserializedSchema.Minimum); + Assert.Equal("1", deserializedSchema.ExclusiveMinimum); + } + } +} From 28e43a803181fe2b58ea12f14fc14a9ac6b0b13f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 23:33:59 +0000 Subject: [PATCH 24/53] chore(deps): bump github/codeql-action from 4.37.3 to 4.37.4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.3 to 4.37.4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.37.3...v4.37.4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9a7add063..cc58a043c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Initialize CodeQL id: init_codeql - uses: github/codeql-action/init@v4.37.3 + uses: github/codeql-action/init@v4.37.4 with: queries: security-and-quality @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis id: analyze_codeql - uses: github/codeql-action/analyze@v4.37.3 + uses: github/codeql-action/analyze@v4.37.4 # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file From ebaf27ac46f8820e7b817cc33e693632c2fc908a Mon Sep 17 00:00:00 2001 From: Youssef Fahmy Date: Fri, 7 Aug 2026 19:03:05 +0200 Subject: [PATCH 25/53] feat: do not ignore multiple types when serializing to 3.0 (#2960) Ensures multiple types are not ignored when serializing to 3.0 Round-tripping work as intended updates benchmark report artifacts ensures that types with constraints are preserved Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .../performance.Descriptions-report-github.md | 24 +- .../performance.Descriptions-report.csv | 12 +- .../performance.Descriptions-report.html | 22 +- .../performance.Descriptions-report.json | 2 +- .../performance.EmptyModels-report-github.md | 68 +- .../performance.EmptyModels-report.csv | 56 +- .../performance.EmptyModels-report.html | 68 +- .../performance.EmptyModels-report.json | 2 +- .../Extensions/OpenApiTypeMapper.cs | 7 +- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 130 ++- .../Reader/V3/OpenApiSchemaDeserializer.cs | 106 +++ .../Models/OpenApiSchemaTests.cs | 749 ++++++++++++++++++ .../OpenApiSchemaV30CompatibilityTests.cs | 69 +- 13 files changed, 1142 insertions(+), 173 deletions(-) diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md index 2abba8670..6ee697332 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md @@ -1,20 +1,20 @@ ``` BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat) -AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores -.NET SDK 10.0.301 - [Host] : .NET 8.0.28 (8.0.28, 8.0.2826.26413), X64 RyuJIT x86-64-v3 - ShortRun : .NET 8.0.28 (8.0.28, 8.0.2826.26413), X64 RyuJIT x86-64-v3 +INTEL XEON PLATINUM 8573C 2.30GHz, 1 CPU, 4 logical and 2 physical cores +.NET SDK 10.0.302 + [Host] : .NET 8.0.29 (8.0.29, 8.0.2926.32403), X64 RyuJIT x86-64-v4 + ShortRun : .NET 8.0.29 (8.0.29, 8.0.2926.32403), X64 RyuJIT x86-64-v4 Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------- |---------------:|--------------:|-------------:|-----------:|-----------:|----------:|-------------:| -| PetStoreYaml | 582.2 μs | 208.61 μs | 11.43 μs | 19.5313 | 3.9063 | - | 375.68 KB | -| PetStoreJson | 252.3 μs | 29.46 μs | 1.61 μs | 11.7188 | 1.9531 | - | 209.67 KB | -| GHESYaml | 1,005,560.1 μs | 211,112.24 μs | 11,571.77 μs | 22000.0000 | 18000.0000 | 3000.0000 | 311035.48 KB | -| GHESJson | 369,047.2 μs | 94,799.88 μs | 5,196.30 μs | 9000.0000 | 8000.0000 | 2000.0000 | 140644.21 KB | -| GHESNextYaml | 1,241,432.6 μs | 106,390.00 μs | 5,831.60 μs | 34000.0000 | 19000.0000 | 3000.0000 | 513301.23 KB | -| GHESNextJson | 626,499.0 μs | 70,802.74 μs | 3,880.94 μs | 22000.0000 | 10000.0000 | 2000.0000 | 345119.94 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------- |---------------:|--------------:|-------------:|----------:|----------:|----------:|-------------:| +| PetStoreYaml | 477.6 μs | 237.53 μs | 13.02 μs | 3.9063 | - | - | 375.67 KB | +| PetStoreJson | 193.3 μs | 29.03 μs | 1.59 μs | 1.9531 | - | - | 209.67 KB | +| GHESYaml | 878,325.2 μs | 555,688.50 μs | 30,459.16 μs | 4000.0000 | 3000.0000 | 1000.0000 | 310814.2 KB | +| GHESJson | 225,445.4 μs | 35,058.33 μs | 1,921.67 μs | 1000.0000 | - | - | 140426.65 KB | +| GHESNextYaml | 1,254,063.7 μs | 245,210.30 μs | 13,440.80 μs | 8000.0000 | 6000.0000 | 2000.0000 | 512928.9 KB | +| GHESNextJson | 588,121.5 μs | 83,680.85 μs | 4,586.83 μs | 5000.0000 | 4000.0000 | 1000.0000 | 344754.7 KB | diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv index 316be68f2..c5aeb2762 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv @@ -1,7 +1,7 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Gen0,Gen1,Gen2,Allocated -PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,582.2 μs,208.61 μs,11.43 μs,19.5313,3.9063,0.0000,375.68 KB -PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,252.3 μs,29.46 μs,1.61 μs,11.7188,1.9531,0.0000,209.67 KB -GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,005,560.1 μs","211,112.24 μs","11,571.77 μs",22000.0000,18000.0000,3000.0000,311035.48 KB -GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"369,047.2 μs","94,799.88 μs","5,196.30 μs",9000.0000,8000.0000,2000.0000,140644.21 KB -GHESNextYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,241,432.6 μs","106,390.00 μs","5,831.60 μs",34000.0000,19000.0000,3000.0000,513301.23 KB -GHESNextJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"626,499.0 μs","70,802.74 μs","3,880.94 μs",22000.0000,10000.0000,2000.0000,345119.94 KB +PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,477.6 μs,237.53 μs,13.02 μs,3.9063,0.0000,0.0000,375.67 KB +PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,193.3 μs,29.03 μs,1.59 μs,1.9531,0.0000,0.0000,209.67 KB +GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"878,325.2 μs","555,688.50 μs","30,459.16 μs",4000.0000,3000.0000,1000.0000,310814.2 KB +GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"225,445.4 μs","35,058.33 μs","1,921.67 μs",1000.0000,0.0000,0.0000,140426.65 KB +GHESNextYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,254,063.7 μs","245,210.30 μs","13,440.80 μs",8000.0000,6000.0000,2000.0000,512928.9 KB +GHESNextJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"588,121.5 μs","83,680.85 μs","4,586.83 μs",5000.0000,4000.0000,1000.0000,344754.7 KB diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html index 95b1dadc6..f015841f0 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html @@ -2,7 +2,7 @@ -performance.Descriptions-20260714-121715 +performance.Descriptions-20260807-144733