Skip to content

Commit 9cfcdd2

Browse files
baywetaustinaviv
andauthored
fix: support reinitializing OpenApiOperation.Tags to null and custom comparers (#3116) (#3142)
* fix: preserve custom tag comparers and sorted sets in OpenApiOperation copy constructor Align the OpenApiOperation copy constructor with OpenApiOperation.Tags setter to preserve custom equality comparers, sorted sets, and immutable sorted sets, and add corresponding copy constructor unit tests. * refactor: deduplicate tag normalization logic into static helper Extract CopyTags static method to deduplicate tag normalization logic between OpenApiOperation.Tags setter and the copy constructor as requested in review. Co-authored-by: Austin Aviv <abhikr6714@gmail.com>
1 parent 295c89c commit 9cfcdd2

2 files changed

Lines changed: 71 additions & 14 deletions

File tree

‎src/Microsoft.OpenApi/Models/OpenApiOperation.cs‎

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,22 @@ public ISet<OpenApiTagReference>? Tags
2828
}
2929
set
3030
{
31-
if (value is null)
32-
{
33-
_tags = null;
34-
return;
35-
}
36-
_tags = value switch
37-
{
38-
HashSet<OpenApiTagReference> tags when !ReferenceEquals(tags.Comparer, EqualityComparer<OpenApiTagReference>.Default) => value,
39-
SortedSet<OpenApiTagReference> => value,
31+
_tags = CopyTags(value);
32+
}
33+
}
34+
35+
private static ISet<OpenApiTagReference>? CopyTags(ISet<OpenApiTagReference>? tags, bool copy = false)
36+
{
37+
return tags switch
38+
{
39+
null => null,
40+
HashSet<OpenApiTagReference> hashSet when !ReferenceEquals(hashSet.Comparer, EqualityComparer<OpenApiTagReference>.Default) => copy ? new HashSet<OpenApiTagReference>(hashSet, hashSet.Comparer) : hashSet,
41+
SortedSet<OpenApiTagReference> sorted => copy ? new SortedSet<OpenApiTagReference>(sorted, sorted.Comparer) : sorted,
4042
#if NET
41-
ImmutableSortedSet<OpenApiTagReference> => value,
43+
ImmutableSortedSet<OpenApiTagReference> => tags,
4244
#endif
43-
_ => new HashSet<OpenApiTagReference>(value, OpenApiTagComparer.Instance),
44-
};
45-
}
45+
_ => new HashSet<OpenApiTagReference>(tags, OpenApiTagComparer.Instance),
46+
};
4647
}
4748

4849
/// <summary>
@@ -139,7 +140,7 @@ public OpenApiOperation() { }
139140
public OpenApiOperation(OpenApiOperation operation)
140141
{
141142
Utils.CheckArgumentNull(operation);
142-
Tags = operation.Tags != null ? new HashSet<OpenApiTagReference>(operation.Tags) : null;
143+
Tags = CopyTags(operation.Tags, copy: true);
143144
Summary = operation.Summary ?? Summary;
144145
Description = operation.Description ?? Description;
145146
ExternalDocs = operation.ExternalDocs != null ? new(operation.ExternalDocs) : null;

‎test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,5 +939,61 @@ public void TagsSupportsImmutableSortedSets()
939939
Assert.Equal(["tagC", "tagB", "tagA"], names);
940940
Assert.IsType<ImmutableSortedSet<OpenApiTagReference>>(operation.Tags);
941941
}
942+
943+
[Fact]
944+
public void OpenApiOperationCopyConstructorPreservesCustomComparer()
945+
{
946+
var operation = new OpenApiOperation
947+
{
948+
Tags = new HashSet<OpenApiTagReference>(new CaseInsensitiveOpenApiTagReferenceEqualityComparer())
949+
{
950+
new("Tag1")
951+
}
952+
};
953+
954+
var copy = new OpenApiOperation(operation);
955+
Assert.NotNull(copy.Tags);
956+
Assert.False(copy.Tags.Add(new OpenApiTagReference("tag1")));
957+
Assert.Single(copy.Tags);
958+
}
959+
960+
[Fact]
961+
public void OpenApiOperationCopyConstructorPreservesSortedSet()
962+
{
963+
var operation = new OpenApiOperation
964+
{
965+
Tags = new SortedSet<OpenApiTagReference>(new DescendingOpenApiTagReferenceComparer())
966+
{
967+
new("tagB"),
968+
new("tagA"),
969+
new("tagC"),
970+
}
971+
};
972+
973+
var copy = new OpenApiOperation(operation);
974+
Assert.IsType<SortedSet<OpenApiTagReference>>(copy.Tags);
975+
var copyNames = copy.Tags.Select(t => t.Name);
976+
Assert.Equal(["tagC", "tagB", "tagA"], copyNames);
977+
}
978+
979+
[Fact]
980+
public void OpenApiOperationCopyConstructorPreservesImmutableSortedSet()
981+
{
982+
var operation = new OpenApiOperation
983+
{
984+
Tags = ImmutableSortedSet.Create(
985+
new DescendingOpenApiTagReferenceComparer(),
986+
[
987+
new OpenApiTagReference("tagB"),
988+
new OpenApiTagReference("tagA"),
989+
new OpenApiTagReference("tagC"),
990+
]),
991+
};
992+
993+
var copy = new OpenApiOperation(operation);
994+
Assert.IsType<ImmutableSortedSet<OpenApiTagReference>>(copy.Tags);
995+
var copyNames = copy.Tags.Select(t => t.Name);
996+
Assert.Equal(["tagC", "tagB", "tagA"], copyNames);
997+
}
942998
}
943999
}

0 commit comments

Comments
 (0)