Skip to content

Commit e791bf6

Browse files
committed
add V3.2 properties on OpenApiTag
1 parent 9bba39f commit e791bf6

File tree

10 files changed

+511
-23
lines changed

10 files changed

+511
-23
lines changed

src/Microsoft.OpenApi/Models/Interfaces/IOpenApiTag.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,19 @@ public interface IOpenApiTag : IOpenApiReadOnlyExtensible, IOpenApiReadOnlyDescr
1515
/// Additional external documentation for this tag.
1616
/// </summary>
1717
public OpenApiExternalDocs? ExternalDocs { get; }
18+
19+
/// <summary>
20+
/// A short summary of the tag, used for display purposes.
21+
/// </summary>
22+
public string? Summary { get; }
23+
24+
/// <summary>
25+
/// The tag that this tag is nested under.
26+
/// </summary>
27+
public OpenApiTagReference? Parent { get; }
28+
29+
/// <summary>
30+
/// A machine-readable string to categorize what sort of tag it is.
31+
/// </summary>
32+
public string? Kind { get; }
1833
}

src/Microsoft.OpenApi/Models/OpenApiTag.cs

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ public class OpenApiTag : IOpenApiExtensible, IOpenApiTag, IOpenApiDescribedElem
2323
/// <inheritdoc/>
2424
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }
2525

26+
/// <inheritdoc/>
27+
public string? Summary { get; set; }
28+
29+
/// <inheritdoc/>
30+
public OpenApiTagReference? Parent { get; set; }
31+
32+
/// <inheritdoc/>
33+
public string? Kind { get; set; }
34+
2635
/// <summary>
2736
/// Parameterless constructor
2837
/// </summary>
@@ -38,15 +47,30 @@ internal OpenApiTag(IOpenApiTag tag)
3847
Description = tag.Description ?? Description;
3948
ExternalDocs = tag.ExternalDocs != null ? new(tag.ExternalDocs) : null;
4049
Extensions = tag.Extensions != null ? new Dictionary<string, IOpenApiExtension>(tag.Extensions) : null;
50+
Summary = tag.Summary ?? Summary;
51+
Parent = tag.Parent ?? Parent;
52+
Kind = tag.Kind ?? Kind;
4153
}
42-
54+
4355
/// <summary>
4456
/// Serialize <see cref="OpenApiTag"/> to Open Api v3.2
4557
/// </summary>
46-
public virtual void SerializeAsV32(IOpenApiWriter writer)
58+
public virtual void SerializeAsV32(IOpenApiWriter writer)
4759
{
4860
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2,
4961
(writer, element) => element.SerializeAsV32(writer));
62+
63+
if (Summary != null)
64+
writer.WriteProperty("summary", Summary);
65+
if (Parent != null)
66+
{
67+
writer.WritePropertyName("parent");
68+
Parent.SerializeAsV32(writer);
69+
}
70+
if (Kind != null)
71+
writer.WriteProperty("kind", Kind);
72+
73+
writer.WriteEndObject();
5074
}
5175

5276
/// <summary>
@@ -56,22 +80,48 @@ public virtual void SerializeAsV31(IOpenApiWriter writer)
5680
{
5781
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1,
5882
(writer, element) => element.SerializeAsV31(writer));
83+
84+
if (Summary != null)
85+
writer.WriteProperty("x-oas-summary", Summary);
86+
if (Parent != null)
87+
{
88+
writer.WritePropertyName("x-oas-parent");
89+
Parent.SerializeAsV31(writer);
90+
}
91+
if (Kind != null)
92+
writer.WriteProperty("x-oas-kind", Kind);
93+
94+
95+
writer.WriteEndObject();
5996
}
6097

6198
/// <summary>
6299
/// Serialize <see cref="OpenApiTag"/> to Open Api v3.0
63100
/// </summary>
64-
public virtual void SerializeAsV3(IOpenApiWriter writer)
101+
public virtual void SerializeAsV3(IOpenApiWriter writer)
65102
{
66103
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0,
67104
(writer, element) => element.SerializeAsV3(writer));
105+
106+
if (Summary != null)
107+
writer.WriteProperty("x-oas-summary", Summary);
108+
if (Parent != null)
109+
{
110+
writer.WritePropertyName("x-oas-parent");
111+
Parent.SerializeAsV31(writer);
112+
}
113+
if (Kind != null)
114+
writer.WriteProperty("x-oas-kind", Kind);
115+
116+
117+
writer.WriteEndObject();
68118
}
69119

70-
internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
120+
internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
71121
Action<IOpenApiWriter, IOpenApiSerializable> callback)
72122
{
73123
writer.WriteStartObject();
74-
124+
75125
// name
76126
writer.WriteProperty(OpenApiConstants.Name, Name);
77127

@@ -83,28 +133,15 @@ internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion versio
83133

84134
// extensions.
85135
writer.WriteExtensions(Extensions, version);
86-
87-
writer.WriteEndObject();
88136
}
89137

90138
/// <summary>
91139
/// Serialize <see cref="OpenApiTag"/> to Open Api v2.0
92140
/// </summary>
93141
public virtual void SerializeAsV2(IOpenApiWriter writer)
94142
{
95-
writer.WriteStartObject();
96-
97-
// name
98-
writer.WriteProperty(OpenApiConstants.Name, Name);
99-
100-
// description
101-
writer.WriteProperty(OpenApiConstants.Description, Description);
102-
103-
// external docs
104-
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV2(w));
105-
106-
// extensions
107-
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);
143+
SerializeInternal(writer, OpenApiSpecVersion.OpenApi2_0,
144+
(writer, element) => element.SerializeAsV3(writer));
108145

109146
writer.WriteEndObject();
110147
}

src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ public string? Description
5858

5959
/// <inheritdoc/>
6060
public string? Name { get => Target?.Name ?? Reference?.Id; }
61+
62+
/// <inheritdoc/>
63+
public string? Summary => Target?.Summary;
64+
65+
/// <inheritdoc/>
66+
public OpenApiTagReference? Parent => Target?.Parent;
67+
68+
/// <inheritdoc/>
69+
public string? Kind => Target?.Kind;
70+
6171
/// <inheritdoc/>
6272
public override IOpenApiTag CopyReferenceAsTargetElementWithOverrides(IOpenApiTag source)
6373
{

src/Microsoft.OpenApi/Reader/V3/OpenApiTagDeserializer.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,32 @@ internal static partial class OpenApiV3Deserializer
2929

3030
private static readonly PatternFieldMap<OpenApiTag> _tagPatternFields = new()
3131
{
32-
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
32+
{
33+
s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase),
34+
(o, p, n, doc) =>
35+
{
36+
if (p.Equals("x-oas-summary", StringComparison.OrdinalIgnoreCase))
37+
{
38+
o.Summary = n.GetScalarValue();
39+
}
40+
else if (p.Equals("x-oas-parent", StringComparison.OrdinalIgnoreCase))
41+
{
42+
var tagName = n.GetScalarValue();
43+
if (tagName != null)
44+
{
45+
o.Parent = LoadTagByReference(tagName, doc);
46+
}
47+
}
48+
else if (p.Equals("x-oas-kind", StringComparison.OrdinalIgnoreCase))
49+
{
50+
o.Kind = n.GetScalarValue();
51+
}
52+
else
53+
{
54+
o.AddExtension(p, LoadExtension(p, n));
55+
}
56+
}
57+
}
3358
};
3459

3560
public static OpenApiTag LoadTag(ParseNode n, OpenApiDocument hostDocument)

src/Microsoft.OpenApi/Reader/V31/OpenApiTagDeserializer.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,32 @@ internal static partial class OpenApiV31Deserializer
3535

3636
private static readonly PatternFieldMap<OpenApiTag> _tagPatternFields = new()
3737
{
38-
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
38+
{
39+
s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase),
40+
(o, p, n, doc) =>
41+
{
42+
if (p.Equals("x-oas-summary", StringComparison.OrdinalIgnoreCase))
43+
{
44+
o.Summary = n.GetScalarValue();
45+
}
46+
else if (p.Equals("x-oas-parent", StringComparison.OrdinalIgnoreCase))
47+
{
48+
var tagName = n.GetScalarValue();
49+
if (tagName != null)
50+
{
51+
o.Parent = LoadTagByReference(tagName, doc);
52+
}
53+
}
54+
else if (p.Equals("x-oas-kind", StringComparison.OrdinalIgnoreCase))
55+
{
56+
o.Kind = n.GetScalarValue();
57+
}
58+
else
59+
{
60+
o.AddExtension(p, LoadExtension(p, n));
61+
}
62+
}
63+
}
3964
};
4065

4166
public static OpenApiTag LoadTag(ParseNode n, OpenApiDocument hostDocument)

src/Microsoft.OpenApi/Reader/V32/OpenApiTagDeserializer.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System;
@@ -30,7 +30,30 @@ internal static partial class OpenApiV32Deserializer
3030
{
3131
o.ExternalDocs = LoadExternalDocs(n, t);
3232
}
33+
},
34+
{
35+
OpenApiConstants.Summary, (o, n, _) =>
36+
{
37+
o.Summary = n.GetScalarValue();
38+
}
39+
},
40+
{
41+
"parent", (o, n, doc) =>
42+
{
43+
var tagName = n.GetScalarValue();
44+
if (tagName != null)
45+
{
46+
o.Parent = LoadTagByReference(tagName, doc);
47+
}
48+
}
49+
},
50+
{
51+
"kind", (o, n, _) =>
52+
{
53+
o.Kind = n.GetScalarValue();
54+
}
3355
}
56+
3457
};
3558

3659
private static readonly PatternFieldMap<OpenApiTag> _tagPatternFields = new()
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json.Nodes;
3+
using Microsoft.OpenApi.Reader;
4+
using Microsoft.OpenApi.Reader.V31;
5+
using Xunit;
6+
7+
namespace Microsoft.OpenApi.Readers.Tests.V31Tests;
8+
9+
public class OpenApiTagDeserializerTests
10+
{
11+
[Fact]
12+
public void ShouldDeserializeTagWithNewV31Properties()
13+
{
14+
var json =
15+
"""
16+
{
17+
"name": "store",
18+
"description": "Store operations",
19+
"x-oas-summary": "Operations related to the pet store",
20+
"x-oas-parent": "pet",
21+
"x-oas-kind": "operational",
22+
"externalDocs": {
23+
"description": "Find more info here",
24+
"url": "https://example.com/"
25+
},
26+
"x-custom-extension": "test-value"
27+
}
28+
""";
29+
30+
var hostDocument = new OpenApiDocument();
31+
hostDocument.Tags ??= new HashSet<OpenApiTag>();
32+
hostDocument.Tags.Add(new OpenApiTag
33+
{
34+
Name = "pet",
35+
Description = "Parent tag for pets operations",
36+
});
37+
38+
var jsonNode = JsonNode.Parse(json);
39+
var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode);
40+
41+
var result = OpenApiV31Deserializer.LoadTag(parseNode, hostDocument);
42+
43+
Assert.NotNull(result);
44+
Assert.Equal("store", result.Name);
45+
Assert.Equal("Store operations", result.Description);
46+
Assert.Equal("Operations related to the pet store", result.Summary);
47+
Assert.Equal("operational", result.Kind);
48+
Assert.NotNull(result.Parent);
49+
Assert.Equal("pet", result.Parent.Reference.Id);
50+
Assert.NotNull(result.ExternalDocs);
51+
Assert.Equal("Find more info here", result.ExternalDocs.Description);
52+
Assert.Equal("https://example.com/", result.ExternalDocs.Url?.ToString());
53+
Assert.NotNull(result.Extensions);
54+
Assert.Single(result.Extensions);
55+
Assert.True(result.Extensions.ContainsKey("x-custom-extension"));
56+
}
57+
58+
[Fact]
59+
public void ShouldDeserializeTagWithBasicProperties()
60+
{
61+
var json =
62+
"""
63+
{
64+
"name": "pets",
65+
"description": "Pet operations",
66+
"x-oas-summary": "All operations for managing pets"
67+
}
68+
""";
69+
70+
var hostDocument = new OpenApiDocument();
71+
var jsonNode = JsonNode.Parse(json);
72+
var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode);
73+
74+
var result = OpenApiV31Deserializer.LoadTag(parseNode, hostDocument);
75+
76+
Assert.NotNull(result);
77+
Assert.Equal("pets", result.Name);
78+
Assert.Equal("Pet operations", result.Description);
79+
Assert.Equal("All operations for managing pets", result.Summary);
80+
Assert.Null(result.Parent);
81+
Assert.Null(result.Kind);
82+
}
83+
}

0 commit comments

Comments
 (0)