Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
7 changes: 1 addition & 6 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 @@ -394,6 +388,7 @@ private static async Task<ReadResult> ParseOpenApiAsync(string openApiFile, bool
new(openApiFile) :
new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar)
};
settings.AddYamlReader();

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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.AddReaderToSettings(OpenApiConstants.Yaml, yamlReader);
settings.AddReaderToSettings(OpenApiConstants.Yml, yamlReader);
}
private static void AddReaderToSettings(this OpenApiReaderSettings settings, string format, IOpenApiReader reader)
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET5_0_OR_GREATER
settings.Readers.Add(format, reader);
#else
if (!settings.Readers.ContainsKey(format))
{
settings.Readers.Add(format, reader);
}
#endif
}
}
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 @@ -222,7 +222,7 @@
/// <summary>
/// Serialize <see cref="OpenApiDocument"/> to OpenAPI object V2.0.
/// </summary>
public void SerializeAsV2(IOpenApiWriter writer)

Check warning on line 225 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 225 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 @@ -516,7 +516,7 @@
}
else
{
string relativePath = OpenApiConstants.ComponentsSegment + reference.Type.GetDisplayName() + "/" + reference.Id;

Check warning on line 519 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 519 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 @@ -545,10 +545,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
13 changes: 4 additions & 9 deletions src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
{
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,15 +68,15 @@
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);
return settings.Readers[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)
{
Expand Down Expand Up @@ -243,10 +238,10 @@

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

if (settings?.LoadExternalRefs ?? DefaultReaderSettings.LoadExternalRefs)

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

View workflow job for this annotation

GitHub Actions / Build

Remove this unnecessary check for null. (https://rules.sonarsource.com/csharp/RSPEC-2589)

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

View workflow job for this annotation

GitHub Actions / Build

Remove this unnecessary check for null. Some code paths are unreachable. (https://rules.sonarsource.com/csharp/RSPEC-2583)
{
var diagnosticExternalRefs = await LoadExternalRefsAsync(readResult.Document, settings, format, cancellationToken).ConfigureAwait(false);
// Merge diagnostics of external reference
Expand Down Expand Up @@ -283,7 +278,7 @@
throw new ArgumentException($"Cannot parse the stream: {nameof(input)} is empty or contains no elements.");
}

var reader = OpenApiReaderRegistry.GetReader(format);
var reader = settings.Readers[format];

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

View workflow job for this annotation

GitHub Actions / Build

'settings' is null on at least one execution path. (https://rules.sonarsource.com/csharp/RSPEC-2259)
var readResult = reader.Read(input, settings);
return readResult;
}
Expand All @@ -301,7 +296,7 @@
var response = await _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 299 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 299 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 299 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.

22 changes: 22 additions & 0 deletions src/Microsoft.OpenApi/Reader/OpenApiReaderSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.MicrosoftExtensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Validations;

namespace Microsoft.OpenApi.Reader
Expand All @@ -16,6 +17,27 @@ namespace Microsoft.OpenApi.Reader
/// </summary>
public class OpenApiReaderSettings
{
/// <summary>
/// Adds a reader for the specified format
/// </summary>
public void AddJsonReader()
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET5_0_OR_GREATER
Readers.TryAdd(OpenApiConstants.Json, new OpenApiJsonReader());
#else
if (!Readers.ContainsKey(OpenApiConstants.Json))
{
Readers.Add(OpenApiConstants.Json, new OpenApiJsonReader());
}
#endif
}
/// <summary>
/// Readers to use to parse the OpenAPI document
/// </summary>
public Dictionary<string, IOpenApiReader> Readers { get; init; } = new Dictionary<string, IOpenApiReader>(StringComparer.OrdinalIgnoreCase)
{
{ OpenApiConstants.Json, new OpenApiJsonReader() }
};
/// <summary>
/// When external references are found, load them into a shared workspace
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Services;
using Microsoft.OpenApi.Tests.UtilityFiles;
using Moq;
Expand Down Expand Up @@ -232,7 +233,9 @@ public async Task CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly(

// Act
using var stream = File.OpenRead(filePath);
var doc = (await OpenApiDocument.LoadAsync(stream, "yaml")).Document;
var settings = new OpenApiReaderSettings();
settings.AddYamlReader();
var doc = (await OpenApiDocument.LoadAsync(stream, "yaml", settings)).Document;
var predicate = OpenApiFilterService.CreatePredicate(operationIds: operationIds);
var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(doc, predicate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public sealed class OpenApiServiceTests : IDisposable
public OpenApiServiceTests()
{
_logger = new Logger<OpenApiServiceTests>(_loggerFactory);
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yml, new OpenApiYamlReader());
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,10 @@ namespace Microsoft.OpenApi.Readers.Tests.OpenApiReaderTests
[Collection("DefaultSettings")]
public class OpenApiDiagnosticTests
{
public OpenApiDiagnosticTests()
{
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
}

[Fact]
public async Task DetectedSpecificationVersionShouldBeV2_0()
{
var actual = await OpenApiDocument.LoadAsync("V2Tests/Samples/basic.v2.yaml");
var actual = await OpenApiDocument.LoadAsync("V2Tests/Samples/basic.v2.yaml", SettingsFixture.ReaderSettings);

Assert.NotNull(actual.Diagnostic);
Assert.Equal(OpenApiSpecVersion.OpenApi2_0, actual.Diagnostic.SpecificationVersion);
Expand All @@ -32,7 +27,7 @@ public async Task DetectedSpecificationVersionShouldBeV2_0()
[Fact]
public async Task DetectedSpecificationVersionShouldBeV3_0()
{
var actual = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml");
var actual = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml", SettingsFixture.ReaderSettings);

Assert.NotNull(actual.Diagnostic);
Assert.Equal(OpenApiSpecVersion.OpenApi3_0, actual.Diagnostic.SpecificationVersion);
Expand All @@ -48,6 +43,7 @@ public async Task DiagnosticReportMergedForExternalReferenceAsync()
CustomExternalLoader = new ResourceLoader(),
BaseUrl = new("fie://c:\\")
};
settings.AddYamlReader();

ReadResult result;
result = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml", settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@ public class OpenApiStreamReaderTests
{
private const string SampleFolderPath = "V3Tests/Samples/OpenApiDocument/";

public OpenApiStreamReaderTests()
{
OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader());
}

[Fact]
public async Task StreamShouldCloseIfLeaveStreamOpenSettingEqualsFalse()
{
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml"));
var settings = new OpenApiReaderSettings { LeaveStreamOpen = false };
settings.AddYamlReader();
_ = await OpenApiDocument.LoadAsync(stream, settings: settings);
Assert.False(stream.CanRead);
}
Expand All @@ -34,6 +30,7 @@ public async Task StreamShouldNotCloseIfLeaveStreamOpenSettingEqualsTrue()
{
using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml"));
var settings = new OpenApiReaderSettings { LeaveStreamOpen = true };
settings.AddYamlReader();
_ = await OpenApiDocument.LoadAsync(stream, settings: settings);
Assert.True(stream.CanRead);
}
Expand All @@ -48,7 +45,9 @@ public async Task StreamShouldNotBeDisposedIfLeaveStreamOpenSettingIsTrueAsync()
memoryStream.Position = 0;
var stream = memoryStream;

_ = await OpenApiDocument.LoadAsync(stream, settings: new OpenApiReaderSettings { LeaveStreamOpen = true });
var settings = new OpenApiReaderSettings { LeaveStreamOpen = true };
settings.AddYamlReader();
_ = await OpenApiDocument.LoadAsync(stream, settings: settings);
stream.Seek(0, SeekOrigin.Begin); // does not throw an object disposed exception
Assert.True(stream.CanRead);
}
Expand All @@ -64,7 +63,9 @@ public async Task StreamShouldReadWhenInitializedAsync()
var stream = await httpClient.GetStreamAsync("20fe7a7b720a0e48e5842d002ac418b12a8201df/tests/v3.0/pass/petstore.yaml");

// Read V3 as YAML
var result = await OpenApiDocument.LoadAsync(stream);
var settings = new OpenApiReaderSettings();
settings.AddYamlReader();
var result = await OpenApiDocument.LoadAsync(stream, settings: settings);
Assert.NotNull(result.Document);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public async Task ThrowOpenApiUnsupportedSpecVersionException()
{
try
{
_ = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/unsupported.v1.yaml");
_ = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/unsupported.v1.yaml", SettingsFixture.ReaderSettings);
}
catch (OpenApiUnsupportedSpecVersionException exception)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ namespace Microsoft.OpenApi.Readers.Tests.OpenApiWorkspaceTests
{
public class OpenApiWorkspaceStreamTests
{
private const string SampleFolderPath = "V3Tests/Samples/OpenApiWorkspace/";

public OpenApiWorkspaceStreamTests()
{
OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
}

// Use OpenApiWorkspace to load a document and a referenced document

[Fact]
Expand All @@ -30,6 +23,7 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW
CustomExternalLoader = new MockLoader(),
BaseUrl = new("file://c:\\")
};
settings.AddYamlReader();

var stream = new MemoryStream();
var doc = """
Expand Down Expand Up @@ -59,6 +53,7 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo
CustomExternalLoader = new ResourceLoader(),
BaseUrl = new("file://c:\\"),
};
settings.AddYamlReader();

ReadResult result;
result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings);
Expand Down
9 changes: 2 additions & 7 deletions test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ namespace Microsoft.OpenApi.Tests
{
public class ParseNodeTests
{
public ParseNodeTests()
{
OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader());
}

[Fact]
public void BrokenSimpleList()
{
Expand All @@ -30,7 +25,7 @@ public void BrokenSimpleList()
paths: { }
""";

var result = OpenApiDocument.Parse(input, "yaml");
var result = OpenApiDocument.Parse(input, "yaml", SettingsFixture.ReaderSettings);

Assert.Equivalent(new List<OpenApiError>() {
new OpenApiError(new OpenApiReaderException("Expected a value."))
Expand All @@ -56,7 +51,7 @@ public void BadSchema()
schema: asdasd
""";

var res= OpenApiDocument.Parse(input, "yaml");
var res= OpenApiDocument.Parse(input, "yaml", SettingsFixture.ReaderSettings);

Assert.Equivalent(new List<OpenApiError>
{
Expand Down
Loading
Loading