diff --git a/.release-please-manifest.json b/.release-please-manifest.json index adf5fd769..bd1ba1cb3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.4.1" + ".": "2.4.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7414944db..b7d9b73f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [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) diff --git a/Directory.Build.props b/Directory.Build.props index 2d0fc7255..a64e378aa 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 - 2.4.1 + 2.4.2 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/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs index 57f55e95e..96de89cf9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs @@ -44,6 +44,80 @@ 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")] + 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