Skip to content

Commit 763c0c1

Browse files
committed
feat: tags references are now deduplicated as well
Signed-off-by: Vincent Biret <[email protected]>
1 parent 7b7be4a commit 763c0c1

File tree

16 files changed

+166
-57
lines changed

16 files changed

+166
-57
lines changed

src/Microsoft.OpenApi/Models/OpenApiOperation.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,33 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA
2323
/// </summary>
2424
public const bool DeprecatedDefault = false;
2525

26+
private HashSet<OpenApiTagReference>? _tags;
2627
/// <summary>
2728
/// A list of tags for API documentation control.
2829
/// Tags can be used for logical grouping of operations by resources or any other qualifier.
2930
/// </summary>
30-
public IList<OpenApiTagReference>? Tags { get; set; } = [];
31+
public ISet<OpenApiTagReference>? Tags
32+
{
33+
get
34+
{
35+
return _tags;
36+
}
37+
set
38+
{
39+
if (value is null)
40+
{
41+
return;
42+
}
43+
if (value is HashSet<OpenApiTagReference> tags && tags.Comparer is OpenApiTagComparer)
44+
{
45+
_tags = tags;
46+
}
47+
else
48+
{
49+
_tags = new HashSet<OpenApiTagReference>(value, OpenApiTagComparer.Instance);
50+
}
51+
}
52+
}
3153

3254
/// <summary>
3355
/// A short summary of what the operation does.
@@ -123,7 +145,7 @@ public OpenApiOperation() { }
123145
public OpenApiOperation(OpenApiOperation operation)
124146
{
125147
Utils.CheckArgumentNull(operation);
126-
Tags = operation.Tags != null ? new List<OpenApiTagReference>(operation.Tags) : null;
148+
Tags = operation.Tags != null ? new HashSet<OpenApiTagReference>(operation.Tags) : null;
127149
Summary = operation.Summary ?? Summary;
128150
Description = operation.Description ?? Description;
129151
ExternalDocs = operation.ExternalDocs != null ? new(operation.ExternalDocs) : null;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public override OpenApiTag Target
2121
{
2222
get
2323
{
24-
return Reference.HostDocument?.Tags.FirstOrDefault(t => OpenApiTagComparer.StringComparer.Equals(t.Name, Reference.Id));
24+
return Reference.HostDocument?.Tags?.FirstOrDefault(t => OpenApiTagComparer.StringComparer.Equals(t.Name, Reference.Id));
2525
}
2626
}
2727

src/Microsoft.OpenApi/OpenApiTagComparer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3-
using Microsoft.OpenApi.Models;
3+
using Microsoft.OpenApi.Models.Interfaces;
44

55
namespace Microsoft.OpenApi;
66

@@ -9,7 +9,7 @@ namespace Microsoft.OpenApi;
99
/// This comparer is used to maintain a globally unique list of tags encountered
1010
/// in a particular OpenAPI document.
1111
/// </summary>
12-
internal sealed class OpenApiTagComparer : IEqualityComparer<OpenApiTag>
12+
internal sealed class OpenApiTagComparer : IEqualityComparer<IOpenApiTag>
1313
{
1414
private static readonly Lazy<OpenApiTagComparer> _lazyInstance = new(() => new OpenApiTagComparer());
1515
/// <summary>
@@ -18,7 +18,7 @@ internal sealed class OpenApiTagComparer : IEqualityComparer<OpenApiTag>
1818
internal static OpenApiTagComparer Instance { get => _lazyInstance.Value; }
1919

2020
/// <inheritdoc/>
21-
public bool Equals(OpenApiTag? x, OpenApiTag? y)
21+
public bool Equals(IOpenApiTag? x, IOpenApiTag? y)
2222
{
2323
if (x is null && y is null)
2424
{
@@ -42,6 +42,6 @@ public bool Equals(OpenApiTag? x, OpenApiTag? y)
4242
internal static readonly StringComparer StringComparer = StringComparer.Ordinal;
4343

4444
/// <inheritdoc/>
45-
public int GetHashCode(OpenApiTag obj) => obj?.Name is null ? 0 : StringComparer.GetHashCode(obj.Name);
45+
public int GetHashCode(IOpenApiTag obj) => string.IsNullOrEmpty(obj?.Name) ? 0 : StringComparer.GetHashCode(obj!.Name);
4646
}
4747
#nullable restore

src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ internal static partial class OpenApiV2Deserializer
2323
new()
2424
{
2525
{
26-
"tags", (o, n, doc) => o.Tags = n.CreateSimpleList(
27-
(valueNode, doc) =>
28-
LoadTagByReference(
29-
valueNode.GetScalarValue(), doc), doc)
26+
"tags", (o, n, doc) => {
27+
if (n.CreateSimpleList((valueNode, doc) => LoadTagByReference(valueNode.GetScalarValue(), doc), doc) is {Count: > 0} tags)
28+
{
29+
o.Tags = new HashSet<OpenApiTagReference>(tags, OpenApiTagComparer.Instance);
30+
}
31+
}
3032
},
3133
{
3234
"summary",

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33

44
using System;
5+
using System.Collections.Generic;
56
using Microsoft.OpenApi.Extensions;
67
using Microsoft.OpenApi.Models;
78
using Microsoft.OpenApi.Models.References;
@@ -19,10 +20,12 @@ internal static partial class OpenApiV3Deserializer
1920
new()
2021
{
2122
{
22-
"tags", (o, n, doc) => o.Tags = n.CreateSimpleList(
23-
(valueNode, doc) =>
24-
LoadTagByReference(
25-
valueNode.GetScalarValue(), doc), doc)
23+
"tags", (o, n, doc) => {
24+
if (n.CreateSimpleList((valueNode, doc) => LoadTagByReference(valueNode.GetScalarValue(), doc), doc) is {Count: > 0} tags)
25+
{
26+
o.Tags = new HashSet<OpenApiTagReference>(tags, OpenApiTagComparer.Instance);
27+
}
28+
}
2629
},
2730
{
2831
"summary",

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Microsoft.OpenApi.Extensions;
34
using Microsoft.OpenApi.Models;
45
using Microsoft.OpenApi.Models.References;
@@ -16,9 +17,12 @@ internal static partial class OpenApiV31Deserializer
1617
new()
1718
{
1819
{
19-
"tags", (o, n, doc) => o.Tags = n.CreateSimpleList(
20-
(valueNode, doc) =>
21-
LoadTagByReference(valueNode.GetScalarValue(), doc), doc)
20+
"tags", (o, n, doc) => {
21+
if (n.CreateSimpleList((valueNode, doc) => LoadTagByReference(valueNode.GetScalarValue(), doc), doc) is {Count: > 0} tags)
22+
{
23+
o.Tags = new HashSet<OpenApiTagReference>(tags, OpenApiTagComparer.Instance);
24+
}
25+
}
2226
},
2327
{
2428
"summary", (o, n, _) =>

src/Microsoft.OpenApi/Services/OpenApiFilterService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,11 @@ private static void ValidateFilters(IDictionary<string, List<string>> requestUrl
363363
if (tagsArray.Length == 1)
364364
{
365365
var regex = new Regex(tagsArray[0]);
366-
return (_, _, operation) => operation.Tags.Any(tag => regex.IsMatch(tag.Name));
366+
return (_, _, operation) => operation.Tags?.Any(tag => regex.IsMatch(tag.Name)) ?? false;
367367
}
368368
else
369369
{
370-
return (_, _, operation) => operation.Tags.Any(tag => tagsArray.Contains(tag.Name));
370+
return (_, _, operation) => operation.Tags?.Any(tag => tagsArray.Contains(tag.Name)) ?? false;
371371
}
372372
}
373373

src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public virtual void Visit(ISet<OpenApiTag> openApiTags)
316316
/// <summary>
317317
/// Visits list of <see cref="OpenApiTagReference"/>
318318
/// </summary>
319-
public virtual void Visit(IList<OpenApiTagReference> openApiTags)
319+
public virtual void Visit(ISet<OpenApiTagReference> openApiTags)
320320
{
321321
}
322322

src/Microsoft.OpenApi/Services/OpenApiWalker.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ internal void Walk(ISet<OpenApiTag> tags)
8484
/// <summary>
8585
/// Visits list of <see cref="OpenApiTagReference"/> and child objects
8686
/// </summary>
87-
internal void Walk(IList<OpenApiTagReference> tags)
87+
internal void Walk(ISet<OpenApiTagReference> tags)
8888
{
8989
if (tags == null)
9090
{
@@ -96,9 +96,10 @@ internal void Walk(IList<OpenApiTagReference> tags)
9696
// Visit tags
9797
if (tags != null)
9898
{
99-
for (var i = 0; i < tags.Count; i++)
99+
var referencesAsArray = tags.ToArray();
100+
for (var i = 0; i < referencesAsArray.Length; i++)
100101
{
101-
Walk(i.ToString(), () => Walk(tags[i]));
102+
Walk(i.ToString(), () => Walk(referencesAsArray[i]));
102103
}
103104
}
104105
}

test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -678,18 +678,18 @@ public static OpenApiDocument CreateOpenApiDocument()
678678
}
679679
}
680680
};
681-
document.Paths[getTeamsActivityByPeriodPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("reports.Functions", document));
682-
document.Paths[getTeamsActivityByDatePath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("reports.Functions", document));
683-
document.Paths[usersPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("users.user", document));
684-
document.Paths[usersByIdPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("users.user", document));
685-
document.Paths[usersByIdPath].Operations[OperationType.Patch].Tags!.Add(new OpenApiTagReference("users.user", document));
686-
document.Paths[messagesByIdPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("users.message", document));
687-
document.Paths[administrativeUnitRestorePath].Operations[OperationType.Post].Tags!.Add(new OpenApiTagReference("administrativeUnits.Actions", document));
688-
document.Paths[logoPath].Operations[OperationType.Put].Tags!.Add(new OpenApiTagReference("applications.application", document));
689-
document.Paths[securityProfilesPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("security.hostSecurityProfile", document));
690-
document.Paths[communicationsCallsKeepAlivePath].Operations[OperationType.Post].Tags!.Add(new OpenApiTagReference("communications.Actions", document));
691-
document.Paths[eventsDeltaPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("groups.Functions", document));
692-
document.Paths[refPath].Operations[OperationType.Get].Tags!.Add(new OpenApiTagReference("applications.directoryObject", document));
681+
document.Paths[getTeamsActivityByPeriodPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("reports.Functions", document)};
682+
document.Paths[getTeamsActivityByDatePath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("reports.Functions", document)};
683+
document.Paths[usersPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("users.user", document)};
684+
document.Paths[usersByIdPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("users.user", document)};
685+
document.Paths[usersByIdPath].Operations[OperationType.Patch].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("users.user", document)};
686+
document.Paths[messagesByIdPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("users.message", document)};
687+
document.Paths[administrativeUnitRestorePath].Operations[OperationType.Post].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("administrativeUnits.Actions", document)};
688+
document.Paths[logoPath].Operations[OperationType.Put].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("applications.application", document)};
689+
document.Paths[securityProfilesPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("security.hostSecurityProfile", document)};
690+
document.Paths[communicationsCallsKeepAlivePath].Operations[OperationType.Post].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("communications.Actions", document)};
691+
document.Paths[eventsDeltaPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("groups.Functions", document)};
692+
document.Paths[refPath].Operations[OperationType.Get].Tags = new HashSet<OpenApiTagReference> {new OpenApiTagReference("applications.directoryObject", document)};
693693
((OpenApiSchema)document.Paths[usersPath].Operations[OperationType.Get].Responses!["200"].Content[applicationJsonMediaType].Schema!.Properties["value"]).Items = new OpenApiSchemaReference("microsoft.graph.user", document);
694694
document.Paths[usersByIdPath].Operations[OperationType.Get].Responses!["200"].Content[applicationJsonMediaType].Schema = new OpenApiSchemaReference("microsoft.graph.user", document);
695695
document.Paths[messagesByIdPath].Operations[OperationType.Get].Responses!["200"].Content[applicationJsonMediaType].Schema = new OpenApiSchemaReference("microsoft.graph.message", document);

0 commit comments

Comments
 (0)