From b75709adc868fa11e869583ab469537ec2dc4f67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 10:09:07 +0000 Subject: [PATCH 1/7] Initial plan From 94b606dae1ab89d291133e986d57cf944f787945 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 10:16:07 +0000 Subject: [PATCH 2/7] fix(reader): preserve nullable Null flag when type appears after nullable in V3.1/V3.2 deserializers Agent-Logs-Url: https://github.com/microsoft/OpenAPI.NET/sessions/40596ef1-e9f2-4d1b-b9e2-0650b96d73b4 Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .../Reader/V31/OpenApiSchemaDeserializer.cs | 7 +++++-- .../Reader/V32/OpenApiSchemaDeserializer.cs | 7 +++++-- .../V31Tests/OpenApiSchemaTests.cs | 12 ++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 39c32c979..296626a93 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -198,14 +198,17 @@ internal static partial class OpenApiV31Deserializer "type", (o, n, doc) => { + // Preserve any Null flag set by a preceding "nullable: true" handler + var preserveNull = o.Type.HasValue && o.Type.Value.HasFlag(JsonSchemaType.Null); if (n is ValueNode) { - o.Type = n.GetScalarValue()?.ToJsonSchemaType(); + var parsedType = n.GetScalarValue()?.ToJsonSchemaType(); + o.Type = preserveNull ? parsedType | JsonSchemaType.Null : parsedType; } else { var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc); - JsonSchemaType combinedType = 0; + JsonSchemaType combinedType = preserveNull ? JsonSchemaType.Null : 0; foreach(var type in list.Where(static t => t is not null).Select(static t => t!.ToJsonSchemaType())) { combinedType |= type; diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index c18c48462..f0df47b99 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -198,14 +198,17 @@ internal static partial class OpenApiV32Deserializer "type", (o, n, doc) => { + // Preserve any Null flag set by a preceding "nullable: true" handler + var preserveNull = o.Type.HasValue && o.Type.Value.HasFlag(JsonSchemaType.Null); if (n is ValueNode) { - o.Type = n.GetScalarValue()?.ToJsonSchemaType(); + var parsedType = n.GetScalarValue()?.ToJsonSchemaType(); + o.Type = preserveNull ? parsedType | JsonSchemaType.Null : parsedType; } else { var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc); - JsonSchemaType combinedType = 0; + JsonSchemaType combinedType = preserveNull ? JsonSchemaType.Null : 0; foreach(var type in list.Where(static t => t is not null).Select(static t => t!.ToJsonSchemaType())) { combinedType |= type; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index b65b2d8c3..ec66dcbb9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -132,6 +132,18 @@ public void ParseSchemaWithTypeArrayWorks() Assert.Equivalent(expected, actual); } + [Theory] + [InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")] + [InlineData(@"{ ""type"": ""string"", ""nullable"": true }")] + public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson) + { + // Act + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_1, new(), out _, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + [Fact] public void TestSchemaCopyConstructorWithTypeArrayWorks() { From de72b1dd2ad152152bd36d6713fffd8b8670c1fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 12:21:07 +0000 Subject: [PATCH 3/7] test(reader): add nullable-before-type tests for V3.0 and V3.2 Agent-Logs-Url: https://github.com/microsoft/OpenAPI.NET/sessions/6de5cea9-ff8f-4a95-804d-493007f279f3 Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .../V32Tests/OpenApiSchemaTests.cs | 12 ++++++++++++ .../V3Tests/OpenApiSchemaTests.cs | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs index 7f91b0327..621cd156c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -131,6 +131,18 @@ public void ParseSchemaWithTypeArrayWorks() Assert.Equivalent(expected, actual); } + [Theory] + [InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")] + [InlineData(@"{ ""type"": ""string"", ""nullable"": true }")] + public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson) + { + // Act + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_2, new(), out _, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + [Fact] public void TestSchemaCopyConstructorWithTypeArrayWorks() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index e16e40594..8241e55ed 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -133,6 +133,18 @@ public void ParsePathFragmentShouldSucceed() }, openApiAny); } + [Theory] + [InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")] + [InlineData(@"{ ""type"": ""string"", ""nullable"": true }")] + public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson) + { + // Act + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_0, new(), out _, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + [Fact] public void ParseDictionarySchemaShouldSucceed() { From d298917ecb7828702dbc1f37a2291f279cb24920 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 01:29:37 +0000 Subject: [PATCH 4/7] Bump Microsoft.NET.Test.Sdk from 18.5.1 to 18.6.0 --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../Microsoft.OpenApi.Hidi.Tests.csproj | 2 +- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj index d0766cb56..8e30f7f56 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj +++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index d8ea9ff74..2c37b7724 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 427c84eb1..ba1164688 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -11,7 +11,7 @@ - + From 23683059064a527fe48db5697f61a2ec0527d97a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 28 May 2026 10:05:00 -0400 Subject: [PATCH 5/7] ci: integrate GitHub code coverage uploads Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b9d453d2a..2e00ba347 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -9,6 +9,10 @@ jobs: ci: name: Continuous Integration runs-on: ubuntu-latest + permissions: + contents: read + code-quality: write + pull-requests: read env: ARTIFACTS_FOLDER: ${{ github.workspace }}/Artifacts GITHUB_RUN_NUMBER: ${{ github.run_number }} @@ -27,6 +31,7 @@ jobs: id: checkout_repo uses: actions/checkout@v6 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 @@ -40,7 +45,36 @@ jobs: id: run_unit_tests shell: pwsh run: | - dotnet test Microsoft.OpenApi.slnx -c Release -v n + dotnet test Microsoft.OpenApi.slnx -c Release --no-build -v n --collect:"XPlat Code Coverage" + + - name: Install report generator + shell: pwsh + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool + + - name: Generate coverage report + shell: pwsh + run: | + reportgenerator -reports:**/coverage.cobertura.xml -targetdir:./reports/coverage -reporttypes:"Html;MarkdownSummaryGithub;Cobertura" + + - name: Add coverage to job summary + shell: bash + run: | + cat ./reports/coverage/SummaryGithub.md >> "$GITHUB_STEP_SUMMARY" + + - name: Upload coverage report + if: github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') + uses: actions/upload-code-coverage@v1 + with: + file: ./reports/coverage/Cobertura.xml + language: CSharp + label: code-coverage/dotnet + + - name: Upload coverage artifact + uses: actions/upload-artifact@v7 + with: + name: coverage + path: reports/coverage validate-trimming: name: Validate Project for Trimming From 92e95053e36cfc923f27594a47c018e0d524df66 Mon Sep 17 00:00:00 2001 From: "release-please-token-provider[bot]" <225477224+release-please-token-provider[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 16:33:34 +0000 Subject: [PATCH 6/7] chore(main): release 3.5.5 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ Directory.Build.props | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7cc8e7abd..1153ec074 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.5.4" + ".": "3.5.5" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 672d1121f..a72932424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.5.5](https://github.com/microsoft/OpenAPI.NET/compare/v3.5.4...v3.5.5) (2026-05-28) + + +### Bug Fixes + +* **reader:** preserve Null flag when nullable appears before type in V3.0/V3.1/V3.2 deserializers ([2b9d7f4](https://github.com/microsoft/OpenAPI.NET/commit/2b9d7f46bfcef3e2d5da3f73fd4bf83be677c79a)) + ## [3.5.4](https://github.com/microsoft/OpenAPI.NET/compare/v3.5.3...v3.5.4) (2026-05-26) diff --git a/Directory.Build.props b/Directory.Build.props index 46e756a9e..8339d1656 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ https://github.com/Microsoft/OpenAPI.NET © Microsoft Corporation. All rights reserved. OpenAPI .NET - 3.5.4 + 3.5.5 From 75f3144e4a15dc336616aaee94963e1d3d820e43 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 28 May 2026 12:46:50 -0400 Subject: [PATCH 7/7] ci: skip coverage uploads off default branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .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 2e00ba347..fb99868ee 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -63,7 +63,7 @@ jobs: cat ./reports/coverage/SummaryGithub.md >> "$GITHUB_STEP_SUMMARY" - name: Upload coverage report - if: github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') + 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 with: file: ./reports/coverage/Cobertura.xml