Skip to content

Commit 9742e7d

Browse files
authored
PathString.StartsWithSegments: Clarify trailing slash behavior in doc comments (#53924)
* Add remarks clarifying trailing slash behavior * Add StartsWithSegments test methods for trailing slash behavior
1 parent 6525ef0 commit 9742e7d

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

src/Http/Http.Abstractions/src/PathString.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,12 @@ public static PathString FromUriComponent(Uri uri)
212212
/// </summary>
213213
/// <param name="other">The <see cref="PathString"/> to compare.</param>
214214
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
215+
/// <remarks>
216+
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
217+
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
218+
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
219+
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
220+
/// </remarks>
215221
public bool StartsWithSegments(PathString other)
216222
{
217223
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase);
@@ -224,6 +230,12 @@ public bool StartsWithSegments(PathString other)
224230
/// <param name="other">The <see cref="PathString"/> to compare.</param>
225231
/// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
226232
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
233+
/// <remarks>
234+
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
235+
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
236+
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
237+
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
238+
/// </remarks>
227239
public bool StartsWithSegments(PathString other, StringComparison comparisonType)
228240
{
229241
var value1 = Value ?? string.Empty;
@@ -242,6 +254,12 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
242254
/// <param name="other">The <see cref="PathString"/> to compare.</param>
243255
/// <param name="remaining">The remaining segments after the match.</param>
244256
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
257+
/// <remarks>
258+
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
259+
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
260+
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
261+
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
262+
/// </remarks>
245263
public bool StartsWithSegments(PathString other, out PathString remaining)
246264
{
247265
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining);
@@ -255,6 +273,12 @@ public bool StartsWithSegments(PathString other, out PathString remaining)
255273
/// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
256274
/// <param name="remaining">The remaining segments after the match.</param>
257275
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
276+
/// <remarks>
277+
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
278+
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
279+
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
280+
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
281+
/// </remarks>
258282
public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining)
259283
{
260284
var value1 = Value ?? string.Empty;
@@ -279,6 +303,12 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
279303
/// <param name="matched">The matched segments with the original casing in the source value.</param>
280304
/// <param name="remaining">The remaining segments after the match.</param>
281305
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
306+
/// <remarks>
307+
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
308+
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
309+
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
310+
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
311+
/// </remarks>
282312
public bool StartsWithSegments(PathString other, out PathString matched, out PathString remaining)
283313
{
284314
return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out matched, out remaining);
@@ -293,6 +323,12 @@ public bool StartsWithSegments(PathString other, out PathString matched, out Pat
293323
/// <param name="matched">The matched segments with the original casing in the source value.</param>
294324
/// <param name="remaining">The remaining segments after the match.</param>
295325
/// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
326+
/// <remarks>
327+
/// When the <paramref name="other"/> parameter contains a trailing slash, the <see cref="PathString"/> being checked
328+
/// must either exactly match or include a trailing slash. For instance, for a <see cref="PathString"/> of "/a/b",
329+
/// this method will return <c>true</c> for "/a", but will return <c>false</c> for "/a/".
330+
/// Whereas, a <see cref="PathString"/> of "/a//b/" will return <c>true</c> when compared with "/a/".
331+
/// </remarks>
296332
public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString matched, out PathString remaining)
297333
{
298334
var value1 = Value ?? string.Empty;

src/Http/Http.Abstractions/test/PathStringTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ public void StartsWithSegments_DoesACaseInsensitiveMatch(string sourcePath, stri
126126
Assert.Equal(expectedResult, result);
127127
}
128128

129+
[Theory]
130+
[InlineData("/a/", "/a/", true)]
131+
[InlineData("/a/b", "/a/", false)]
132+
[InlineData("/a/b/", "/a/", false)]
133+
[InlineData("/a//b", "/a/", true)]
134+
[InlineData("/a//b/", "/a/", true)]
135+
public void StartsWithSegments_DoesMatchExactPathOrPathWithExtraTrailingSlash(string sourcePath, string testPath, bool expectedResult)
136+
{
137+
var source = new PathString(sourcePath);
138+
var test = new PathString(testPath);
139+
140+
var result = source.StartsWithSegments(test);
141+
142+
Assert.Equal(expectedResult, result);
143+
}
144+
129145
[Theory]
130146
[InlineData("/test/path", "/TEST", true)]
131147
[InlineData("/test/path", "/TEST/pa", false)]
@@ -142,6 +158,22 @@ public void StartsWithSegmentsWithRemainder_DoesACaseInsensitiveMatch(string sou
142158
Assert.Equal(expectedResult, result);
143159
}
144160

161+
[Theory]
162+
[InlineData("/a/", "/a/", true)]
163+
[InlineData("/a/b", "/a/", false)]
164+
[InlineData("/a/b/", "/a/", false)]
165+
[InlineData("/a//b", "/a/", true)]
166+
[InlineData("/a//b/", "/a/", true)]
167+
public void StartsWithSegmentsWithRemainder_DoesMatchExactPathOrPathWithExtraTrailingSlash(string sourcePath, string testPath, bool expectedResult)
168+
{
169+
var source = new PathString(sourcePath);
170+
var test = new PathString(testPath);
171+
172+
var result = source.StartsWithSegments(test, out var remaining);
173+
174+
Assert.Equal(expectedResult, result);
175+
}
176+
145177
[Theory]
146178
[InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
147179
[InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
@@ -163,6 +195,27 @@ public void StartsWithSegments_DoesMatchUsingSpecifiedComparison(string sourcePa
163195
Assert.Equal(expectedResult, result);
164196
}
165197

198+
[Theory]
199+
[InlineData("/a/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
200+
[InlineData("/a/", "/a/", StringComparison.Ordinal, true)]
201+
[InlineData("/a/b", "/a/", StringComparison.OrdinalIgnoreCase, false)]
202+
[InlineData("/a/b", "/a/", StringComparison.Ordinal, false)]
203+
[InlineData("/a/b/", "/a/", StringComparison.OrdinalIgnoreCase, false)]
204+
[InlineData("/a/b/", "/a/", StringComparison.Ordinal, false)]
205+
[InlineData("/a//b", "/a/", StringComparison.OrdinalIgnoreCase, true)]
206+
[InlineData("/a//b", "/a/", StringComparison.Ordinal, true)]
207+
[InlineData("/a//b/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
208+
[InlineData("/a//b/", "/a/", StringComparison.Ordinal, true)]
209+
public void StartsWithSegments_DoesMatchExactPathOrPathWithExtraTrailingSlashUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
210+
{
211+
var source = new PathString(sourcePath);
212+
var test = new PathString(testPath);
213+
214+
var result = source.StartsWithSegments(test, comparison);
215+
216+
Assert.Equal(expectedResult, result);
217+
}
218+
166219
[Theory]
167220
[InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
168221
[InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
@@ -184,6 +237,27 @@ public void StartsWithSegmentsWithRemainder_DoesMatchUsingSpecifiedComparison(st
184237
Assert.Equal(expectedResult, result);
185238
}
186239

240+
[Theory]
241+
[InlineData("/a/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
242+
[InlineData("/a/", "/a/", StringComparison.Ordinal, true)]
243+
[InlineData("/a/b", "/a/", StringComparison.OrdinalIgnoreCase, false)]
244+
[InlineData("/a/b", "/a/", StringComparison.Ordinal, false)]
245+
[InlineData("/a/b/", "/a/", StringComparison.OrdinalIgnoreCase, false)]
246+
[InlineData("/a/b/", "/a/", StringComparison.Ordinal, false)]
247+
[InlineData("/a//b", "/a/", StringComparison.OrdinalIgnoreCase, true)]
248+
[InlineData("/a//b", "/a/", StringComparison.Ordinal, true)]
249+
[InlineData("/a//b/", "/a/", StringComparison.OrdinalIgnoreCase, true)]
250+
[InlineData("/a//b/", "/a/", StringComparison.Ordinal, true)]
251+
public void StartsWithSegmentsWithRemainder_DoesMatchExactPathOrPathWithExtraTrailingSlashUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
252+
{
253+
var source = new PathString(sourcePath);
254+
var test = new PathString(testPath);
255+
256+
var result = source.StartsWithSegments(test, comparison, out var remaining);
257+
258+
Assert.Equal(expectedResult, result);
259+
}
260+
187261
[Theory]
188262
// unreserved
189263
[InlineData("/abc123.-_~", "/abc123.-_~")]

0 commit comments

Comments
 (0)