Skip to content

PathString.StartsWithSegments: Clarify trailing slash behavior in doc comments #53924

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

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
36 changes: 36 additions & 0 deletions src/Http/Http.Abstractions/src/PathString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ public static PathString FromUriComponent(Uri uri)
/// </summary>
/// <param name="other">The <see cref="PathString"/> to compare.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
/// <remarks>
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
/// </remarks>
public bool StartsWithSegments(PathString other)
{
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase);
Expand All @@ -224,6 +230,12 @@ public bool StartsWithSegments(PathString other)
/// <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>
/// <remarks>
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
/// </remarks>
public bool StartsWithSegments(PathString other, StringComparison comparisonType)
{
var value1 = Value ?? string.Empty;
Expand All @@ -242,6 +254,12 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
/// <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>
/// <remarks>
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
/// </remarks>
public bool StartsWithSegments(PathString other, out PathString remaining)
{
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining);
Expand All @@ -255,6 +273,12 @@ public bool StartsWithSegments(PathString other, out PathString remaining)
/// <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>
/// <remarks>
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
/// </remarks>
public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining)
{
var value1 = Value ?? string.Empty;
Expand All @@ -279,6 +303,12 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
/// <param name="matched">The matched segments with the original casing in the source value.</param>
/// <param name="remaining">The remaining segments after the match.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
/// <remarks>
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
/// </remarks>
public bool StartsWithSegments(PathString other, out PathString matched, out PathString remaining)
{
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out matched, out remaining);
Expand All @@ -293,6 +323,12 @@ public bool StartsWithSegments(PathString other, out PathString matched, out Pat
/// <param name="matched">The matched segments with the original casing in the source value.</param>
/// <param name="remaining">The remaining segments after the match.</param>
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
/// <remarks>
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
/// </remarks>
public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString matched, out PathString remaining)
{
var value1 = Value ?? string.Empty;
Expand Down
74 changes: 74 additions & 0 deletions src/Http/Http.Abstractions/test/PathStringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@ public void StartsWithSegments_DoesACaseInsensitiveMatch(string sourcePath, stri
Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("/a/", "/a/", true)]
[InlineData("/a/b", "/a/", false)]
[InlineData("/a/b/", "/a/", false)]
[InlineData("/a//b", "/a/", true)]
[InlineData("/a//b/", "/a/", true)]
public void StartsWithSegments_DoesMatchExactPathOrPathWithExtraTrailingSlash(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)]
Expand All @@ -142,6 +158,22 @@ public void StartsWithSegmentsWithRemainder_DoesACaseInsensitiveMatch(string sou
Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("/a/", "/a/", true)]
[InlineData("/a/b", "/a/", false)]
[InlineData("/a/b/", "/a/", false)]
[InlineData("/a//b", "/a/", true)]
[InlineData("/a//b/", "/a/", true)]
public void StartsWithSegmentsWithRemainder_DoesMatchExactPathOrPathWithExtraTrailingSlash(string sourcePath, string testPath, bool expectedResult)
{
var source = new PathString(sourcePath);
var test = new PathString(testPath);

var result = source.StartsWithSegments(test, out var remaining);

Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
Expand All @@ -163,6 +195,27 @@ public void StartsWithSegments_DoesMatchUsingSpecifiedComparison(string sourcePa
Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("/a/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/a/", "/a/", StringComparison.Ordinal, true)]
[InlineData("/a/b", "/a/", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/a/b", "/a/", StringComparison.Ordinal, false)]
[InlineData("/a/b/", "/a/", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/a/b/", "/a/", StringComparison.Ordinal, false)]
[InlineData("/a//b", "/a/", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/a//b", "/a/", StringComparison.Ordinal, true)]
[InlineData("/a//b/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/a//b/", "/a/", StringComparison.Ordinal, true)]
public void StartsWithSegments_DoesMatchExactPathOrPathWithExtraTrailingSlashUsingSpecifiedComparison(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)]
Expand All @@ -184,6 +237,27 @@ public void StartsWithSegmentsWithRemainder_DoesMatchUsingSpecifiedComparison(st
Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("/a/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/a/", "/a/", StringComparison.Ordinal, true)]
[InlineData("/a/b", "/a/", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/a/b", "/a/", StringComparison.Ordinal, false)]
[InlineData("/a/b/", "/a/", StringComparison.OrdinalIgnoreCase, false)]
[InlineData("/a/b/", "/a/", StringComparison.Ordinal, false)]
[InlineData("/a//b", "/a/", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/a//b", "/a/", StringComparison.Ordinal, true)]
[InlineData("/a//b/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/a//b/", "/a/", StringComparison.Ordinal, true)]
public void StartsWithSegmentsWithRemainder_DoesMatchExactPathOrPathWithExtraTrailingSlashUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
{
var source = new PathString(sourcePath);
var test = new PathString(testPath);

var result = source.StartsWithSegments(test, comparison, out var remaining);

Assert.Equal(expectedResult, result);
}

[Theory]
// unreserved
[InlineData("/abc123.-_~", "/abc123.-_~")]
Expand Down