From 65fe6440a0abd7d375033a81e7467f3821cafc07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:28:17 +0000 Subject: [PATCH 1/7] Initial plan From 46dbfbb760db01adf33e199a047f04402d4732cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:45:33 +0000 Subject: [PATCH 2/7] Add OpenApiXmlNodeType enum and update OpenApiXml for OAI 3.2.0 Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .../Models/OpenApiConstants.cs | 5 + src/Microsoft.OpenApi/Models/OpenApiXml.cs | 87 ++++++++++-- .../Models/OpenApiXmlNodeType.cs | 36 +++++ .../Reader/V2/OpenApiXmlDeserializer.cs | 4 + .../Reader/V3/OpenApiXmlDeserializer.cs | 4 + .../Reader/V31/OpenApiXmlDeserializer.cs | 4 + .../Reader/V32/OpenApiXmlDeserializer.cs | 23 +++ .../V3Tests/OpenApiXmlTests.cs | 2 + .../Models/OpenApiXmlTests.cs | 132 ++++++++++++++++++ .../PublicApi/PublicApi.approved.txt | 17 +++ 10 files changed, 305 insertions(+), 9 deletions(-) create mode 100644 src/Microsoft.OpenApi/Models/OpenApiXmlNodeType.cs diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index baf401b83..295a2bcf6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -245,6 +245,11 @@ public static class OpenApiConstants /// public const string Wrapped = "wrapped"; + /// + /// Field: NodeType + /// + public const string NodeType = "nodeType"; + /// /// Field: In /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index 62c1b64de..f875071c3 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -11,6 +11,10 @@ namespace Microsoft.OpenApi /// public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible { + private OpenApiXmlNodeType? _nodeType; + private bool _attribute; + private bool _wrapped; + /// /// Replaces the name of the element/attribute used for the described schema property. /// @@ -30,13 +34,56 @@ public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible /// Declares whether the property definition translates to an attribute instead of an element. /// Default value is false. /// - public bool Attribute { get; set; } + [Obsolete("Use NodeType property instead. This property will be removed in a future version.")] + public bool Attribute + { + get + { + // If NodeType is explicitly set, use it; otherwise use the backing field + if (_nodeType.HasValue) + { + return _nodeType == OpenApiXmlNodeType.Attribute; + } + return _attribute; + } + set + { + _attribute = value; + _nodeType = value ? OpenApiXmlNodeType.Attribute : OpenApiXmlNodeType.None; + } + } /// /// Signifies whether the array is wrapped. /// Default value is false. /// - public bool Wrapped { get; set; } + [Obsolete("Use NodeType property instead. This property will be removed in a future version.")] + public bool Wrapped + { + get + { + // If NodeType is explicitly set, use it; otherwise use the backing field + if (_nodeType.HasValue) + { + return _nodeType == OpenApiXmlNodeType.Element; + } + return _wrapped; + } + set + { + _wrapped = value; + _nodeType = value ? OpenApiXmlNodeType.Element : OpenApiXmlNodeType.None; + } + } + + /// + /// The node type of the XML representation. + /// + public OpenApiXmlNodeType? NodeType + { + get => _nodeType; + set => _nodeType = value; + } /// /// Specification Extensions. @@ -56,8 +103,9 @@ public OpenApiXml(OpenApiXml xml) Name = xml?.Name ?? Name; Namespace = xml?.Namespace ?? Namespace; Prefix = xml?.Prefix ?? Prefix; - Attribute = xml?.Attribute ?? Attribute; - Wrapped = xml?.Wrapped ?? Wrapped; + _nodeType = xml?._nodeType ?? _nodeType; + _attribute = xml?._attribute ?? _attribute; + _wrapped = xml?._wrapped ?? _wrapped; Extensions = xml?.Extensions != null ? new Dictionary(xml.Extensions) : null; } @@ -108,11 +156,32 @@ private void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) // prefix writer.WriteProperty(OpenApiConstants.Prefix, Prefix); - // attribute - writer.WriteProperty(OpenApiConstants.Attribute, Attribute, false); - - // wrapped - writer.WriteProperty(OpenApiConstants.Wrapped, Wrapped, false); + // For OpenAPI 3.2.0 and above, serialize nodeType + if (specVersion >= OpenApiSpecVersion.OpenApi3_2) + { + if (_nodeType.HasValue) + { + writer.WriteProperty(OpenApiConstants.NodeType, _nodeType.Value.GetDisplayName()); + } + } + else + { + // For OpenAPI 3.1.0 and below, serialize attribute and wrapped + // Use backing fields if they were set via obsolete properties, + // otherwise derive from NodeType if set + bool attribute = _attribute; + bool wrapped = _wrapped; + + if (_nodeType.HasValue && !_attribute && !_wrapped) + { + // NodeType was set directly, not via obsolete properties + attribute = _nodeType == OpenApiXmlNodeType.Attribute; + wrapped = _nodeType == OpenApiXmlNodeType.Element; + } + + writer.WriteProperty(OpenApiConstants.Attribute, attribute, false); + writer.WriteProperty(OpenApiConstants.Wrapped, wrapped, false); + } // extensions writer.WriteExtensions(Extensions, specVersion); diff --git a/src/Microsoft.OpenApi/Models/OpenApiXmlNodeType.cs b/src/Microsoft.OpenApi/Models/OpenApiXmlNodeType.cs new file mode 100644 index 000000000..11b8f85db --- /dev/null +++ b/src/Microsoft.OpenApi/Models/OpenApiXmlNodeType.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.OpenApi +{ + /// + /// The type of the XML node + /// + public enum OpenApiXmlNodeType + { + /// + /// Element node type + /// + [Display("element")] Element, + + /// + /// Attribute node type + /// + [Display("attribute")] Attribute, + + /// + /// Text node type + /// + [Display("text")] Text, + + /// + /// CDATA node type + /// + [Display("cdata")] Cdata, + + /// + /// None node type + /// + [Display("none")] None + } +} diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs index 2a8c395fe..c5034017d 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs @@ -42,7 +42,9 @@ internal static partial class OpenApiV2Deserializer var attribute = n.GetScalarValue(); if (attribute is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Attribute = bool.Parse(attribute); +#pragma warning restore CS0618 // Type or member is obsolete } } }, @@ -53,7 +55,9 @@ internal static partial class OpenApiV2Deserializer var wrapped = n.GetScalarValue(); if (wrapped is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Wrapped = bool.Parse(wrapped); +#pragma warning restore CS0618 // Type or member is obsolete } } }, diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs index 2a02d52bb..677cbfc0f 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs @@ -39,7 +39,9 @@ internal static partial class OpenApiV3Deserializer var attribute = n.GetScalarValue(); if (attribute is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Attribute = bool.Parse(attribute); +#pragma warning restore CS0618 // Type or member is obsolete } } }, @@ -50,7 +52,9 @@ internal static partial class OpenApiV3Deserializer var wrapped = n.GetScalarValue(); if (wrapped is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Wrapped = bool.Parse(wrapped); +#pragma warning restore CS0618 // Type or member is obsolete } } }, diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs index 77d1c0163..f43018714 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs @@ -41,7 +41,9 @@ internal static partial class OpenApiV31Deserializer var attribute = n.GetScalarValue(); if (attribute is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Attribute = bool.Parse(attribute); +#pragma warning restore CS0618 // Type or member is obsolete } } }, @@ -52,7 +54,9 @@ internal static partial class OpenApiV31Deserializer var wrapped = n.GetScalarValue(); if (wrapped is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Wrapped = bool.Parse(wrapped); +#pragma warning restore CS0618 // Type or member is obsolete } } } diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs index a0169629a..0bb2a63c8 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs @@ -41,7 +41,9 @@ internal static partial class OpenApiV32Deserializer var attribute = n.GetScalarValue(); if (attribute is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Attribute = bool.Parse(attribute); +#pragma warning restore CS0618 // Type or member is obsolete } } }, @@ -52,7 +54,28 @@ internal static partial class OpenApiV32Deserializer var wrapped = n.GetScalarValue(); if (wrapped is not null) { +#pragma warning disable CS0618 // Type or member is obsolete o.Wrapped = bool.Parse(wrapped); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + }, + { + "nodeType", + (o, n, _) => + { + var nodeType = n.GetScalarValue(); + if (nodeType is not null) + { + o.NodeType = nodeType.ToLowerInvariant() switch + { + "element" => OpenApiXmlNodeType.Element, + "attribute" => OpenApiXmlNodeType.Attribute, + "text" => OpenApiXmlNodeType.Text, + "cdata" => OpenApiXmlNodeType.Cdata, + "none" => OpenApiXmlNodeType.None, + _ => null + }; } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs index 259400c90..0c9c78e6b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs @@ -21,6 +21,7 @@ public async Task ParseBasicXmlShouldSucceed() var xml = await OpenApiModelFactory.LoadAsync(Resources.GetStream(Path.Combine(SampleFolderPath, "basicXml.yaml")), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); // Assert +#pragma warning disable CS0618 // Type or member is obsolete Assert.Equivalent( new OpenApiXml { @@ -29,6 +30,7 @@ public async Task ParseBasicXmlShouldSucceed() Prefix = "samplePrefix", Wrapped = true }, xml); +#pragma warning restore CS0618 // Type or member is obsolete } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index 5424e1eb9..cd1e446c2 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.OpenApi.Tests.Models [Collection("DefaultSettings")] public class OpenApiXmlTests { +#pragma warning disable CS0618 // Type or member is obsolete public static OpenApiXml AdvancedXml = new() { Name = "animal", @@ -22,9 +23,28 @@ public class OpenApiXmlTests {"x-xml-extension", new JsonNodeExtension(7)} } }; +#pragma warning restore CS0618 // Type or member is obsolete public static OpenApiXml BasicXml = new(); + public static OpenApiXml XmlWithNodeType = new() + { + Name = "pet", + Namespace = new("http://example.com/schema"), + Prefix = "ex", + NodeType = OpenApiXmlNodeType.Element, + Extensions = new Dictionary() + { + {"x-custom", new JsonNodeExtension("test")} + } + }; + + public static OpenApiXml XmlWithAttributeNodeType = new() + { + Name = "id", + NodeType = OpenApiXmlNodeType.Attribute + }; + [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Json)] [InlineData(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json)] @@ -93,5 +113,117 @@ public async Task SerializeAdvancedXmlAsYamlWorks(OpenApiSpecVersion version) expected = expected.MakeLineBreaksEnvironmentNeutral(); Assert.Equal(expected, actual); } + + [Fact] + public async Task SerializeXmlWithNodeTypeAsJsonV32Works() + { + // Arrange + var expected = + """ + { + "name": "pet", + "namespace": "http://example.com/schema", + "prefix": "ex", + "nodeType": "element", + "x-custom": "test" + } + """; + + // Act + var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task SerializeXmlWithNodeTypeAsYamlV32Works() + { + // Arrange + var expected = + """ + name: pet + namespace: http://example.com/schema + prefix: ex + nodeType: element + x-custom: test + """; + + // Act + var actual = await XmlWithNodeType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task SerializeXmlWithAttributeNodeTypeAsJsonV32Works() + { + // Arrange + var expected = + """ + { + "name": "id", + "nodeType": "attribute" + } + """; + + // Act + var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task SerializeXmlWithNodeTypeAsJsonV31DoesNotSerializeNodeType() + { + // Arrange - In v3.1, nodeType should not be serialized, instead attribute/wrapped should be + var expected = + """ + { + "name": "pet", + "namespace": "http://example.com/schema", + "prefix": "ex", + "wrapped": true, + "x-custom": "test" + } + """; + + // Act + var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task SerializeXmlWithAttributeNodeTypeAsJsonV31DoesNotSerializeNodeType() + { + // Arrange - In v3.1, nodeType should not be serialized, instead attribute/wrapped should be + var expected = + """ + { + "name": "id", + "attribute": true + } + """; + + // Act + var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } } } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index e88b96782..ebe72efff 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -507,6 +507,7 @@ namespace Microsoft.OpenApi public const string MultipleOf = "multipleOf"; public const string Name = "name"; public const string Namespace = "namespace"; + public const string NodeType = "nodeType"; public const string Not = "not"; public const string Null = "null"; public const string Nullable = "nullable"; @@ -1692,17 +1693,33 @@ namespace Microsoft.OpenApi { public OpenApiXml() { } public OpenApiXml(Microsoft.OpenApi.OpenApiXml xml) { } + [System.Obsolete("Use NodeType property instead. This property will be removed in a future version.")] public bool Attribute { get; set; } public System.Collections.Generic.IDictionary? Extensions { get; set; } public string? Name { get; set; } public System.Uri? Namespace { get; set; } + public Microsoft.OpenApi.OpenApiXmlNodeType? NodeType { get; set; } public string? Prefix { get; set; } + [System.Obsolete("Use NodeType property instead. This property will be removed in a future version.")] public bool Wrapped { get; set; } public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } + public enum OpenApiXmlNodeType + { + [Microsoft.OpenApi.Display("element")] + Element = 0, + [Microsoft.OpenApi.Display("attribute")] + Attribute = 1, + [Microsoft.OpenApi.Display("text")] + Text = 2, + [Microsoft.OpenApi.Display("cdata")] + Cdata = 3, + [Microsoft.OpenApi.Display("none")] + None = 4, + } public class OpenApiYamlWriter : Microsoft.OpenApi.OpenApiWriterBase { public OpenApiYamlWriter(System.IO.TextWriter textWriter) { } From 61bf18b8d5bf42b1ff9d0c9a0dda154bb4b744d4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 2 Oct 2025 13:37:45 -0400 Subject: [PATCH 3/7] chore: removes fields to avoid multi-state issues Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Models/OpenApiXml.cs | 49 ++++--------------- .../Models/OpenApiXmlTests.cs | 7 +-- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index f875071c3..cd0103872 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -11,10 +11,6 @@ namespace Microsoft.OpenApi /// public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible { - private OpenApiXmlNodeType? _nodeType; - private bool _attribute; - private bool _wrapped; - /// /// Replaces the name of the element/attribute used for the described schema property. /// @@ -39,17 +35,11 @@ public bool Attribute { get { - // If NodeType is explicitly set, use it; otherwise use the backing field - if (_nodeType.HasValue) - { - return _nodeType == OpenApiXmlNodeType.Attribute; - } - return _attribute; + return NodeType == OpenApiXmlNodeType.Attribute; } set { - _attribute = value; - _nodeType = value ? OpenApiXmlNodeType.Attribute : OpenApiXmlNodeType.None; + NodeType = value ? OpenApiXmlNodeType.Attribute : OpenApiXmlNodeType.None; } } @@ -62,28 +52,18 @@ public bool Wrapped { get { - // If NodeType is explicitly set, use it; otherwise use the backing field - if (_nodeType.HasValue) - { - return _nodeType == OpenApiXmlNodeType.Element; - } - return _wrapped; + return NodeType == OpenApiXmlNodeType.Element; } set { - _wrapped = value; - _nodeType = value ? OpenApiXmlNodeType.Element : OpenApiXmlNodeType.None; + NodeType = value ? OpenApiXmlNodeType.Element : OpenApiXmlNodeType.None; } } /// /// The node type of the XML representation. /// - public OpenApiXmlNodeType? NodeType - { - get => _nodeType; - set => _nodeType = value; - } + public OpenApiXmlNodeType? NodeType { get; set; } /// /// Specification Extensions. @@ -103,9 +83,7 @@ public OpenApiXml(OpenApiXml xml) Name = xml?.Name ?? Name; Namespace = xml?.Namespace ?? Namespace; Prefix = xml?.Prefix ?? Prefix; - _nodeType = xml?._nodeType ?? _nodeType; - _attribute = xml?._attribute ?? _attribute; - _wrapped = xml?._wrapped ?? _wrapped; + NodeType = xml?.NodeType ?? NodeType; Extensions = xml?.Extensions != null ? new Dictionary(xml.Extensions) : null; } @@ -159,9 +137,9 @@ private void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) // For OpenAPI 3.2.0 and above, serialize nodeType if (specVersion >= OpenApiSpecVersion.OpenApi3_2) { - if (_nodeType.HasValue) + if (NodeType.HasValue) { - writer.WriteProperty(OpenApiConstants.NodeType, _nodeType.Value.GetDisplayName()); + writer.WriteProperty(OpenApiConstants.NodeType, NodeType.Value.GetDisplayName()); } } else @@ -169,15 +147,8 @@ private void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) // For OpenAPI 3.1.0 and below, serialize attribute and wrapped // Use backing fields if they were set via obsolete properties, // otherwise derive from NodeType if set - bool attribute = _attribute; - bool wrapped = _wrapped; - - if (_nodeType.HasValue && !_attribute && !_wrapped) - { - // NodeType was set directly, not via obsolete properties - attribute = _nodeType == OpenApiXmlNodeType.Attribute; - wrapped = _nodeType == OpenApiXmlNodeType.Element; - } + var attribute = NodeType.HasValue && NodeType == OpenApiXmlNodeType.Attribute; + var wrapped = NodeType.HasValue && NodeType == OpenApiXmlNodeType.Element; writer.WriteProperty(OpenApiConstants.Attribute, attribute, false); writer.WriteProperty(OpenApiConstants.Wrapped, wrapped, false); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index cd1e446c2..ed4bbc697 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Xunit; @@ -75,7 +76,6 @@ public async Task SerializeAdvancedXmlAsJsonWorks(OpenApiSpecVersion version) "namespace": "http://swagger.io/schema/sample", "prefix": "sample", "attribute": true, - "wrapped": true, "x-xml-extension": 7 } """; @@ -84,9 +84,7 @@ public async Task SerializeAdvancedXmlAsJsonWorks(OpenApiSpecVersion version) var actual = await AdvancedXml.SerializeAsJsonAsync(version); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } [Theory] @@ -101,7 +99,6 @@ public async Task SerializeAdvancedXmlAsYamlWorks(OpenApiSpecVersion version) namespace: http://swagger.io/schema/sample prefix: sample attribute: true - wrapped: true x-xml-extension: 7 """; From 1125d61d0071b148ac27507341d1a52a8e81b62e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 2 Oct 2025 13:40:29 -0400 Subject: [PATCH 4/7] fix!: hides the attribute and wrapped properties on the xml node to avoid any confusion with the new node type property Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Models/OpenApiXml.cs | 4 +-- .../Reader/V32/OpenApiXmlDeserializer.cs | 26 ------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index cd0103872..82d7c2180 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -31,7 +31,7 @@ public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible /// Default value is false. /// [Obsolete("Use NodeType property instead. This property will be removed in a future version.")] - public bool Attribute + internal bool Attribute { get { @@ -48,7 +48,7 @@ public bool Attribute /// Default value is false. /// [Obsolete("Use NodeType property instead. This property will be removed in a future version.")] - public bool Wrapped + internal bool Wrapped { get { diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs index 0bb2a63c8..ae52c4abe 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs @@ -34,32 +34,6 @@ internal static partial class OpenApiV32Deserializer "prefix", (o, n, _) => o.Prefix = n.GetScalarValue() }, - { - "attribute", - (o, n, _) => - { - var attribute = n.GetScalarValue(); - if (attribute is not null) - { -#pragma warning disable CS0618 // Type or member is obsolete - o.Attribute = bool.Parse(attribute); -#pragma warning restore CS0618 // Type or member is obsolete - } - } - }, - { - "wrapped", - (o, n, _) => - { - var wrapped = n.GetScalarValue(); - if (wrapped is not null) - { -#pragma warning disable CS0618 // Type or member is obsolete - o.Wrapped = bool.Parse(wrapped); -#pragma warning restore CS0618 // Type or member is obsolete - } - } - }, { "nodeType", (o, n, _) => From d8a9e28bc8a3c3f5135d9dcdcb2791a7eb9a4f7f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 2 Oct 2025 14:15:21 -0400 Subject: [PATCH 5/7] chore: use deep equals for unit tests Signed-off-by: Vincent Biret --- .../Models/OpenApiXmlTests.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index ed4bbc697..6426c0237 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -130,9 +130,7 @@ public async Task SerializeXmlWithNodeTypeAsJsonV32Works() var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } [Fact] @@ -173,9 +171,7 @@ public async Task SerializeXmlWithAttributeNodeTypeAsJsonV32Works() var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } [Fact] @@ -197,9 +193,7 @@ public async Task SerializeXmlWithNodeTypeAsJsonV31DoesNotSerializeNodeType() var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } [Fact] @@ -218,9 +212,7 @@ public async Task SerializeXmlWithAttributeNodeTypeAsJsonV31DoesNotSerializeNode var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); } } } From dd8b848ea167d36d50ed0c4cf86ff91ad450cae4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 2 Oct 2025 14:17:17 -0400 Subject: [PATCH 6/7] chore: updates the public api export Signed-off-by: Vincent Biret --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 1bc2006ae..d96b0d82f 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1697,15 +1697,11 @@ namespace Microsoft.OpenApi { public OpenApiXml() { } public OpenApiXml(Microsoft.OpenApi.OpenApiXml xml) { } - [System.Obsolete("Use NodeType property instead. This property will be removed in a future version.")] - public bool Attribute { get; set; } public System.Collections.Generic.IDictionary? Extensions { get; set; } public string? Name { get; set; } public System.Uri? Namespace { get; set; } public Microsoft.OpenApi.OpenApiXmlNodeType? NodeType { get; set; } public string? Prefix { get; set; } - [System.Obsolete("Use NodeType property instead. This property will be removed in a future version.")] - public bool Wrapped { get; set; } public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } From 1b57b16a174545cc7b4c1e871f729364789326f9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 2 Oct 2025 14:20:38 -0400 Subject: [PATCH 7/7] chore: use serialization infrastructure for enums Signed-off-by: Vincent Biret --- .../Reader/V32/OpenApiXmlDeserializer.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs index ae52c4abe..6f3de6145 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs @@ -38,19 +38,11 @@ internal static partial class OpenApiV32Deserializer "nodeType", (o, n, _) => { - var nodeType = n.GetScalarValue(); - if (nodeType is not null) + if (!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var nodeType)) { - o.NodeType = nodeType.ToLowerInvariant() switch - { - "element" => OpenApiXmlNodeType.Element, - "attribute" => OpenApiXmlNodeType.Attribute, - "text" => OpenApiXmlNodeType.Text, - "cdata" => OpenApiXmlNodeType.Cdata, - "none" => OpenApiXmlNodeType.None, - _ => null - }; + return; } + o.NodeType = nodeType; } } };