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> /// <param name="value">The unescaped path to be assigned to the Value property.</param>
public PathString(string value) public PathString(string value)
{ {
if (!String.IsNullOrEmpty(value) && value[0] != '/') if (!string.IsNullOrEmpty(value) && value[0] != '/')
{ {
throw new ArgumentException(""/*Resources.Exception_PathMustStartWithSlash*/, nameof(value)); throw new ArgumentException(""/*Resources.Exception_PathMustStartWithSlash*/, nameof(value));
} }
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Http
/// </summary> /// </summary>
public bool HasValue public bool HasValue
{ {
get { return !String.IsNullOrEmpty(_value); } get { return !string.IsNullOrEmpty(_value); }
} }
/// <summary> /// <summary>
@ -98,23 +98,61 @@ namespace Microsoft.AspNet.Http
return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); 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) public bool StartsWithSegments(PathString other)
{ {
string value1 = Value ?? String.Empty; return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase);
string value2 = other.Value ?? String.Empty; }
if (value1.StartsWith(value2, 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 value1.Length == value2.Length || value1[value2.Length] == '/';
} }
return false; 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")] [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) public bool StartsWithSegments(PathString other, out PathString remaining)
{ {
string value1 = Value ?? String.Empty; return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining);
string value2 = other.Value ?? String.Empty; }
if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase))
/// <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] == '/') if (value1.Length == value2.Length || value1[value2.Length] == '/')
{ {
@ -278,7 +316,7 @@ namespace Microsoft.AspNet.Http
/// Implicitly calls ToString(). /// Implicitly calls ToString().
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
public static implicit operator string(PathString path) public static implicit operator string (PathString path)
{ {
return path.ToString(); return path.ToString();
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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 Microsoft.AspNet.Testing;
using Xunit; using Xunit;
@ -70,5 +71,81 @@ namespace Microsoft.AspNet.Http
result = path + "text"; result = path + "text";
Assert.Equal("/pathtext", result); 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);
}
} }
} }