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