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;
}
}
};