Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0ff19f8
draft: removes static registry for readers
baywet Feb 24, 2025
fe7a2fd
fix: removes static readers registry
baywet Feb 24, 2025
e29c01d
chore: default registration of json reader
baywet Feb 25, 2025
a8c7e16
chore: adds missing yaml reader for test
baywet Feb 25, 2025
75e9d9a
chore: updates public export of API
baywet Feb 25, 2025
243a111
fix: adds missing cancellation parameter to async method
baywet Feb 25, 2025
01398f0
chore: adds missing yaml reader to settings for base tests
baywet Feb 25, 2025
f8a775b
chore: adds missing yaml reader for readers tests
baywet Feb 25, 2025
0a819b4
chore; updates public api surface for missing cancellation token para…
baywet Feb 25, 2025
cea8929
chore: fixes potential NRT
baywet Feb 25, 2025
9b910f3
fix: moves the http client for the reader to settings so it can be pa…
baywet Feb 25, 2025
205fec1
chore: changes visibility of client getter to avoid it being used for…
baywet Feb 25, 2025
f257ad5
fix avoid creating a client for each request in hidi
baywet Feb 25, 2025
0f23798
fix: avoid creating new http clients to load additional documents of …
baywet Feb 25, 2025
f260e58
chore: updates public api surface export
baywet Feb 25, 2025
9386fae
fix: use a single http client in hidi
baywet Feb 25, 2025
9d5787e
Merge branch 'main' into fix/static-factories
baywet Feb 26, 2025
c632305
chore: use try add instead of add
baywet Feb 26, 2025
31bfaac
chore: adds tests on readers settings
baywet Feb 26, 2025
ea6e99b
chore: use the implemented operator
baywet Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions src/Microsoft.OpenApi.Hidi/OpenApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ namespace Microsoft.OpenApi.Hidi
{
internal static class OpenApiService
{
static OpenApiService()
{
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yml, new OpenApiYamlReader());
}

/// <summary>
/// Implementation of the transform command
/// </summary>
Expand Down Expand Up @@ -392,8 +386,10 @@ private static async Task<ReadResult> ParseOpenApiAsync(string openApiFile, bool
LoadExternalRefs = inlineExternal,
BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ?
new(openApiFile) :
new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar)
new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar),
HttpClient = httpClient.Value
};
settings.AddYamlReader();

result = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -497,6 +493,11 @@ private static Dictionary<string, List<string>> EnumerateJsonDocument(JsonElemen
return paths;
}

private static readonly Lazy<HttpClient> httpClient = new(() => new HttpClient()
{
DefaultRequestVersion = HttpVersion.Version20
});

/// <summary>
/// Reads stream from file system or makes HTTP request depending on the input string
/// </summary>
Expand All @@ -512,11 +513,7 @@ private static async Task<Stream> GetStreamAsync(string input, ILogger logger, C
{
try
{
using var httpClient = new HttpClient
{
DefaultRequestVersion = HttpVersion.Version20
};
stream = await httpClient.GetStreamAsync(new Uri(input), cancellationToken).ConfigureAwait(false);
stream = await httpClient.Value.GetStreamAsync(new Uri(input), cancellationToken).ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;

namespace Microsoft.OpenApi.Reader;

/// <summary>
/// Extensions for <see cref="OpenApiReaderSettings"/>
/// </summary>
public static class OpenApiReaderSettingsExtensions
{
/// <summary>
/// Adds a reader for the specified format
/// </summary>
/// <param name="settings">The settings to add the reader to.</param>
public static void AddYamlReader(this OpenApiReaderSettings settings)
{
var yamlReader = new OpenApiYamlReader();
settings.TryAddReader(OpenApiConstants.Yaml, yamlReader);
settings.TryAddReader(OpenApiConstants.Yml, yamlReader);
}
}
4 changes: 1 addition & 3 deletions src/Microsoft.OpenApi.Workbench/MainModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,6 @@ protected void OnPropertyChanged(string propertyName)
/// </summary>
internal async Task ParseDocumentAsync()
{
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yml, new OpenApiYamlReader());

Stream stream = null;
try
{
Expand All @@ -238,6 +235,7 @@ internal async Task ParseDocumentAsync()
{
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
};
settings.AddYamlReader();
if (ResolveExternal && !string.IsNullOrWhiteSpace(_inputFile))
{
settings.BaseUrl = _inputFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? new(_inputFile)
Expand Down
5 changes: 3 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
/// <summary>
/// Serialize <see cref="OpenApiDocument"/> to OpenAPI object V2.0.
/// </summary>
public void SerializeAsV2(IOpenApiWriter writer)

Check warning on line 242 in src/Microsoft.OpenApi/Models/OpenApiDocument.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 30 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 242 in src/Microsoft.OpenApi/Models/OpenApiDocument.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this method to reduce its Cognitive Complexity from 30 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
Utils.CheckArgumentNull(writer);

Expand Down Expand Up @@ -533,7 +533,7 @@
}
else
{
string relativePath = OpenApiConstants.ComponentsSegment + reference.Type.GetDisplayName() + "/" + reference.Id;

Check warning on line 536 in src/Microsoft.OpenApi/Models/OpenApiDocument.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this hardcoded path-delimiter. (https://rules.sonarsource.com/csharp/RSPEC-1075)

Check warning on line 536 in src/Microsoft.OpenApi/Models/OpenApiDocument.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this hardcoded path-delimiter. (https://rules.sonarsource.com/csharp/RSPEC-1075)

uriLocation = useExternal
? Workspace?.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath
Expand Down Expand Up @@ -562,10 +562,11 @@
/// </summary>
/// <param name="url"> The path to the OpenAPI file.</param>
/// <param name="settings">The OpenApi reader settings.</param>
/// <param name="token">The cancellation token</param>
/// <returns></returns>
public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings? settings = null)
public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings? settings = null, CancellationToken token = default)
{
return await OpenApiModelFactory.LoadAsync(url, settings);
return await OpenApiModelFactory.LoadAsync(url, settings, token).ConfigureAwait(false);
}

/// <summary>
Expand Down
37 changes: 17 additions & 20 deletions src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security;
using System.Text;
using System.Threading;
Expand All @@ -21,13 +20,6 @@
/// </summary>
public static class OpenApiModelFactory
{
private static readonly HttpClient _httpClient = new();

static OpenApiModelFactory()
{
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Json, new OpenApiJsonReader());
}

/// <summary>
/// Loads the input stream and parses it into an Open API document.
/// </summary>
Expand Down Expand Up @@ -73,19 +65,21 @@
public static T Load<T>(MemoryStream input, OpenApiSpecVersion version, string format, OpenApiDocument openApiDocument, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings settings = null) where T : IOpenApiElement
{
format ??= InspectStreamFormat(input);
return OpenApiReaderRegistry.GetReader(format).ReadFragment<T>(input, version, openApiDocument, out diagnostic, settings);
settings ??= DefaultReaderSettings.Value;
return settings.GetReader(format).ReadFragment<T>(input, version, openApiDocument, out diagnostic, settings);
}

/// <summary>
/// Loads the input URL and parses it into an Open API document.
/// </summary>
/// <param name="url">The path to the OpenAPI file</param>
/// <param name="settings"> The OpenApi reader settings.</param>
/// <param name="token"></param>
/// <param name="token">The cancellation token</param>
/// <returns></returns>
public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings settings = null, CancellationToken token = default)
{
var (stream, format) = await RetrieveStreamAndFormatAsync(url, token).ConfigureAwait(false);
settings ??= DefaultReaderSettings.Value;
var (stream, format) = await RetrieveStreamAndFormatAsync(url, settings, token).ConfigureAwait(false);
return await LoadAsync(stream, format, settings, token).ConfigureAwait(false);
}

Expand All @@ -102,7 +96,8 @@
/// <returns>The OpenAPI element.</returns>
public static async Task<T> LoadAsync<T>(string url, OpenApiSpecVersion version, OpenApiDocument openApiDocument, OpenApiReaderSettings settings = null, CancellationToken token = default) where T : IOpenApiElement
{
var (stream, format) = await RetrieveStreamAndFormatAsync(url, token).ConfigureAwait(false);
settings ??= DefaultReaderSettings.Value;
var (stream, format) = await RetrieveStreamAndFormatAsync(url, settings, token).ConfigureAwait(false);
return await LoadAsync<T>(stream, version, openApiDocument, format, settings, token);
}

Expand Down Expand Up @@ -239,14 +234,15 @@
return Load<T>(stream, version, format, openApiDocument, out diagnostic, settings);
}

private static readonly OpenApiReaderSettings DefaultReaderSettings = new();
private static readonly Lazy<OpenApiReaderSettings> DefaultReaderSettings = new(() => new OpenApiReaderSettings());

private static async Task<ReadResult> InternalLoadAsync(Stream input, string format, OpenApiReaderSettings settings, CancellationToken cancellationToken = default)
{
var reader = OpenApiReaderRegistry.GetReader(format);
settings ??= DefaultReaderSettings.Value;
var reader = settings.GetReader(format);
var readResult = await reader.ReadAsync(input, settings, cancellationToken).ConfigureAwait(false);

if (settings?.LoadExternalRefs ?? DefaultReaderSettings.LoadExternalRefs)
if (settings.LoadExternalRefs)
{
var diagnosticExternalRefs = await LoadExternalRefsAsync(readResult.Document, settings, format, cancellationToken).ConfigureAwait(false);
// Merge diagnostics of external reference
Expand All @@ -267,14 +263,15 @@
var openApiWorkSpace = new OpenApiWorkspace(baseUrl);

// Load this root document into the workspace
var streamLoader = new DefaultStreamLoader(settings.BaseUrl);
var streamLoader = new DefaultStreamLoader(settings.BaseUrl, settings.HttpClient);
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, settings.CustomExternalLoader ?? streamLoader, settings);
return await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document, format ?? OpenApiConstants.Json, null, token).ConfigureAwait(false);
}

private static ReadResult InternalLoad(MemoryStream input, string format, OpenApiReaderSettings settings)
{
if (settings?.LoadExternalRefs ?? DefaultReaderSettings.LoadExternalRefs)
settings ??= DefaultReaderSettings.Value;
if (settings.LoadExternalRefs)
{
throw new InvalidOperationException("Loading external references are not supported when using synchronous methods.");
}
Expand All @@ -283,12 +280,12 @@
throw new ArgumentException($"Cannot parse the stream: {nameof(input)} is empty or contains no elements.");
}

var reader = OpenApiReaderRegistry.GetReader(format);
var reader = settings.GetReader(format);
var readResult = reader.Read(input, settings);
return readResult;
}

private static async Task<(Stream, string)> RetrieveStreamAndFormatAsync(string url, CancellationToken token = default)
private static async Task<(Stream, string)> RetrieveStreamAndFormatAsync(string url, OpenApiReaderSettings settings, CancellationToken token = default)
{
if (!string.IsNullOrEmpty(url))
{
Expand All @@ -298,10 +295,10 @@
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)
|| url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
var response = await _httpClient.GetAsync(url, token).ConfigureAwait(false);
var response = await settings.HttpClient.GetAsync(url, token).ConfigureAwait(false);
var mediaType = response.Content.Headers.ContentType.MediaType;
var contentType = mediaType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0];
format = contentType.Split('/').Last().Split('+').Last().Split('-').Last();

Check warning on line 301 in src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)

Check warning on line 301 in src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)

Check warning on line 301 in src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)

Check warning on line 301 in src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)

Check warning on line 301 in src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)

Check warning on line 301 in src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

View workflow job for this annotation

GitHub Actions / Build

Indexing at Count-1 should be used instead of the "Enumerable" extension method "Last" (https://rules.sonarsource.com/csharp/RSPEC-6608)
// for non-standard MIME types e.g. text/x-yaml used in older libs or apps
#if NETSTANDARD2_0
stream = await response.Content.ReadAsStreamAsync();
Expand Down
48 changes: 0 additions & 48 deletions src/Microsoft.OpenApi/Reader/OpenApiReaderRegistry.cs

This file was deleted.

Loading