Added overload to PathString.StartsWithSegments to allow specifying StringComparison:

- This allows us to have a fast-path (or just be more explicit) for the comparison by doing case-sensitive checks (which are cheaper)
This commit is contained in:
damianedwards 2015-10-02 15:38:22 -07:00
parent 61466af7a3
commit 8ecb147332
2 changed files with 124 additions and 9 deletions

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Http
/// <param name="value">The unescaped path to be assigned to the Value property.</param>
public PathString(string value)
{
if (!String.IsNullOrEmpty(value) && value[0] != '/')
if (!string.IsNullOrEmpty(value) && value[0] != '/')
{
throw new ArgumentException(""/*Resources.Exception_PathMustStartWithSlash*/, nameof(value));
}
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Http
/// </summary>
public bool HasValue
{
get { return !String.IsNullOrEmpty(_value); }
get { return !string.IsNullOrEmpty(_value); }
}
/// <summary>
@ -98,23 +98,61 @@ namespace Microsoft.AspNet.Http
return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped));
}
/// <summary>
/// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/>.
/// </summary>
/// <param name="other">The <see cref="PathString"/> to compare.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
public bool StartsWithSegments(PathString other)
{
string value1 = Value ?? String.Empty;
string value2 = other.Value ?? String.Empty;
if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase))
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> when compared
/// using the specified comparison option.
/// </summary>
/// <param name="other">The <see cref="PathString"/> to compare.</param>
/// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
public bool StartsWithSegments(PathString other, StringComparison comparisonType)
{
var value1 = Value ?? string.Empty;
var value2 = other.Value ?? string.Empty;
if (value1.StartsWith(value2, comparisonType))
{
return value1.Length == value2.Length || value1[value2.Length] == '/';
}
return false;
}
/// <summary>
/// Determines whether the beginning of this PathString instance matches the specified <see cref="PathString"/> when compared
/// using the specified comparison option and returns the remaining segments.
/// </summary>
/// <param name="other">The <see cref="PathString"/> to compare.</param>
/// <param name="remaining">The remaining segments after the match.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Secondary information needed after boolean result obtained")]
public bool StartsWithSegments(PathString other, out PathString remaining)
{
string value1 = Value ?? String.Empty;
string value2 = other.Value ?? String.Empty;
if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase))
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining);
}
/// <summary>
/// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> and returns
/// the remaining segments.
/// </summary>
/// <param name="other">The <see cref="PathString"/> to compare.</param>
/// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
/// <param name="remaining">The remaining segments after the match.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Secondary information needed after boolean result obtained")]
public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining)
{
var value1 = Value ?? string.Empty;
var value2 = other.Value ?? string.Empty;
if (value1.StartsWith(value2, comparisonType))
{
if (value1.Length == value2.Length || value1[value2.Length] == '/')
{
@ -278,7 +316,7 @@ namespace Microsoft.AspNet.Http
/// Implicitly calls ToString().
/// </summary>
/// <param name="path"></param>
public static implicit operator string(PathString path)
public static implicit operator string (PathString path)
{
return path.ToString();
}

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNet.Testing;
using Xunit;
@ -70,5 +71,81 @@ namespace Microsoft.AspNet.Http
result = path + "text";
Assert.Equal("/pathtext", result);
}
[Theory]
[InlineData("/test/path", "/TEST", true)]
[InlineData("/test/path", "/TEST/pa", false)]
[InlineData("/TEST/PATH", "/test", true)]
[InlineData("/TEST/path", "/test/pa", false)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", true)]
public void StartsWithSegments_DoesACaseInsensitiveMatch(string sourcePath, string testPath, bool expectedResult)
{
var source = new PathString(sourcePath);
var test = new PathString(testPath);
var result = source.StartsWithSegments(test);
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("/test/path", "/TEST", true)]
[InlineData("/test/path", "/TEST/pa", false)]
[InlineData("/TEST/PATH", "/test", true)]
[InlineData("/TEST/path", "/test/pa", false)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", true)]
public void StartsWithSegmentsWithRemainder_DoesACaseInsensitiveMatch(string sourcePath, string testPath, bool expectedResult)
{
var source = new PathString(sourcePath);
var test = new PathString(testPath);
PathString remaining;
var result = source.StartsWithSegments(test, out remaining);
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
[InlineData("/test/path", "/TEST/pa", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/test/path", "/TEST/pa", StringComparison.Ordinal, false)]
[InlineData("/TEST/PATH", "/test", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/TEST/PATH", "/test", StringComparison.Ordinal, false)]
[InlineData("/TEST/path", "/test/pa", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)]
public void StartsWithSegments_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
{
var source = new PathString(sourcePath);
var test = new PathString(testPath);
var result = source.StartsWithSegments(test, comparison);
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
[InlineData("/test/path", "/TEST/pa", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/test/path", "/TEST/pa", StringComparison.Ordinal, false)]
[InlineData("/TEST/PATH", "/test", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/TEST/PATH", "/test", StringComparison.Ordinal, false)]
[InlineData("/TEST/path", "/test/pa", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)]
public void StartsWithSegmentsWithRemainder_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
{
var source = new PathString(sourcePath);
var test = new PathString(testPath);
PathString remaining;
var result = source.StartsWithSegments(test, comparison, out remaining);
Assert.Equal(expectedResult, result);
}
}
}