diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 9d55602b3..4d53ec759 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -20,7 +20,7 @@ jobs: - name: Checkout repository id: checkout_repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 @@ -41,7 +41,7 @@ jobs: name: Validate Project for Trimming runs-on: windows-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -58,7 +58,7 @@ jobs: needs: [ci] steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 72eea8bbf..30c8acef4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repository id: checkout_repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml index 9c76a0d64..c905a3f48 100644 --- a/.github/workflows/release-please-gha.yml +++ b/.github/workflows/release-please-gha.yml @@ -16,6 +16,7 @@ on: - main - support/v1 - support/v2 + workflow_dispatch: permissions: contents: read @@ -24,7 +25,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Generate GitHub App token id: app-token diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 51f9b5986..3f969e825 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -43,7 +43,7 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: 8.0.x - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 56441f9d3..a1304a172 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.0.1" + ".": "3.0.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 98861fe62..83c66a66d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ # Changelog -## [3.0.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.0...v3.0.1) (2025-11-17) +## [3.0.2](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.1...v3.0.2) (2025-12-08) + + +### Bug Fixes + +* additional properties serialization should not emit a schema in v2 ([946cba9](https://github.com/microsoft/OpenAPI.NET/commit/946cba992a2733a60182453e38722b4ed789b729)) +* additional properties serialization should not emit booleans in v3.1+ ([946cba9](https://github.com/microsoft/OpenAPI.NET/commit/946cba992a2733a60182453e38722b4ed789b729)) +## [3.0.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.0...v3.0.1) (2025-11-17) ### Bug Fixes @@ -10,7 +17,6 @@ ## [3.0.0](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.9...v3.0.0) (2025-11-11) - ### ⚠ BREAKING CHANGES * adds support for OpenAPI 3.2.0 @@ -23,8 +29,12 @@ * adds support for OpenAPI 3.2.0 ([765a8dd](https://github.com/microsoft/OpenAPI.NET/commit/765a8dd4d6efd1a31b6a76d282ccffa5877a845a)) -## [2.3.9](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.8...v2.3.9) (2025-11-06) +## [2.3.10](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.9...v2.3.10) (2025-11-17) +* empty strings should be quoted in yaml ([e919b33](https://github.com/microsoft/OpenAPI.NET/commit/e919b33e9d09159217066248483ef4c767865c82)) +* empty strings should be quoted in yaml ([0ca10db](https://github.com/microsoft/OpenAPI.NET/commit/0ca10db3bb9ffa937dd35862068926f3586d6991)) + +## [2.3.9](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.8...v2.3.9) (2025-11-06) ### Bug Fixes diff --git a/Directory.Build.props b/Directory.Build.props index 476cfb435..8582f5826 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.0.1 + 3.0.2 diff --git a/performance/benchmark/PerformanceTests.csproj b/performance/benchmark/PerformanceTests.csproj index 53c0784d7..89b757f90 100644 --- a/performance/benchmark/PerformanceTests.csproj +++ b/performance/benchmark/PerformanceTests.csproj @@ -15,8 +15,8 @@ CA1822;CS9057; - - + + diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index c371e9786..1bb7639da 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -471,17 +471,20 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, callback); // additionalProperties - if (AdditionalPropertiesAllowed) + if (AdditionalProperties is not null && version >= OpenApiSpecVersion.OpenApi3_0) { writer.WriteOptionalObject( OpenApiConstants.AdditionalProperties, AdditionalProperties, callback); } - else + // true is the default in earlier versions 3, no need to write it out + // boolean value is only supported for version 3 and earlier (version 2 is implemented in the other serialize method, the condition is a failsafe) + else if (!AdditionalPropertiesAllowed && version <= OpenApiSpecVersion.OpenApi3_0) { writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); } + // not having anything is the same as having it set to true (v2/v3) or an empty schema (v3.1+) // description writer.WriteProperty(OpenApiConstants.Description, Description); @@ -732,14 +735,9 @@ private void SerializeAsV2( }); // additionalProperties - if (AdditionalPropertiesAllowed) - { - writer.WriteOptionalObject( - OpenApiConstants.AdditionalProperties, - AdditionalProperties, - (w, s) => s.SerializeAsV2(w)); - } - else + // a schema cannot be serialized in v2 + // true is the default, no need to write it out + if (!AdditionalPropertiesAllowed) { writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); } diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index aac7c907f..e0f481858 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 87dc85ced..04406a370 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -684,6 +684,114 @@ public async Task SerializeConstAsEnumV20() Assert.False(v2Node.AsObject().ContainsKey("const")); } + [Fact] + public async Task SerializeAdditionalPropertiesAsV2DoesNotEmit() + { + var expected = @"{ }"; + // Given + var schema = new OpenApiSchema + { + AdditionalProperties = new OpenApiSchema() + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeAdditionalPropertiesAllowedAsV2DefaultDoesNotEmit() + { + var expected = @"{ }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = true + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeAdditionalPropertiesAllowedAsV2FalseEmits() + { + var expected = @"{ ""additionalProperties"": false }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + public async Task SerializeAdditionalPropertiesAllowedAsV3PlusDefaultDoesNotEmit(OpenApiSpecVersion version) + { + var expected = @"{ }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = true + }; + + // When + var actual = await schema.SerializeAsJsonAsync(version); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeAdditionalPropertiesAllowedAsV3FalseEmits() + { + var expected = @"{ ""additionalProperties"": false }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + public async Task SerializeAdditionalPropertiesAsV3PlusEmits(OpenApiSpecVersion version) + { + var expected = @"{ ""additionalProperties"": { } }"; + // Given + var schema = new OpenApiSchema + { + AdditionalProperties = new OpenApiSchema() + }; + + // When + var actual = await schema.SerializeAsJsonAsync(version); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + internal class SchemaVisitor : OpenApiVisitorBase {