diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index ee1afcc0a..c9878d132 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +#if NET +using System.Collections.Immutable; +#endif using System.Linq; namespace Microsoft.OpenApi @@ -27,11 +30,18 @@ public ISet? Tags { if (value is null) { + _tags = null; return; } - _tags = value is HashSet tags && tags.Comparer is OpenApiTagComparer ? - tags : - new HashSet(value, OpenApiTagComparer.Instance); + _tags = value switch + { + HashSet tags when tags.Comparer != EqualityComparer.Default => value, + SortedSet => value, +#if NET + ImmutableSortedSet => value, +#endif + _ => new HashSet(value, OpenApiTagComparer.Instance), + }; } } @@ -129,7 +139,15 @@ public OpenApiOperation() { } public OpenApiOperation(OpenApiOperation operation) { Utils.CheckArgumentNull(operation); - Tags = operation.Tags != null ? new HashSet(operation.Tags) : null; + Tags = operation.Tags switch + { + HashSet tags when tags.Comparer != EqualityComparer.Default => new HashSet(tags, tags.Comparer), + SortedSet sorted => new SortedSet(sorted, sorted.Comparer), +#if NET + ImmutableSortedSet => operation.Tags, +#endif + _ => operation.Tags != null ? new HashSet(operation.Tags, OpenApiTagComparer.Instance) : null, + }; Summary = operation.Summary ?? Summary; Description = operation.Description ?? Description; ExternalDocs = operation.ExternalDocs != null ? new(operation.ExternalDocs) : null; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index d281f4437..b07526b6d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -851,5 +851,105 @@ public void OpenApiOperationCopyConstructorWithAnnotationsSucceeds() Assert.NotEqual(baseOperation.Metadata["key1"], actualOperation.Metadata["key1"]); } + + [Fact] + public void TagsCanBeReInitializedToNull() + { + var operation = new OpenApiOperation(); + Assert.Null(operation.Tags); + operation.Tags = new HashSet(); + Assert.NotNull(operation.Tags); + operation.Tags = null; + Assert.Null(operation.Tags); + } + + [Fact] + public void TagsSupportsCustomComparer() + { + var operation = new OpenApiOperation + { + Tags = new HashSet(new CaseInsensitiveOpenApiTagReferenceEqualityComparer()), + }; + + Assert.True(operation.Tags.Add(new OpenApiTagReference("Tag1"))); + Assert.False(operation.Tags.Add(new OpenApiTagReference("tag1"))); + Assert.True(operation.Tags.Add(new OpenApiTagReference("tag2"))); + Assert.False(operation.Tags.Add(new OpenApiTagReference("TAG1"))); + Assert.Equal(2, operation.Tags.Count); + } + + [Fact] + public void TagsSupportsSortedSet() + { + var operation = new OpenApiOperation + { + Tags = new SortedSet(new TagReferenceComparer()), + }; + + Assert.IsType>(operation.Tags); + } + + [Fact] + public void OpenApiOperationCopyConstructorPreservesCustomComparer() + { + var operation = new OpenApiOperation + { + Tags = new HashSet(new CaseInsensitiveOpenApiTagReferenceEqualityComparer()) + { + new OpenApiTagReference("Tag1") + } + }; + + var copy = new OpenApiOperation(operation); + Assert.NotNull(copy.Tags); + Assert.False(copy.Tags.Add(new OpenApiTagReference("tag1"))); + Assert.Single(copy.Tags); + } + + [Fact] + public void OpenApiOperationCopyConstructorPreservesSortedSet() + { + var comparer = Comparer.Create((x, y) => + string.Compare(y?.Name, x?.Name, System.StringComparison.Ordinal)); + var operation = new OpenApiOperation + { + Tags = new SortedSet(comparer) + { + new OpenApiTagReference("b"), + new OpenApiTagReference("a") + } + }; + + Assert.Collection(operation.Tags, + tag => Assert.Equal("b", tag.Name), + tag => Assert.Equal("a", tag.Name)); + + var copy = new OpenApiOperation(operation); + Assert.IsType>(copy.Tags); + Assert.Collection(copy.Tags, + tag => Assert.Equal("b", tag.Name), + tag => Assert.Equal("a", tag.Name)); + } + + private sealed class TagReferenceComparer : IComparer + { + public int Compare(OpenApiTagReference x, OpenApiTagReference y) + { + return string.Compare(x?.Name, y?.Name, System.StringComparison.Ordinal); + } + } + + private sealed class CaseInsensitiveOpenApiTagReferenceEqualityComparer : IEqualityComparer + { + public bool Equals(OpenApiTagReference x, OpenApiTagReference y) + { + return string.Equals(x?.Name, y?.Name, System.StringComparison.OrdinalIgnoreCase); + } + + public int GetHashCode(OpenApiTagReference obj) + { + return obj?.Name == null ? 0 : System.StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name); + } + } } }