Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Configuration
/// </summary>
public class ConfigurationKeyComparer : IComparer<string>
{
private static readonly string[] _keyDelimiterArray = new[] { ConfigurationPath.KeyDelimiter };
private const char KeyDelimiter = ':';

/// <summary>
/// The default instance.
Expand All @@ -29,49 +29,73 @@ public class ConfigurationKeyComparer : IComparer<string>
/// <returns>Less than 0 if x is less than y, 0 if x is equal to y and greater than 0 if x is greater than y.</returns>
public int Compare(string? x, string? y)
{
string[] xParts = x?.Split(_keyDelimiterArray, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
string[] yParts = y?.Split(_keyDelimiterArray, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
ReadOnlySpan<char> xSpan = x.AsSpan();
ReadOnlySpan<char> ySpan = y.AsSpan();

xSpan = SkipAheadOnDelimiter(xSpan);
ySpan = SkipAheadOnDelimiter(ySpan);

// Compare each part until we get two parts that are not equal
for (int i = 0; i < Math.Min(xParts.Length, yParts.Length); i++)
while (!xSpan.IsEmpty && !ySpan.IsEmpty)
{
x = xParts[i];
y = yParts[i];
int xDelimiterIndex = xSpan.IndexOf(KeyDelimiter);
int yDelimiterIndex = ySpan.IndexOf(KeyDelimiter);

int compareResult = Compare(
xDelimiterIndex == -1 ? xSpan : xSpan.Slice(0, xDelimiterIndex),
yDelimiterIndex == -1 ? ySpan : ySpan.Slice(0, yDelimiterIndex));

if (compareResult != 0)
{
return compareResult;
}

int value1 = 0;
int value2 = 0;
xSpan = xDelimiterIndex == -1 ? default :
SkipAheadOnDelimiter(xSpan.Slice(xDelimiterIndex + 1));
ySpan = yDelimiterIndex == -1 ? default :
SkipAheadOnDelimiter(ySpan.Slice(yDelimiterIndex + 1));
}

bool xIsInt = x != null && int.TryParse(x, out value1);
bool yIsInt = y != null && int.TryParse(y, out value2);
return xSpan.IsEmpty ? (ySpan.IsEmpty ? 0 : -1) : 1;

static ReadOnlySpan<char> SkipAheadOnDelimiter(ReadOnlySpan<char> a)
{
while (!a.IsEmpty && a[0] == KeyDelimiter)
{
a = a.Slice(1);
}
return a;
}

static int Compare(ReadOnlySpan<char> a, ReadOnlySpan<char> b)
{
#if NETCOREAPP
bool aIsInt = int.TryParse(a, out int value1);
bool bIsInt = int.TryParse(b, out int value2);
#else
bool aIsInt = int.TryParse(a.ToString(), out int value1);
bool bIsInt = int.TryParse(b.ToString(), out int value2);
#endif
int result;

if (!xIsInt && !yIsInt)
if (!aIsInt && !bIsInt)
{
// Both are strings
result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
result = a.CompareTo(b, StringComparison.OrdinalIgnoreCase);
}
else if (xIsInt && yIsInt)
else if (aIsInt && bIsInt)
{
// Both are int
result = value1 - value2;
}
else
{
// Only one of them is int
result = xIsInt ? -1 : 1;
result = aIsInt ? -1 : 1;
}

if (result != 0)
{
// One of them is different
return result;
}
return result;
}

// If we get here, the common parts are equal.
// If they are of the same length, then they are totally identical
return xParts.Length - yParts.Length;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public void CompareWithNull()
ComparerTest(null, null, 0);
ComparerTest(null, "a", -1);
ComparerTest("b", null, 1);
ComparerTest(null, "a:b", -1);
ComparerTest(null, "a:b:c", -1);
}

[Fact]
Expand All @@ -32,6 +34,20 @@ public void CompareWithDifferentLengths()
ComparerTest("aa", "a", 1);
}

[Fact]
public void CompareWithEmpty()
{
ComparerTest(":", "", 0);
ComparerTest(":", "::", 0);
ComparerTest(null, "", 0);
ComparerTest(":", null, 0);
ComparerTest("::", null, 0);
ComparerTest(" : : ", null, 1);
ComparerTest("b: :a", "b::a", -1);
ComparerTest("b:\t:a", "b::a", -1);
ComparerTest("b::a: ", "b::a:", 1);
}

[Fact]
public void CompareWithLetters()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,64 @@ public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders()
Assert.Null(config["NotExist"]);
}

[Fact]
private void GetChildKeys_CanChainEmptyKeys()
{
var input = new Dictionary<string, string>() { };
for (int i = 0; i < 1000; i++)
{
input.Add(new string(' ', i), string.Empty);
}

IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource
{
InitialData = input
})
.Build();

var chainedConfigurationSource = new ChainedConfigurationSource
{
Configuration = configurationRoot,
ShouldDisposeConfiguration = false,
};

var chainedConfiguration = new ChainedConfigurationProvider(chainedConfigurationSource);
IEnumerable<string> childKeys = chainedConfiguration.GetChildKeys(new string[0], null);
Assert.Equal(1000, childKeys.Count());
Assert.Equal(string.Empty, childKeys.First());
Assert.Equal(999, childKeys.Last().Length);
}

[Fact]
private void GetChildKeys_CanChainKeyWithNoDelimiter()
{
var input = new Dictionary<string, string>() { };
for (int i = 1000; i < 2000; i++)
{
input.Add(i.ToString(), string.Empty);
}

IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource
{
InitialData = input
})
.Build();

var chainedConfigurationSource = new ChainedConfigurationSource
{
Configuration = configurationRoot,
ShouldDisposeConfiguration = false,
};

var chainedConfiguration = new ChainedConfigurationProvider(chainedConfigurationSource);
IEnumerable<string> childKeys = chainedConfiguration.GetChildKeys(new string[0], null);
Assert.Equal(1000, childKeys.Count());
Assert.Equal("1000", childKeys.First());
Assert.Equal("1999", childKeys.Last());
}

[Fact]
public void CanChainConfiguration()
{
Expand Down