diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 9d7db69f9..534c8ff8b 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.1.2"
+ ".": "3.1.3"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed99a1173..44e6eba95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,15 @@
# Changelog
-## [3.1.2](https://github.com/microsoft/OpenAPI.NET/compare/v3.1.1...v3.1.2) (2026-01-06)
+## [3.1.3](https://github.com/microsoft/OpenAPI.NET/compare/v3.1.2...v3.1.3) (2026-01-16)
+### Bug Fixes
+
+* Support custom tag ordering ([008576c](https://github.com/microsoft/OpenAPI.NET/commit/008576c31f8dcecf59363c9c2f85d691601faa73))
+* Support custom tag ordering ([7610d07](https://github.com/microsoft/OpenAPI.NET/commit/7610d07bd11a99a4ce1cc31338d180dbd764d952))
+
+## [3.1.2](https://github.com/microsoft/OpenAPI.NET/compare/v3.1.1...v3.1.2) (2026-01-06)
+
### Bug Fixes
* correct error pointer when extension parser throws OpenApiException ([43c75a9](https://github.com/microsoft/OpenAPI.NET/commit/43c75a90746344fcc975611100e995fe4045edf0))
@@ -57,6 +64,21 @@
* adds support for OpenAPI 3.2.0 ([765a8dd](https://github.com/microsoft/OpenAPI.NET/commit/765a8dd4d6efd1a31b6a76d282ccffa5877a845a))
+## [2.4.3](https://github.com/microsoft/OpenAPI.NET/compare/v2.4.2...v2.4.3) (2026-01-16)
+
+## Bug Fixes
+
+* Support custom tag ordering ([008576c](https://github.com/microsoft/OpenAPI.NET/commit/008576c31f8dcecf59363c9c2f85d691601faa73))
+* Support custom tag ordering ([7610d07](https://github.com/microsoft/OpenAPI.NET/commit/7610d07bd11a99a4ce1cc31338d180dbd764d952))
+
+## [2.4.2](https://github.com/microsoft/OpenAPI.NET/compare/v2.4.1...v2.4.2) (2025-12-22)
+
+
+### Bug Fixes
+
+* wrap extension parser calls in try-catch to ensure correct error pointers ([63cf4a3](https://github.com/microsoft/OpenAPI.NET/commit/63cf4a3029fe2d285a6fe2724e30ffcf3bdd2f9f))
+* wrap extension parser calls in try-catch to ensure correct error pointers ([458cabe](https://github.com/microsoft/OpenAPI.NET/commit/458cabe6cd0fbdb192dbd17f2d6ab3b8162d1166))
+
## [2.4.1](https://github.com/microsoft/OpenAPI.NET/compare/v2.4.0...v2.4.1) (2025-12-18)
### Bug Fixes
diff --git a/Directory.Build.props b/Directory.Build.props
index c1b6e63d1..b0681cf72 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,7 +12,7 @@
https://github.com/Microsoft/OpenAPI.NET
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
- 3.1.2
+ 3.1.3
diff --git a/global.json b/global.json
index 3d946171e..4ca757b0e 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "8.0.416"
+ "version": "8.0.417"
}
}
\ No newline at end of file
diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj
index aab8f01bb..248caf5fa 100644
--- a/performance/resultsComparer/resultsComparer.csproj
+++ b/performance/resultsComparer/resultsComparer.csproj
@@ -8,12 +8,12 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
index 458972ef0..2e955f3a6 100644
--- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
+++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
@@ -29,10 +29,10 @@
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
index c7af543df..96973a701 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
@@ -3,6 +3,9 @@
using System;
using System.Collections.Generic;
+#if NET
+using System.Collections.Immutable;
+#endif
using System.IO;
using System.Linq;
using System.Security.Cryptography;
@@ -88,9 +91,15 @@ public ISet? Tags
{
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),
+ };
}
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
index a49f77ebd..95365132f 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
+++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
index 6913fdd97..8792e4404 100644
--- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
+++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
index f62407b7b..1acd8ce8c 100644
--- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
@@ -3,8 +3,11 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using VerifyXunit;
@@ -2126,6 +2129,79 @@ public void DeduplicatesTags()
Assert.Contains(document.Tags, t => t.Name == "tag2");
}
+ [Fact]
+ public void TagsSupportsCustomComparer()
+ {
+ var document = new OpenApiDocument
+ {
+ Tags = new HashSet(new CaseInsensitiveOpenApiTagEqualityComparer()),
+ };
+
+ Assert.True(document.Tags.Add(new OpenApiTag { Name = "Tag1" }));
+ Assert.False(document.Tags.Add(new OpenApiTag { Name = "tag1" }));
+ Assert.True(document.Tags.Add(new OpenApiTag { Name = "tag2" }));
+ Assert.False(document.Tags.Add(new OpenApiTag { Name = "TAG1" }));
+ Assert.Equal(2, document.Tags.Count);
+ }
+
+ private sealed class CaseInsensitiveOpenApiTagEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(OpenApiTag x, OpenApiTag y)
+ {
+ return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public int GetHashCode([DisallowNull] OpenApiTag obj)
+ {
+ return obj.Name.GetHashCode(StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ [Fact]
+ public void TagsSupportsSortedSets()
+ {
+ var document = new OpenApiDocument
+ {
+ Tags = new SortedSet(new DescendingOpenApiTagComparer())
+ {
+ new OpenApiTag { Name = "tagB" },
+ new OpenApiTag { Name = "tagA" },
+ new OpenApiTag { Name = "tagC" },
+ }
+ };
+
+ var names = document.Tags.Select(t => t.Name);
+ Assert.Equal(["tagC", "tagB", "tagA"], names);
+ Assert.IsType>(document.Tags);
+ }
+
+ private sealed class DescendingOpenApiTagComparer : IComparer
+ {
+ public int Compare(OpenApiTag x, OpenApiTag y)
+ {
+ return string.Compare(y?.Name, x?.Name, StringComparison.Ordinal);
+ }
+ }
+
+ [Fact]
+ public void TagsSupportsImmutableSortedSets()
+ {
+ var document = new OpenApiDocument
+ {
+ Tags = ImmutableSortedSet.Create(
+ new DescendingOpenApiTagComparer(),
+ [
+ new OpenApiTag { Name = "tagB" },
+ new OpenApiTag { Name = "tagA" },
+ new OpenApiTag { Name = "tagC" },
+ ]),
+ };
+
+ var names = document.Tags.Select(t => t.Name);
+ Assert.Equal(["tagC", "tagB", "tagA"], names);
+ Assert.IsType>(document.Tags);
+ }
+
public static TheoryData OpenApiSpecVersions()
{
var values = new TheoryData();