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..ef00c496f 100644
--- a/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs
+++ b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs
@@ -4,6 +4,8 @@
using System;
using System.IO;
using System.Net.Http;
+using System.Runtime.InteropServices;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
@@ -27,28 +29,29 @@ 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)
- : 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) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) =>
+ 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":
- return await _httpClient.GetStreamAsync(absoluteUri);
- default:
- throw new ArgumentException("Unsupported scheme");
- }
+ "file" => File.OpenRead(absoluteUri.AbsolutePath),
+ "http" or "https" =>
+#if NET5_0_OR_GREATER
+ await _httpClient.GetStreamAsync(absoluteUri, cancellationToken).ConfigureAwait(false),
+#else
+ await _httpClient.GetStreamAsync(absoluteUri).ConfigureAwait(false),
+#endif
+ _ => 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
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