diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs
index 1756f9dde..47ef07d53 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..82d7c2180 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs
@@ -30,13 +30,40 @@ 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.")]
+ internal bool Attribute
+ {
+ get
+ {
+ return NodeType == OpenApiXmlNodeType.Attribute;
+ }
+ set
+ {
+ 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.")]
+ internal bool Wrapped
+ {
+ get
+ {
+ return NodeType == OpenApiXmlNodeType.Element;
+ }
+ set
+ {
+ NodeType = value ? OpenApiXmlNodeType.Element : OpenApiXmlNodeType.None;
+ }
+ }
+
+ ///
+ /// The node type of the XML representation.
+ ///
+ public OpenApiXmlNodeType? NodeType { get; set; }
///
/// Specification Extensions.
@@ -56,8 +83,7 @@ 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;
Extensions = xml?.Extensions != null ? new Dictionary(xml.Extensions) : null;
}
@@ -108,11 +134,25 @@ 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
+ 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);
+ }
// 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..6f3de6145 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs
@@ -35,25 +35,14 @@ internal static partial class OpenApiV32Deserializer
(o, n, _) => o.Prefix = n.GetScalarValue()
},
{
- "attribute",
+ "nodeType",
(o, n, _) =>
{
- var attribute = n.GetScalarValue();
- if (attribute is not null)
+ if (!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var nodeType))
{
- o.Attribute = bool.Parse(attribute);
- }
- }
- },
- {
- "wrapped",
- (o, n, _) =>
- {
- var wrapped = n.GetScalarValue();
- if (wrapped is not null)
- {
- o.Wrapped = bool.Parse(wrapped);
+ return;
}
+ o.NodeType = nodeType;
}
}
};
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..6426c0237 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;
@@ -10,6 +11,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 +24,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)]
@@ -55,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
}
""";
@@ -64,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]
@@ -81,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
""";
@@ -93,5 +110,109 @@ 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
+ Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)));
+ }
+
+ [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
+ Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)));
+ }
+
+ [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
+ Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)));
+ }
+
+ [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
+ Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)));
+ }
}
}
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index fc6560475..d96b0d82f 100644
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -508,6 +508,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";
@@ -1696,17 +1697,29 @@ namespace Microsoft.OpenApi
{
public OpenApiXml() { }
public OpenApiXml(Microsoft.OpenApi.OpenApiXml xml) { }
- 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; }
- 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) { }