Skip to content

Commit 46e08d4

Browse files
committed
fix: tag reference proxy design pattern implementation
Signed-off-by: Vincent Biret <[email protected]>
1 parent b17a552 commit 46e08d4

File tree

8 files changed

+95
-128
lines changed

8 files changed

+95
-128
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,15 @@ public interface IOpenApiDescribedElement : IOpenApiElement
1313
/// </summary>
1414
public string Description { get; set; }
1515
}
16+
17+
/// <summary>
18+
/// Describes an element that has a description.
19+
/// </summary>
20+
public interface IOpenApiReadOnlyDescribedElement : IOpenApiElement
21+
{
22+
/// <summary>
23+
/// Long description for the example.
24+
/// CommonMark syntax MAY be used for rich text representation.
25+
/// </summary>
26+
public string Description { get; }
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.OpenApi.Interfaces;
2+
3+
namespace Microsoft.OpenApi.Models.Interfaces;
4+
5+
/// <summary>
6+
/// Defines the base properties for the path item object.
7+
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
8+
/// </summary>
9+
public interface IOpenApiTag : IOpenApiSerializable, IOpenApiReadOnlyExtensible, IOpenApiReadOnlyDescribedElement
10+
{
11+
/// <summary>
12+
/// The name of the tag.
13+
/// </summary>
14+
public string Name { get; }
15+
16+
/// <summary>
17+
/// Additional external documentation for this tag.
18+
/// </summary>
19+
public OpenApiExternalDocs ExternalDocs { get; }
20+
}

src/Microsoft.OpenApi/Models/OpenApiTag.cs

+16-29
Original file line numberDiff line numberDiff line change
@@ -4,61 +4,48 @@
44
using System;
55
using System.Collections.Generic;
66
using Microsoft.OpenApi.Interfaces;
7+
using Microsoft.OpenApi.Models.Interfaces;
78
using Microsoft.OpenApi.Writers;
89

910
namespace Microsoft.OpenApi.Models
1011
{
1112
/// <summary>
1213
/// Tag Object.
1314
/// </summary>
14-
public class OpenApiTag : IOpenApiReferenceable, IOpenApiExtensible
15+
public class OpenApiTag : IOpenApiExtensible, IOpenApiReferenceable, IOpenApiTag, IOpenApiDescribedElement
1516
{
16-
/// <summary>
17-
/// The name of the tag.
18-
/// </summary>
19-
public virtual string Name { get; set; }
17+
/// <inheritdoc/>
18+
public string Name { get; set; }
2019

21-
/// <summary>
22-
/// A short description for the tag.
23-
/// </summary>
24-
public virtual string Description { get; set; }
20+
/// <inheritdoc/>
21+
public string Description { get; set; }
2522

26-
/// <summary>
27-
/// Additional external documentation for this tag.
28-
/// </summary>
29-
public virtual OpenApiExternalDocs ExternalDocs { get; set; }
23+
/// <inheritdoc/>
24+
public OpenApiExternalDocs ExternalDocs { get; set; }
3025

31-
/// <summary>
32-
/// This object MAY be extended with Specification Extensions.
33-
/// </summary>
34-
public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
35-
36-
/// <summary>
37-
/// Indicates if object is populated with data or is just a reference to the data
38-
/// </summary>
39-
public bool UnresolvedReference { get; set; }
26+
/// <inheritdoc/>
27+
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
4028

4129
/// <summary>
4230
/// Parameterless constructor
4331
/// </summary>
4432
public OpenApiTag() { }
4533

4634
/// <summary>
47-
/// Initializes a copy of an <see cref="OpenApiTag"/> object
35+
/// Initializes a copy of an <see cref="IOpenApiTag"/> object
4836
/// </summary>
49-
public OpenApiTag(OpenApiTag tag)
37+
public OpenApiTag(IOpenApiTag tag)
5038
{
5139
Name = tag?.Name ?? Name;
5240
Description = tag?.Description ?? Description;
5341
ExternalDocs = tag?.ExternalDocs != null ? new(tag.ExternalDocs) : null;
5442
Extensions = tag?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(tag.Extensions) : null;
55-
UnresolvedReference = tag?.UnresolvedReference ?? UnresolvedReference;
5643
}
5744

5845
/// <summary>
5946
/// Serialize <see cref="OpenApiTag"/> to Open Api v3.1
6047
/// </summary>
61-
public virtual void SerializeAsV31(IOpenApiWriter writer)
48+
public void SerializeAsV31(IOpenApiWriter writer)
6249
{
6350
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1,
6451
(writer, element) => element.SerializeAsV31(writer));
@@ -67,13 +54,13 @@ public virtual void SerializeAsV31(IOpenApiWriter writer)
6754
/// <summary>
6855
/// Serialize <see cref="OpenApiTag"/> to Open Api v3.0
6956
/// </summary>
70-
public virtual void SerializeAsV3(IOpenApiWriter writer)
57+
public void SerializeAsV3(IOpenApiWriter writer)
7158
{
7259
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0,
7360
(writer, element) => element.SerializeAsV3(writer));
7461
}
7562

76-
internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
63+
internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
7764
Action<IOpenApiWriter, IOpenApiSerializable> callback)
7865
{
7966
writer.WriteStartObject();
@@ -96,7 +83,7 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
9683
/// <summary>
9784
/// Serialize <see cref="OpenApiTag"/> to Open Api v2.0
9885
/// </summary>
99-
public virtual void SerializeAsV2(IOpenApiWriter writer)
86+
public void SerializeAsV2(IOpenApiWriter writer)
10087
{
10188
writer.WriteStartObject();
10289

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ namespace Microsoft.OpenApi.Models.References;
1010
/// <typeparam name="V">The interface type for the model.</typeparam>
1111
public abstract class BaseOpenApiReferenceHolder<T, V> : IOpenApiReferenceHolder<T, V> where T : class, IOpenApiReferenceable, V where V : IOpenApiSerializable
1212
{
13-
private T _target;
13+
/// <summary>
14+
/// The resolved target object.
15+
/// </summary>
16+
protected T _target;
1417
/// <inheritdoc/>
15-
public T Target
18+
public virtual T Target
1619
{
1720
get
1821
{

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

+13-73
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,19 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using Microsoft.OpenApi.Interfaces;
8-
using Microsoft.OpenApi.Writers;
8+
using Microsoft.OpenApi.Models.Interfaces;
99

1010
namespace Microsoft.OpenApi.Models.References
1111
{
1212
/// <summary>
1313
/// Tag Object Reference
1414
/// </summary>
15-
public class OpenApiTagReference : OpenApiTag, IOpenApiReferenceHolder<OpenApiTag>
15+
public class OpenApiTagReference : BaseOpenApiReferenceHolder<OpenApiTag, IOpenApiTag>, IOpenApiTag
1616
{
17-
internal OpenApiTag _target;
18-
19-
/// <summary>
20-
/// Reference.
21-
/// </summary>
22-
public OpenApiReference Reference { get; set; }
23-
2417
/// <summary>
2518
/// Resolved target of the reference.
2619
/// </summary>
27-
public OpenApiTag Target
20+
public override OpenApiTag Target
2821
{
2922
get
3023
{
@@ -38,85 +31,32 @@ public OpenApiTag Target
3831
/// </summary>
3932
/// <param name="referenceId">The reference Id.</param>
4033
/// <param name="hostDocument">The host OpenAPI document.</param>
41-
public OpenApiTagReference(string referenceId, OpenApiDocument hostDocument)
34+
public OpenApiTagReference(string referenceId, OpenApiDocument hostDocument):base(referenceId, hostDocument, ReferenceType.Tag)
4235
{
43-
Utils.CheckArgumentNullOrEmpty(referenceId);
44-
45-
Reference = new OpenApiReference()
46-
{
47-
Id = referenceId,
48-
HostDocument = hostDocument,
49-
Type = ReferenceType.Tag
50-
};
5136
}
5237

53-
/// <summary>
54-
/// Copy Constructor
55-
/// </summary>
56-
/// <param name="source">The source to copy information from.</param>
57-
public OpenApiTagReference(OpenApiTagReference source):base()
38+
internal OpenApiTagReference(OpenApiTag target, string referenceId):base(target, referenceId, ReferenceType.Tag)
5839
{
59-
Reference = source?.Reference != null ? new(source.Reference) : null;
60-
_target = source?._target;
6140
}
6241

63-
private const string ReferenceErrorMessage = "Setting the value from the reference is not supported, use the target property instead.";
6442
/// <inheritdoc/>
65-
public override string Description { get => Target.Description; set => throw new InvalidOperationException(ReferenceErrorMessage); }
66-
67-
/// <inheritdoc/>
68-
public override OpenApiExternalDocs ExternalDocs { get => Target.ExternalDocs; set => throw new InvalidOperationException(ReferenceErrorMessage); }
69-
70-
/// <inheritdoc/>
71-
public override IDictionary<string, IOpenApiExtension> Extensions { get => Target.Extensions; set => throw new InvalidOperationException(ReferenceErrorMessage); }
72-
73-
/// <inheritdoc/>
74-
public override string Name { get => Target.Name; set => throw new InvalidOperationException(ReferenceErrorMessage); }
75-
76-
/// <inheritdoc/>
77-
public override void SerializeAsV3(IOpenApiWriter writer)
43+
public string Description
7844
{
79-
if (!writer.GetSettings().ShouldInlineReference(Reference))
80-
{
81-
Reference.SerializeAsV3(writer);
82-
}
83-
else
84-
{
85-
SerializeInternal(writer);
86-
}
45+
get => string.IsNullOrEmpty(Reference?.Description) ? Target?.Description : Reference.Description;
8746
}
8847

8948
/// <inheritdoc/>
90-
public override void SerializeAsV31(IOpenApiWriter writer)
91-
{
92-
if (!writer.GetSettings().ShouldInlineReference(Reference))
93-
{
94-
Reference.SerializeAsV31(writer);
95-
}
96-
else
97-
{
98-
SerializeInternal(writer);
99-
}
100-
}
49+
public OpenApiExternalDocs ExternalDocs { get => Target?.ExternalDocs; }
10150

10251
/// <inheritdoc/>
103-
public override void SerializeAsV2(IOpenApiWriter writer)
104-
{
105-
if (!writer.GetSettings().ShouldInlineReference(Reference))
106-
{
107-
Reference.SerializeAsV2(writer);
108-
}
109-
else
110-
{
111-
SerializeInternal(writer);
112-
}
113-
}
52+
public IDictionary<string, IOpenApiExtension> Extensions { get => Target?.Extensions; }
11453

11554
/// <inheritdoc/>
116-
private void SerializeInternal(IOpenApiWriter writer)
55+
public string Name { get => Target?.Name; }
56+
/// <inheritdoc/>
57+
public override IOpenApiTag CopyReferenceAsTargetElementWithOverrides(IOpenApiTag source)
11758
{
118-
Utils.CheckArgumentNull(writer);
119-
writer.WriteValue(Name);
59+
return source is OpenApiTag ? new OpenApiTag(this) : source;
12060
}
12161
}
12262
}

test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading.Tasks;
88
using Microsoft.OpenApi.Interfaces;
99
using Microsoft.OpenApi.Models;
10+
using Microsoft.OpenApi.Models.Interfaces;
1011
using Microsoft.OpenApi.Models.References;
1112
using Microsoft.OpenApi.Writers;
1213
using VerifyXunit;
@@ -30,7 +31,7 @@ public class OpenApiTagTests
3031
}
3132
};
3233

33-
public static OpenApiTag ReferencedTag = new OpenApiTagReference("pet", null);
34+
public static IOpenApiTag ReferencedTag = new OpenApiTagReference(AdvancedTag, "pet");
3435

3536
[Theory]
3637
[InlineData(true)]

test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs

-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ public void TagReferenceResolutionWorks()
7474
// Assert
7575
Assert.Equal("user", _openApiTagReference.Name);
7676
Assert.Equal("Operations about users.", _openApiTagReference.Description);
77-
Assert.Throws<InvalidOperationException>(() => _openApiTagReference.Description = "New Description");
7877
}
7978

8079
[Theory]

test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt

+27-22
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ namespace Microsoft.OpenApi.Models.Interfaces
392392
System.Collections.Generic.IList<Microsoft.OpenApi.Models.Interfaces.IOpenApiParameter> Parameters { get; }
393393
System.Collections.Generic.IList<Microsoft.OpenApi.Models.OpenApiServer> Servers { get; }
394394
}
395+
public interface IOpenApiReadOnlyDescribedElement : Microsoft.OpenApi.Interfaces.IOpenApiElement
396+
{
397+
string Description { get; }
398+
}
395399
public interface IOpenApiRequestBody : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiDescribedElement
396400
{
397401
System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Models.OpenApiMediaType> Content { get; }
@@ -473,6 +477,11 @@ namespace Microsoft.OpenApi.Models.Interfaces
473477
{
474478
string Summary { get; set; }
475479
}
480+
public interface IOpenApiTag : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiReadOnlyDescribedElement
481+
{
482+
Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; }
483+
string Name { get; }
484+
}
476485
}
477486
namespace Microsoft.OpenApi.Models
478487
{
@@ -1109,18 +1118,17 @@ namespace Microsoft.OpenApi.Models
11091118
public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
11101119
public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
11111120
}
1112-
public class OpenApiTag : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable
1121+
public class OpenApiTag : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiDescribedElement, Microsoft.OpenApi.Models.Interfaces.IOpenApiReadOnlyDescribedElement, Microsoft.OpenApi.Models.Interfaces.IOpenApiTag
11131122
{
11141123
public OpenApiTag() { }
1115-
public OpenApiTag(Microsoft.OpenApi.Models.OpenApiTag tag) { }
1116-
public bool UnresolvedReference { get; set; }
1117-
public virtual string Description { get; set; }
1118-
public virtual System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Interfaces.IOpenApiExtension> Extensions { get; set; }
1119-
public virtual Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; }
1120-
public virtual string Name { get; set; }
1121-
public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1122-
public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1123-
public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1124+
public OpenApiTag(Microsoft.OpenApi.Models.Interfaces.IOpenApiTag tag) { }
1125+
public string Description { get; set; }
1126+
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Interfaces.IOpenApiExtension> Extensions { get; set; }
1127+
public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; }
1128+
public string Name { get; set; }
1129+
public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1130+
public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1131+
public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
11241132
}
11251133
public class OpenApiXml : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable
11261134
{
@@ -1234,10 +1242,11 @@ namespace Microsoft.OpenApi.Models.References
12341242
where T : class, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, V
12351243
where V : Microsoft.OpenApi.Interfaces.IOpenApiSerializable
12361244
{
1245+
protected T _target;
12371246
protected BaseOpenApiReferenceHolder(Microsoft.OpenApi.Models.References.BaseOpenApiReferenceHolder<T, V> source) { }
12381247
protected BaseOpenApiReferenceHolder(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, Microsoft.OpenApi.Models.ReferenceType referenceType, string externalResource = null) { }
12391248
public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; }
1240-
public T Target { get; }
1249+
public virtual T Target { get; }
12411250
public bool UnresolvedReference { get; set; }
12421251
public abstract V CopyReferenceAsTargetElementWithOverrides(V source);
12431252
public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
@@ -1426,19 +1435,15 @@ namespace Microsoft.OpenApi.Models.References
14261435
public Microsoft.OpenApi.Models.SecuritySchemeType? Type { get; }
14271436
public override Microsoft.OpenApi.Models.Interfaces.IOpenApiSecurityScheme CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.Models.Interfaces.IOpenApiSecurityScheme source) { }
14281437
}
1429-
public class OpenApiTagReference : Microsoft.OpenApi.Models.OpenApiTag, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder<Microsoft.OpenApi.Models.OpenApiTag>, Microsoft.OpenApi.Interfaces.IOpenApiSerializable
1438+
public class OpenApiTagReference : Microsoft.OpenApi.Models.References.BaseOpenApiReferenceHolder<Microsoft.OpenApi.Models.OpenApiTag, Microsoft.OpenApi.Models.Interfaces.IOpenApiTag>, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiReadOnlyDescribedElement, Microsoft.OpenApi.Models.Interfaces.IOpenApiTag
14301439
{
1431-
public OpenApiTagReference(Microsoft.OpenApi.Models.References.OpenApiTagReference source) { }
14321440
public OpenApiTagReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument) { }
1433-
public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; }
1434-
public Microsoft.OpenApi.Models.OpenApiTag Target { get; }
1435-
public override string Description { get; set; }
1436-
public override System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Interfaces.IOpenApiExtension> Extensions { get; set; }
1437-
public override Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; }
1438-
public override string Name { get; set; }
1439-
public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1440-
public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1441-
public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
1441+
public string Description { get; }
1442+
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Interfaces.IOpenApiExtension> Extensions { get; }
1443+
public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; }
1444+
public string Name { get; }
1445+
public override Microsoft.OpenApi.Models.OpenApiTag Target { get; }
1446+
public override Microsoft.OpenApi.Models.Interfaces.IOpenApiTag CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.Models.Interfaces.IOpenApiTag source) { }
14421447
}
14431448
}
14441449
namespace Microsoft.OpenApi.Reader

0 commit comments

Comments
 (0)