diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml
index 8921e7112..53b181b22 100644
--- a/.azure-pipelines/ci-build.yml
+++ b/.azure-pipelines/ci-build.yml
@@ -472,4 +472,8 @@ extends:
-t "$(REGISTRY)/$(IMAGE_NAME):${VERSION}" \
"$(Pipeline.Workspace)"
displayName: 'Build and Push Release Image'
- condition: contains(variables['Build.SourceBranch'], 'refs/tags/v')
\ No newline at end of file
+ condition: contains(variables['Build.SourceBranch'], 'refs/tags/v')
+
+# once the nuget has been released, fill this form to get the public documentation updated.
+# https://dev.azure.com/msft-skilling/Content/_workitems/create/User%20Story?templateId=39fb91e3-64a2-4c8a-83db-b2bdf3603dd3&ownerId=c4a28f90-17ae-4384-b514-7273392b082b
+# https://learn.microsoft.com/en-us/dotnet/api/microsoft.openapi
diff --git a/.github/workflows/auto-merge-dependabot.yml b/.github/workflows/auto-merge-dependabot.yml
index d454cd186..ba2243c97 100644
--- a/.github/workflows/auto-merge-dependabot.yml
+++ b/.github/workflows/auto-merge-dependabot.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
- uses: dependabot/fetch-metadata@v2.4.0
+ uses: dependabot/fetch-metadata@v2.5.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 89c23baca..9d7db69f9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.1.1"
+ ".": "3.1.2"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be4068a9a..ed99a1173 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## [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))
+* wrap extension parser calls in try-catch to ensure correct error pointers ([50b44aa](https://github.com/microsoft/OpenAPI.NET/commit/50b44aad12d8fa86a46f7f6d62896a7b1c344d2e))
+
## [3.1.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.1.0...v3.1.1) (2025-12-18)
diff --git a/Directory.Build.props b/Directory.Build.props
index 5aba3b052..c1b6e63d1 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.1
+ 3.1.2
diff --git a/docs/upgrade-guide-3.md b/docs/upgrade-guide-3.md
index e874a8998..49e467d24 100644
--- a/docs/upgrade-guide-3.md
+++ b/docs/upgrade-guide-3.md
@@ -11,11 +11,11 @@ We are excited to announce OpenAPI.NET v3.0! This major update introduces suppor
If you are using this library with [AspNetCore OpenAPI versions](https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi) `< 10.0` then you must remain on version `1.x` as it's not compatible with version 3 or above of this library.
-If you are using this library with [AspNetCore OpenAPI versions](https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi) `= 10.0` then you must remain on version `2.x` as it's not compatible with version 3 or above of this library.
+If you are using this library with [AspNetCore OpenAPI versions](https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi) `= 10.X` then you must remain on version `2.x` as it's not compatible with version 3 or above of this library.
If you are using this library with [Swashbuckle.AspNetCore version](https://www.nuget.org/packages/Swashbuckle.AspNetCore/) `< 10.0` then you must remain on version `1.x` as it's not compatible with version 3 or above of this library.
-If you are using this library with [Swashbuckle.AspNetCore version](https://www.nuget.org/packages/Swashbuckle.AspNetCore/) `= 10.0` then you must remain on version `2.x` as it's not compatible with version 3 or above of this library.
+If you are using this library with [Swashbuckle.AspNetCore version](https://www.nuget.org/packages/Swashbuckle.AspNetCore/) `= 10.X` then you must remain on version `2.x` as it's not compatible with version 3 or above of this library.
The latest support policy information for this library, and integration with ASP.NET Core can be found on [the contributing documentation](https://github.com/microsoft/OpenAPI.NET/blob/main/CONTRIBUTING.md#branches-and-support-policy).
diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs
index 660fcc5ed..08bfbd001 100644
--- a/src/Microsoft.OpenApi.Workbench/MainModel.cs
+++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs
@@ -42,7 +42,7 @@ public class MainModel : INotifyPropertyChanged
///
/// Default version.
///
- private OpenApiSpecVersion _version = OpenApiSpecVersion.OpenApi3_0;
+ private OpenApiSpecVersion _version = OpenApiSpecVersion.OpenApi3_2;
private static readonly HttpClient _httpClient = new();
diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml
index 32c14659d..87e219796 100644
--- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml
+++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml
@@ -40,8 +40,9 @@
-
-
+
+
+
diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs
index c640b310c..80d079b5e 100644
--- a/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs
@@ -79,12 +79,18 @@ private static IOpenApiExtension LoadExtension(string name, ParseNode node)
{
if (node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser))
{
- return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi2_0);
- }
- else
- {
- return new JsonNodeExtension(node.CreateAny());
+ try
+ {
+ return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi2_0);
+ }
+ catch (OpenApiException ex)
+ {
+ ex.Pointer = node.Context.GetLocation();
+ node.Context.Diagnostic.Errors.Add(new(ex));
+ }
}
+
+ return new JsonNodeExtension(node.CreateAny());
}
private static string? LoadString(ParseNode node)
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs
index 0b74cedc5..1a03268d6 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs
@@ -130,15 +130,24 @@ public static JsonNodeExtension LoadAny(ParseNode node, OpenApiDocument hostDocu
private static IOpenApiExtension LoadExtension(string name, ParseNode node)
{
- if (node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser) && parser(
- node.CreateAny(), OpenApiSpecVersion.OpenApi3_0) is { } result)
+ if (node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser))
{
- return result;
- }
- else
- {
- return new JsonNodeExtension(node.CreateAny());
+ try
+ {
+ var result = parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_0);
+ if (result is { })
+ {
+ return result;
+ }
+ }
+ catch (OpenApiException ex)
+ {
+ ex.Pointer = node.Context.GetLocation();
+ node.Context.Diagnostic.Errors.Add(new(ex));
+ }
}
+
+ return new JsonNodeExtension(node.CreateAny());
}
private static string? LoadString(ParseNode node)
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
index 08f2ec048..3608ad5f7 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
@@ -131,9 +131,20 @@ public static JsonNode LoadAny(ParseNode node, OpenApiDocument hostDocument)
private static IOpenApiExtension LoadExtension(string name, ParseNode node)
{
- return node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser)
- ? parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_1)
- : new JsonNodeExtension(node.CreateAny());
+ if (node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser))
+ {
+ try
+ {
+ return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_1);
+ }
+ catch (OpenApiException ex)
+ {
+ ex.Pointer = node.Context.GetLocation();
+ node.Context.Diagnostic.Errors.Add(new(ex));
+ }
+ }
+
+ return new JsonNodeExtension(node.CreateAny());
}
private static string? LoadString(ParseNode node)
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs
index 9b062931d..55f45b38e 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs
@@ -131,9 +131,20 @@ public static JsonNode LoadAny(ParseNode node, OpenApiDocument hostDocument)
private static IOpenApiExtension LoadExtension(string name, ParseNode node)
{
- return node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser)
- ? parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_2)
- : new JsonNodeExtension(node.CreateAny());
+ if (node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser))
+ {
+ try
+ {
+ return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_2);
+ }
+ catch (OpenApiException ex)
+ {
+ ex.Pointer = node.Context.GetLocation();
+ node.Context.Diagnostic.Errors.Add(new(ex));
+ }
+ }
+
+ return new JsonNodeExtension(node.CreateAny());
}
private static string? LoadString(ParseNode node)
diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs
index 57f55e95e..9f4404c05 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs
@@ -44,6 +44,81 @@ public void ParseCustomExtension()
Assert.Equal("hey", fooExtension.Bar);
Assert.Equal("hi!", fooExtension.Baz);
}
+
+ [Fact]
+ public void ExtensionParserThrowingOpenApiException_V2_ShouldHaveCorrectPointer()
+ {
+ var json = """
+{
+ "swagger": "2.0",
+ "info": {
+ "title": "Demo",
+ "version": "1"
+ },
+ "paths": {},
+ "definitions": {
+ "demo": {
+ "x-tag": null
+ }
+ }
+}
+""";
+ var settings = new OpenApiReaderSettings
+ {
+ ExtensionParsers =
+ {
+ { "x-tag", (any, version) => throw new OpenApiException("Testing") }
+ }
+ };
+
+ var result = OpenApiDocument.Parse(json, "json", settings);
+
+ Assert.NotNull(result.Diagnostic);
+ Assert.NotEmpty(result.Diagnostic.Errors);
+ var error = result.Diagnostic.Errors[0];
+ Assert.Equal("Testing", error.Message);
+ Assert.Equal("#/definitions/demo/x-tag", error.Pointer);
+ }
+
+ [Theory]
+ [InlineData("3.0.4")]
+ [InlineData("3.1.1")]
+ [InlineData("3.2.0")]
+ public void ExtensionParserThrowingOpenApiException_V3_ShouldHaveCorrectPointer(string version)
+ {
+ var json = $$"""
+{
+ "openapi": "{{version}}",
+ "info": {
+ "title": "Demo",
+ "version": "1"
+ },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "demo": {
+ "x-tag": null
+ }
+ }
+ }
+}
+""";
+ var settings = new OpenApiReaderSettings
+ {
+ ExtensionParsers =
+ {
+ { "x-tag", (any, version) => throw new OpenApiException("Testing") }
+ }
+ };
+
+ var result = OpenApiDocument.Parse(json, "json", settings);
+
+ Assert.NotNull(result.Diagnostic);
+ Assert.NotEmpty(result.Diagnostic.Errors);
+ var error = result.Diagnostic.Errors[0];
+ Assert.Equal("Testing", error.Message);
+ Assert.Equal("#/components/schemas/demo/x-tag", error.Pointer);
+ }
}
internal class FooExtension : IOpenApiExtension, IOpenApiElement