diff --git a/CHANGELOG.md b/CHANGELOG.md index 98861fe62..ec4f8b403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## [3.0.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.0...v3.0.1) (2025-11-17) - ### Bug Fixes * empty strings should be quoted in yaml ([8d215f9](https://github.com/microsoft/OpenAPI.NET/commit/8d215f9ea8a780b1e2e8dd6cefb8d470cc35682d)) @@ -10,7 +9,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 +21,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/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/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 {