-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more preferences for configuring openapi search paths #424
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
#nullable enable | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.HttpRepl.OpenApi | ||
{ | ||
internal interface IOpenApiSearchPathsProvider | ||
{ | ||
IEnumerable<string> GetOpenApiSearchPaths(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.HttpRepl.OpenApi; | ||
|
||
namespace Microsoft.HttpRepl.Preferences | ||
{ | ||
internal class OpenApiSearchPathsProvider : IOpenApiSearchPathsProvider | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: sealed? |
||
{ | ||
// OpenAPI description search paths are appended to the base url to | ||
// attempt to find the description document. A search path is a | ||
// relative url that is appended to the base url using Uri.TryCreate, | ||
// so the semantics of relative urls matter here. | ||
// Example: Base path https://localhost/v1/ and search path openapi.json | ||
// will result in https://localhost/v1/openapi.json being tested. | ||
// Example: Base path https://localhost/v1/ and search path /openapi.json | ||
// will result in https://localhost/openapi.json being tested. | ||
internal static IEnumerable<string> DefaultSearchPaths { get; } = new[] { | ||
"swagger.json", | ||
"/swagger.json", | ||
"swagger/v1/swagger.json", | ||
"/swagger/v1/swagger.json", | ||
"openapi.json", | ||
"/openapi.json", | ||
}; | ||
|
||
private readonly IPreferences _preferences; | ||
public OpenApiSearchPathsProvider(IPreferences preferences) | ||
{ | ||
_preferences = preferences; | ||
} | ||
|
||
public IEnumerable<string> GetOpenApiSearchPaths() | ||
{ | ||
string[] configSearchPaths = Split(_preferences.GetValue(WellKnownPreference.SwaggerSearchPaths)); | ||
|
||
if (configSearchPaths.Length > 0) | ||
{ | ||
return configSearchPaths; | ||
} | ||
|
||
string[] addToSearchPaths = Split(_preferences.GetValue(WellKnownPreference.SwaggerAddToSearchPaths)); | ||
string[] removeFromSearchPaths = Split(_preferences.GetValue(WellKnownPreference.SwaggerRemoveFromSearchPaths)); | ||
|
||
return DefaultSearchPaths.Union(addToSearchPaths).Except(removeFromSearchPaths); | ||
} | ||
|
||
private static string[] Split(string searchPaths) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this function for exclusive use with search path preferences, or could it be useful for current other preferences or potential future ones down the line? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a huge fan of pipe- (or frankly anything-) delimited strings, so I'm hoping there won't be too many of those before we get to a better settings system. I believe this is the only use case right now. |
||
{ | ||
if (string.IsNullOrWhiteSpace(searchPaths)) | ||
{ | ||
return Array.Empty<string>(); | ||
} | ||
else | ||
{ | ||
return searchPaths.Split('|', StringSplitOptions.RemoveEmptyEntries); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.HttpRepl.Preferences; | ||
using Microsoft.Repl.ConsoleHandling; | ||
|
||
namespace Microsoft.HttpRepl.Fakes | ||
{ | ||
public class FakePreferences : IPreferences | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: sealed? :P |
||
{ | ||
private readonly Dictionary<string, string> _currentPreferences; | ||
|
||
public FakePreferences() | ||
{ | ||
DefaultPreferences = new Dictionary<string, string>(); | ||
_currentPreferences = new(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this madness...Can you link me to this C# feature? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C# 9 feature. What's new in C# 9 and Updated |
||
} | ||
|
||
public IReadOnlyDictionary<string, string> DefaultPreferences { get; } | ||
public IReadOnlyDictionary<string, string> CurrentPreferences => _currentPreferences; | ||
|
||
public bool GetBoolValue(string preference, bool defaultValue = false) | ||
{ | ||
if (CurrentPreferences.TryGetValue(preference, out string value) && bool.TryParse(value, out bool result)) | ||
{ | ||
return result; | ||
} | ||
|
||
return defaultValue; | ||
} | ||
|
||
public AllowedColors GetColorValue(string preference, AllowedColors defaultValue = AllowedColors.None) | ||
{ | ||
if (CurrentPreferences.TryGetValue(preference, out string value) && Enum.TryParse(value, true, out AllowedColors result)) | ||
{ | ||
return result; | ||
} | ||
|
||
return defaultValue; | ||
} | ||
|
||
public int GetIntValue(string preference, int defaultValue = 0) | ||
{ | ||
if (CurrentPreferences.TryGetValue(preference, out string value) && int.TryParse(value, out int result)) | ||
{ | ||
return result; | ||
} | ||
|
||
return defaultValue; | ||
} | ||
|
||
public string GetValue(string preference, string defaultValue = null) | ||
{ | ||
if (CurrentPreferences.TryGetValue(preference, out string value)) | ||
{ | ||
return value; | ||
} | ||
|
||
return defaultValue; | ||
} | ||
|
||
public bool SetValue(string preference, string value) | ||
{ | ||
_currentPreferences[preference] = value; | ||
return true; | ||
} | ||
|
||
public bool TryGetValue(string preference, out string value) => CurrentPreferences.TryGetValue(preference, out value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.HttpRepl.Fakes; | ||
using Microsoft.HttpRepl.Preferences; | ||
using Xunit; | ||
|
||
namespace Microsoft.HttpRepl.Tests.Preferences | ||
{ | ||
public class OpenApiSearchPathsProviderTests | ||
{ | ||
[Fact] | ||
public void WithNoOverrides_ReturnsDefault() | ||
{ | ||
// Arrange | ||
NullPreferences preferences = new(); | ||
OpenApiSearchPathsProvider provider = new(preferences); | ||
IEnumerable<string> expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths; | ||
|
||
// Act | ||
IEnumerable<string> paths = provider.GetOpenApiSearchPaths(); | ||
|
||
// Assert | ||
AssertPathLists(expectedPaths, paths); | ||
} | ||
|
||
[Fact] | ||
public void WithFullOverride_ReturnsConfiguredOverride() | ||
{ | ||
// Arrange | ||
string searchPathOverrides = "/red|/green|/blue"; | ||
FakePreferences preferences = new(); | ||
preferences.SetValue(WellKnownPreference.SwaggerSearchPaths, searchPathOverrides); | ||
OpenApiSearchPathsProvider provider = new(preferences); | ||
string[] expectedPaths = searchPathOverrides.Split('|'); | ||
|
||
// Act | ||
IEnumerable<string> paths = provider.GetOpenApiSearchPaths(); | ||
|
||
// Assert | ||
AssertPathLists(expectedPaths, paths); | ||
} | ||
|
||
[Fact] | ||
public void WithAdditions_ReturnsDefaultPlusAdditions() | ||
{ | ||
// Arrange | ||
string[] searchPathAdditions = new[] { "/red", "/green", "/blue" }; | ||
FakePreferences preferences = new(); | ||
preferences.SetValue(WellKnownPreference.SwaggerAddToSearchPaths, string.Join('|', searchPathAdditions)); | ||
OpenApiSearchPathsProvider provider = new(preferences); | ||
IEnumerable<string> expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths.Union(searchPathAdditions); | ||
|
||
// Act | ||
IEnumerable<string> paths = provider.GetOpenApiSearchPaths(); | ||
|
||
// Assert | ||
AssertPathLists(expectedPaths, paths); | ||
} | ||
|
||
[Fact] | ||
public void WithRemovals_ReturnsDefaultMinusRemovals() | ||
{ | ||
// Arrange | ||
string[] searchPathRemovals = new[] { "swagger.json", "/swagger.json", "swagger/v1/swagger.json", "/swagger/v1/swagger.json" }; | ||
FakePreferences preferences = new(); | ||
preferences.SetValue(WellKnownPreference.SwaggerRemoveFromSearchPaths, string.Join('|', searchPathRemovals)); | ||
OpenApiSearchPathsProvider provider = new(preferences); | ||
IEnumerable<string> expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths.Except(searchPathRemovals); | ||
|
||
// Act | ||
IEnumerable<string> paths = provider.GetOpenApiSearchPaths(); | ||
|
||
// Assert | ||
AssertPathLists(expectedPaths, paths); | ||
} | ||
|
||
[Fact] | ||
public void WithAdditionsAndRemovals_ReturnsCorrectSet() | ||
{ | ||
// Arrange | ||
string[] searchPathAdditions = new[] { "/red", "/green", "/blue" }; | ||
string[] searchPathRemovals = new[] { "swagger.json", "/swagger.json", "swagger/v1/swagger.json", "/swagger/v1/swagger.json" }; | ||
FakePreferences preferences = new(); | ||
preferences.SetValue(WellKnownPreference.SwaggerAddToSearchPaths, string.Join('|', searchPathAdditions)); | ||
preferences.SetValue(WellKnownPreference.SwaggerRemoveFromSearchPaths, string.Join('|', searchPathRemovals)); | ||
OpenApiSearchPathsProvider provider = new(preferences); | ||
IEnumerable<string> expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths.Union(searchPathAdditions).Except(searchPathRemovals); | ||
|
||
// Act | ||
IEnumerable<string> paths = provider.GetOpenApiSearchPaths(); | ||
|
||
// Assert | ||
AssertPathLists(expectedPaths, paths); | ||
} | ||
|
||
private static void AssertPathLists(IEnumerable<string> expectedPaths, IEnumerable<string> paths) | ||
{ | ||
Assert.NotNull(expectedPaths); | ||
Assert.NotNull(paths); | ||
|
||
IEnumerator<string> expectedPathsEnumerator = expectedPaths.GetEnumerator(); | ||
IEnumerator<string> pathsEnumerator = paths.GetEnumerator(); | ||
|
||
while (expectedPathsEnumerator.MoveNext()) | ||
{ | ||
Assert.True(pathsEnumerator.MoveNext(), $"Missing path \"{expectedPathsEnumerator.Current}\""); | ||
Assert.Equal(expectedPathsEnumerator.Current, pathsEnumerator.Current, StringComparer.Ordinal); | ||
} | ||
|
||
if (pathsEnumerator.MoveNext()) | ||
{ | ||
// We can't do a one-liner here like the Missing path version above because | ||
// the order the second parameter is evaluated regardless of the result of the | ||
// evaluation of the first parameter. Also xUnit doesn't have an Assert.Fail, | ||
// so we have to use Assert.True(false) per their comparison chart. | ||
Assert.True(false, $"Extra path \"{pathsEnumerator.Current}\""); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a little funky, though I understand the intent. Maybe put the ArgumentNullException check in the ctor of OpenApiSearchPathsProvider instead where it's actually used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming the "looks a little funky" was in reference to using the discard. According to Jimmy, it saves a few ops!
But yeah, since I also changed it to not store the value anymore and just pass it, moving it makes sense. Done.