From a5ffab1e77c19987fe468b9297e19e8f4c48f47a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 14 Feb 2025 16:01:45 -0500 Subject: [PATCH 1/4] fix: adds a cancellation token argument to external document loading --- .../Interfaces/IStreamLoader.cs | 4 +++- .../Reader/Services/DefaultStreamLoader.cs | 16 ++++++++-------- .../Reader/Services/OpenApiWorkspaceLoader.cs | 6 +++--- .../OpenApiReaderTests/OpenApiDiagnosticTests.cs | 3 ++- .../OpenApiWorkspaceStreamTests.cs | 5 +++-- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs b/src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs index c3cb9b256..e6438bac1 100644 --- a/src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs +++ b/src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Models; @@ -17,7 +18,8 @@ public interface IStreamLoader /// Use Uri to locate data and convert into an input object. /// /// Identifier of some source of an OpenAPI Description + /// The cancellation token. /// A data object that can be processed by a reader to generate an - Task LoadAsync(Uri uri); + Task LoadAsync(Uri uri, CancellationToken cancellationToken = default); } } diff --git a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs index bb230c4a9..64bb5b20e 100644 --- a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -27,13 +28,8 @@ public DefaultStreamLoader(Uri baseUrl) this.baseUrl = baseUrl; } - /// - /// Use Uri to locate data and convert into an input object. - /// - /// Identifier of some source of an OpenAPI Description - /// A data object that can be processed by a reader to generate an - /// - public async Task LoadAsync(Uri uri) + /// + public async Task LoadAsync(Uri uri, CancellationToken cancellationToken = default) { Uri absoluteUri; absoluteUri = baseUrl.AbsoluteUri.Equals(OpenApiConstants.BaseRegistryUri) ? new Uri(Directory.GetCurrentDirectory() + uri) @@ -45,7 +41,11 @@ public async Task LoadAsync(Uri uri) return File.OpenRead(absoluteUri.AbsolutePath); case "http": case "https": - return await _httpClient.GetStreamAsync(absoluteUri); +#if NET5_0_OR_GREATER + return await _httpClient.GetStreamAsync(absoluteUri, cancellationToken).ConfigureAwait(false); +#else + return await _httpClient.GetStreamAsync(absoluteUri).ConfigureAwait(false); +#endif default: throw new ArgumentException("Unsupported scheme"); } diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index 06231e75c..32090231f 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs @@ -45,8 +45,8 @@ internal async Task LoadAsync(OpenApiReference reference, // If not already in workspace, load it and process references if (!_workspace.Contains(item.ExternalResource)) { - var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); - var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); + var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute), cancellationToken).ConfigureAwait(false); + var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken).ConfigureAwait(false); // Merge diagnostics if (result.Diagnostic != null) { @@ -54,7 +54,7 @@ internal async Task LoadAsync(OpenApiReference reference, } if (result.Document != null) { - var loadDiagnostic = await LoadAsync(item, result.Document, format, diagnostic, cancellationToken); + var loadDiagnostic = await LoadAsync(item, result.Document, format, diagnostic, cancellationToken).ConfigureAwait(false); diagnostic = loadDiagnostic; } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 667bedbd1..5e065a1e8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Threading; using System.Threading.Tasks; using System; using Microsoft.OpenApi.Models; @@ -64,7 +65,7 @@ public Stream Load(Uri uri) return null; } - public Task LoadAsync(Uri uri) + public Task LoadAsync(Uri uri, CancellationToken cancellationToken = default) { var path = new Uri(new("http://example.org/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/"), uri).AbsolutePath; path = path[1..]; // remove leading slash diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index 68ecbe33e..a2badc7c8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -80,7 +81,7 @@ public Stream Load(Uri uri) return null; } - public Task LoadAsync(Uri uri) + public Task LoadAsync(Uri uri, CancellationToken cancellationToken = default) { return Task.FromResult(null); } @@ -93,7 +94,7 @@ public Stream Load(Uri uri) return null; } - public Task LoadAsync(Uri uri) + public Task LoadAsync(Uri uri, CancellationToken cancellationToken = default) { var path = new Uri(new("http://example.org/V3Tests/Samples/OpenApiWorkspace/"), uri).AbsolutePath; path = path[1..]; // remove leading slash From df99a00010001b35b34f9b74bbf12437f90b6b18 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 14 Feb 2025 16:26:43 -0500 Subject: [PATCH 2/4] fix: a bug where external reference loading for local files would not work on linux Signed-off-by: Vincent Biret --- .../Reader/Services/DefaultStreamLoader.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs index 64bb5b20e..017cc9c84 100644 --- a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs @@ -31,24 +31,26 @@ public DefaultStreamLoader(Uri baseUrl) /// public async Task LoadAsync(Uri uri, CancellationToken cancellationToken = default) { - Uri absoluteUri; - absoluteUri = baseUrl.AbsoluteUri.Equals(OpenApiConstants.BaseRegistryUri) ? new Uri(Directory.GetCurrentDirectory() + uri) - : new Uri(baseUrl, uri); + var absoluteUri = (baseUrl.AbsoluteUri.Equals(OpenApiConstants.BaseRegistryUri), baseUrl.IsAbsoluteUri, uri.IsAbsoluteUri) switch + { + (true, _, _) => new Uri(Path.Combine(Directory.GetCurrentDirectory(), uri.ToString())), + // this overcomes a URI concatenation issue for local paths on linux OSes + (_, true, false) when baseUrl.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase) => + new Uri(Path.Combine(baseUrl.AbsoluteUri, uri.ToString())), + (_, _, _) => new Uri(baseUrl, uri), + }; - switch (absoluteUri.Scheme) + return absoluteUri.Scheme switch { - case "file": - return File.OpenRead(absoluteUri.AbsolutePath); - case "http": - case "https": + "file" => File.OpenRead(absoluteUri.AbsolutePath), + "http" or "https" => #if NET5_0_OR_GREATER - return await _httpClient.GetStreamAsync(absoluteUri, cancellationToken).ConfigureAwait(false); + await _httpClient.GetStreamAsync(absoluteUri, cancellationToken).ConfigureAwait(false), #else - return await _httpClient.GetStreamAsync(absoluteUri).ConfigureAwait(false); + await _httpClient.GetStreamAsync(absoluteUri).ConfigureAwait(false), #endif - default: - throw new ArgumentException("Unsupported scheme"); - } + _ => throw new ArgumentException("Unsupported scheme"), + }; } } } From 1cc7c733ab66df85f217cfd41f610e367ecad47b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 14 Feb 2025 16:32:14 -0500 Subject: [PATCH 3/4] chore: makes the fix specific to non-windows Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs index 017cc9c84..ef00c496f 100644 --- a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Net.Http; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; @@ -35,7 +36,7 @@ public async Task LoadAsync(Uri uri, CancellationToken cancellationToken { (true, _, _) => new Uri(Path.Combine(Directory.GetCurrentDirectory(), uri.ToString())), // this overcomes a URI concatenation issue for local paths on linux OSes - (_, true, false) when baseUrl.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase) => + (_, true, false) when baseUrl.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) => new Uri(Path.Combine(baseUrl.AbsoluteUri, uri.ToString())), (_, _, _) => new Uri(baseUrl, uri), }; From 3afb8db7279e32f83ded36ae2c80c75477bf5588 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 14 Feb 2025 16:37:43 -0500 Subject: [PATCH 4/4] chore: updates public api surface --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 273407001..c1fb18800 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -246,7 +246,7 @@ namespace Microsoft.OpenApi.Interfaces } public interface IStreamLoader { - System.Threading.Tasks.Task LoadAsync(System.Uri uri); + System.Threading.Tasks.Task LoadAsync(System.Uri uri, System.Threading.CancellationToken cancellationToken = default); } } namespace Microsoft.OpenApi @@ -1554,7 +1554,7 @@ namespace Microsoft.OpenApi.Reader.Services public class DefaultStreamLoader : Microsoft.OpenApi.Interfaces.IStreamLoader { public DefaultStreamLoader(System.Uri baseUrl) { } - public System.Threading.Tasks.Task LoadAsync(System.Uri uri) { } + public System.Threading.Tasks.Task LoadAsync(System.Uri uri, System.Threading.CancellationToken cancellationToken = default) { } } } namespace Microsoft.OpenApi.Services