Skip to content

Commit 31bfaac

Browse files
committed
chore: adds tests on readers settings
chore: adds method with explicit exception when adding readers Signed-off-by: Vincent Biret <[email protected]>
1 parent c632305 commit 31bfaac

File tree

6 files changed

+181
-35
lines changed

6 files changed

+181
-35
lines changed

src/Microsoft.OpenApi.Readers/OpenApiReaderSettingsExtensions.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,7 @@ public static class OpenApiReaderSettingsExtensions
1616
public static void AddYamlReader(this OpenApiReaderSettings settings)
1717
{
1818
var yamlReader = new OpenApiYamlReader();
19-
settings.AddReaderToSettings(OpenApiConstants.Yaml, yamlReader);
20-
settings.AddReaderToSettings(OpenApiConstants.Yml, yamlReader);
21-
}
22-
private static void AddReaderToSettings(this OpenApiReaderSettings settings, string format, IOpenApiReader reader)
23-
{
24-
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET5_0_OR_GREATER
25-
settings.Readers.TryAdd(format, reader);
26-
#else
27-
if (!settings.Readers.ContainsKey(format))
28-
{
29-
settings.Readers.Add(format, reader);
30-
}
31-
#endif
19+
settings.TryAddReader(OpenApiConstants.Yaml, yamlReader);
20+
settings.TryAddReader(OpenApiConstants.Yml, yamlReader);
3221
}
3322
}

src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static T Load<T>(MemoryStream input, OpenApiSpecVersion version, string f
6666
{
6767
format ??= InspectStreamFormat(input);
6868
settings ??= DefaultReaderSettings.Value;
69-
return settings.Readers[format].ReadFragment<T>(input, version, openApiDocument, out diagnostic, settings);
69+
return settings.GetReader(format).ReadFragment<T>(input, version, openApiDocument, out diagnostic, settings);
7070
}
7171

7272
/// <summary>
@@ -239,7 +239,7 @@ public static T Parse<T>(string input,
239239
private static async Task<ReadResult> InternalLoadAsync(Stream input, string format, OpenApiReaderSettings settings, CancellationToken cancellationToken = default)
240240
{
241241
settings ??= DefaultReaderSettings.Value;
242-
var reader = settings.Readers[format];
242+
var reader = settings.GetReader(format);
243243
var readResult = await reader.ReadAsync(input, settings, cancellationToken).ConfigureAwait(false);
244244

245245
if (settings.LoadExternalRefs)
@@ -280,7 +280,7 @@ private static ReadResult InternalLoad(MemoryStream input, string format, OpenAp
280280
throw new ArgumentException($"Cannot parse the stream: {nameof(input)} is empty or contains no elements.");
281281
}
282282

283-
var reader = settings.Readers[format];
283+
var reader = settings.GetReader(format);
284284
var readResult = reader.Read(input, settings);
285285
return readResult;
286286
}

src/Microsoft.OpenApi/Reader/OpenApiReaderSettings.cs

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,66 @@ internal get
4040
/// </summary>
4141
public void AddJsonReader()
4242
{
43+
TryAddReader(OpenApiConstants.Json, new OpenApiJsonReader());
44+
}
45+
/// <summary>
46+
/// Gets the reader for the specified format
47+
/// </summary>
48+
/// <param name="format">Format to fetch the reader for</param>
49+
/// <returns>The retrieved reader</returns>
50+
/// <exception cref="NotSupportedException">When no reader is registered for that format</exception>
51+
internal IOpenApiReader GetReader(string format)
52+
{
53+
Utils.CheckArgumentNullOrEmpty(format);
54+
if (Readers.TryGetValue(format, out var reader))
55+
{
56+
return reader;
57+
}
58+
59+
throw new NotSupportedException($"Format '{format}' is not supported.");
60+
}
61+
/// <summary>
62+
/// Adds a reader for the specified format.
63+
/// This method is a no-op if the reader already exists.
64+
/// This method is equivalent to TryAdd, is provided for compatibility reasons and TryAdd should be used instead when available.
65+
/// </summary>
66+
/// <param name="format">Format to add a reader for</param>
67+
/// <param name="reader">Reader to add</param>
68+
/// <returns>True if the reader was added, false if it already existed</returns>
69+
public bool TryAddReader(string format, IOpenApiReader reader)
70+
{
71+
Utils.CheckArgumentNullOrEmpty(format);
72+
Utils.CheckArgumentNull(reader);
4373
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET5_0_OR_GREATER
44-
Readers.TryAdd(OpenApiConstants.Json, new OpenApiJsonReader());
74+
return Readers.TryAdd(format, reader);
4575
#else
46-
if (!Readers.ContainsKey(OpenApiConstants.Json))
76+
if (!Readers.ContainsKey(format))
4777
{
48-
Readers.Add(OpenApiConstants.Json, new OpenApiJsonReader());
78+
Readers.Add(format, reader);
79+
return true;
4980
}
81+
return false;
5082
#endif
5183
}
52-
/// <summary>
53-
/// Readers to use to parse the OpenAPI document
54-
/// </summary>
55-
public Dictionary<string, IOpenApiReader> Readers { get; init; } = new Dictionary<string, IOpenApiReader>(StringComparer.OrdinalIgnoreCase)
84+
private Dictionary<string, IOpenApiReader> _readers = new(StringComparer.OrdinalIgnoreCase)
5685
{
5786
{ OpenApiConstants.Json, new OpenApiJsonReader() }
5887
};
5988
/// <summary>
89+
/// Readers to use to parse the OpenAPI document
90+
/// </summary>
91+
public Dictionary<string, IOpenApiReader> Readers
92+
{
93+
get => _readers;
94+
init
95+
{
96+
Utils.CheckArgumentNull(value);
97+
_readers = value.Comparer == StringComparer.OrdinalIgnoreCase ?
98+
value :
99+
new Dictionary<string, IOpenApiReader>(value, StringComparer.OrdinalIgnoreCase);
100+
}
101+
}
102+
/// <summary>
60103
/// When external references are found, load them into a shared workspace
61104
/// </summary>
62105
public bool LoadExternalRefs { get; set; } = false;
@@ -107,18 +150,21 @@ public void AddJsonReader()
107150
/// </summary>
108151
public void AddMicrosoftExtensionParsers()
109152
{
110-
if (!ExtensionParsers.ContainsKey(OpenApiPagingExtension.Name))
111-
ExtensionParsers.Add(OpenApiPagingExtension.Name, static (i, _) => OpenApiPagingExtension.Parse(i));
112-
if (!ExtensionParsers.ContainsKey(OpenApiEnumValuesDescriptionExtension.Name))
113-
ExtensionParsers.Add(OpenApiEnumValuesDescriptionExtension.Name, static (i, _ ) => OpenApiEnumValuesDescriptionExtension.Parse(i));
114-
if (!ExtensionParsers.ContainsKey(OpenApiPrimaryErrorMessageExtension.Name))
115-
ExtensionParsers.Add(OpenApiPrimaryErrorMessageExtension.Name, static (i, _ ) => OpenApiPrimaryErrorMessageExtension.Parse(i));
116-
if (!ExtensionParsers.ContainsKey(OpenApiDeprecationExtension.Name))
117-
ExtensionParsers.Add(OpenApiDeprecationExtension.Name, static (i, _ ) => OpenApiDeprecationExtension.Parse(i));
118-
if (!ExtensionParsers.ContainsKey(OpenApiReservedParameterExtension.Name))
119-
ExtensionParsers.Add(OpenApiReservedParameterExtension.Name, static (i, _ ) => OpenApiReservedParameterExtension.Parse(i));
120-
if (!ExtensionParsers.ContainsKey(OpenApiEnumFlagsExtension.Name))
121-
ExtensionParsers.Add(OpenApiEnumFlagsExtension.Name, static (i, _ ) => OpenApiEnumFlagsExtension.Parse(i));
153+
TryAddExtensionParser(OpenApiPagingExtension.Name, static (i, _) => OpenApiPagingExtension.Parse(i));
154+
TryAddExtensionParser(OpenApiEnumValuesDescriptionExtension.Name, static (i, _ ) => OpenApiEnumValuesDescriptionExtension.Parse(i));
155+
TryAddExtensionParser(OpenApiPrimaryErrorMessageExtension.Name, static (i, _ ) => OpenApiPrimaryErrorMessageExtension.Parse(i));
156+
TryAddExtensionParser(OpenApiDeprecationExtension.Name, static (i, _ ) => OpenApiDeprecationExtension.Parse(i));
157+
TryAddExtensionParser(OpenApiReservedParameterExtension.Name, static (i, _ ) => OpenApiReservedParameterExtension.Parse(i));
158+
TryAddExtensionParser(OpenApiEnumFlagsExtension.Name, static (i, _ ) => OpenApiEnumFlagsExtension.Parse(i));
159+
}
160+
private void TryAddExtensionParser(string name, Func<JsonNode, OpenApiSpecVersion, IOpenApiExtension> parser)
161+
{
162+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET5_0_OR_GREATER
163+
ExtensionParsers.TryAdd(name, parser);
164+
#else
165+
if (!ExtensionParsers.ContainsKey(name))
166+
ExtensionParsers.Add(name, parser);
167+
#endif
122168
}
123169
}
124170
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using Microsoft.OpenApi.Models;
3+
using Microsoft.OpenApi.Reader;
4+
using Xunit;
5+
6+
namespace Microsoft.OpenApi.Readers.Tests;
7+
8+
public class OpenApiReaderSettingsExtensionsTests
9+
{
10+
[Fact]
11+
public void AddsYamlReader()
12+
{
13+
var settings = new OpenApiReaderSettings();
14+
Assert.Single(settings.Readers);
15+
Assert.DoesNotContain(OpenApiConstants.Yaml, settings.Readers.Keys);
16+
Assert.DoesNotContain(OpenApiConstants.Yml, settings.Readers.Keys);
17+
18+
settings.AddYamlReader();
19+
Assert.Equal(3, settings.Readers.Count);
20+
Assert.Contains(OpenApiConstants.Yaml, settings.Readers.Keys);
21+
Assert.Contains(OpenApiConstants.Yml, settings.Readers.Keys);
22+
Assert.IsType<OpenApiYamlReader>(settings.GetReader(OpenApiConstants.Yaml));
23+
Assert.IsType<OpenApiYamlReader>(settings.GetReader(OpenApiConstants.Yml));
24+
}
25+
[Fact]
26+
public void IsAvailableOnSameNamespace()
27+
{
28+
var settingsNS = typeof(OpenApiReaderSettings).Namespace;
29+
var extensionsNS = typeof(OpenApiReaderSettingsExtensions).Namespace;
30+
Assert.Equal(settingsNS, extensionsNS, StringComparer.Ordinal);
31+
}
32+
}

test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,7 @@ namespace Microsoft.OpenApi.Reader
15041504
public Microsoft.OpenApi.Validations.ValidationRuleSet RuleSet { get; set; }
15051505
public void AddJsonReader() { }
15061506
public void AddMicrosoftExtensionParsers() { }
1507+
public bool TryAddReader(string format, Microsoft.OpenApi.Interfaces.IOpenApiReader reader) { }
15071508
}
15081509
public static class OpenApiVersionExtensionMethods
15091510
{
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using Microsoft.OpenApi.MicrosoftExtensions;
3+
using Microsoft.OpenApi.Models;
4+
using Xunit;
5+
6+
namespace Microsoft.OpenApi.Reader.Tests;
7+
8+
public class OpenApiReaderSettingsTests
9+
{
10+
[Fact]
11+
public void Defensive()
12+
{
13+
var settings = new OpenApiReaderSettings();
14+
Assert.Throws<ArgumentNullException>(() => settings.GetReader(null));
15+
Assert.Throws<ArgumentNullException>(() => settings.GetReader(string.Empty));
16+
17+
Assert.Throws<ArgumentNullException>(() => settings.TryAddReader(null, null));
18+
Assert.Throws<ArgumentNullException>(() => settings.TryAddReader(string.Empty, null));
19+
Assert.Throws<ArgumentNullException>(() => settings.TryAddReader(null, new OpenApiJsonReader()));
20+
Assert.Throws<ArgumentNullException>(() => settings.TryAddReader(string.Empty, new OpenApiJsonReader()));
21+
Assert.Throws<ArgumentNullException>(() => settings.TryAddReader("json", null));
22+
}
23+
24+
[Fact]
25+
public void Defaults()
26+
{
27+
var settings = new OpenApiReaderSettings();
28+
Assert.NotNull(settings.HttpClient);
29+
30+
Assert.IsType<OpenApiJsonReader>(settings.GetReader(OpenApiConstants.Json));
31+
Assert.Throws<NotSupportedException>(() =>settings.GetReader(OpenApiConstants.Yaml));
32+
Assert.Single(settings.Readers);
33+
34+
Assert.Equal(StringComparer.OrdinalIgnoreCase, settings.Readers.Comparer);
35+
36+
Assert.False(settings.TryAddReader("json", new OpenApiJsonReader()));
37+
Assert.Empty(settings.ExtensionParsers);
38+
}
39+
[Fact]
40+
public void InitializesReadersWithComparer()
41+
{
42+
var settings = new OpenApiReaderSettings
43+
{
44+
Readers = []
45+
};
46+
47+
Assert.Equal(StringComparer.OrdinalIgnoreCase, settings.Readers.Comparer);
48+
}
49+
[Fact]
50+
public void AddsMicrosoftExtensions()
51+
{
52+
var settings = new OpenApiReaderSettings();
53+
Assert.Empty(settings.ExtensionParsers);
54+
settings.AddMicrosoftExtensionParsers();
55+
56+
Assert.NotEmpty(settings.ExtensionParsers);
57+
Assert.Contains(OpenApiPagingExtension.Name, settings.ExtensionParsers.Keys);
58+
Assert.Contains(OpenApiEnumValuesDescriptionExtension.Name, settings.ExtensionParsers.Keys);
59+
Assert.Contains(OpenApiPrimaryErrorMessageExtension.Name, settings.ExtensionParsers.Keys);
60+
Assert.Contains(OpenApiDeprecationExtension.Name, settings.ExtensionParsers.Keys);
61+
Assert.Contains(OpenApiReservedParameterExtension.Name, settings.ExtensionParsers.Keys);
62+
Assert.Contains(OpenApiEnumFlagsExtension.Name, settings.ExtensionParsers.Keys);
63+
}
64+
[Fact]
65+
public void AddsJsonReader()
66+
{
67+
var settings = new OpenApiReaderSettings()
68+
{
69+
Readers = []
70+
};
71+
72+
Assert.Empty(settings.Readers);
73+
74+
settings.AddJsonReader();
75+
Assert.Single(settings.Readers);
76+
Assert.IsType<OpenApiJsonReader>(settings.GetReader(OpenApiConstants.Json));
77+
}
78+
}

0 commit comments

Comments
 (0)