diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
index 61e70fb8..023ddebc 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
@@ -22,7 +22,7 @@ public class MatcherModel
public object? Pattern { get; set; }
///
- /// Gets or sets the patterns. Can be array of strings (default) or an array of objects.
+ /// Gets or sets the patterns. Can be an array of strings (default) or an array of objects.
///
public object[]? Patterns { get; set; }
diff --git a/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs b/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs
index 0350782a..434a1989 100644
--- a/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs
+++ b/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System;
+using WireMock.Validators;
// ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings;
@@ -94,9 +95,14 @@ public partial class RequestModelBuilder
}
///
- /// Set the Path.
+ /// Set the Path. Must start with a forward slash (/).
///
- public RequestModelBuilder WithPath(string value) => WithPath(() => value);
+ public RequestModelBuilder WithPath(string value)
+ {
+ PathValidator.ValidateAndThrow(value);
+
+ return WithPath(() => value);
+ }
///
/// Set the Path.
diff --git a/src/WireMock.Net.Abstractions/Validators/PathValidator.cs b/src/WireMock.Net.Abstractions/Validators/PathValidator.cs
new file mode 100644
index 00000000..9763f6a2
--- /dev/null
+++ b/src/WireMock.Net.Abstractions/Validators/PathValidator.cs
@@ -0,0 +1,19 @@
+// Copyright © WireMock.Net
+
+using System;
+
+namespace WireMock.Validators;
+
+public static class PathValidator
+{
+ ///
+ /// A valid path must start with a '/' and cannot be null, empty or whitespace.
+ ///
+ public static void ValidateAndThrow(string? path, string? paramName = null)
+ {
+ if (string.IsNullOrWhiteSpace(path) || path?.StartsWith("/") == false)
+ {
+ throw new ArgumentException("Path must start with a '/' and cannot be null, empty or whitespace.", paramName ?? nameof(path));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs
index 2a5939bb..7ff11fd8 100644
--- a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs
+++ b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs
@@ -4,6 +4,7 @@ using System;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
+using WireMock.Validators;
namespace WireMock.RequestBuilders;
@@ -34,6 +35,10 @@ public partial class Request
public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths)
{
Guard.NotNullOrEmpty(paths);
+ foreach (var path in paths)
+ {
+ PathValidator.ValidateAndThrow(path, nameof(paths));
+ }
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
return this;
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs
index 57a1510b..14cb09b3 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs
@@ -109,7 +109,7 @@ public partial class MappingConverterTests
var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc");
var request = Request.Create()
.UsingGet()
- .WithPath("test_path")
+ .WithPath("/test_path")
.WithParam("q", "42")
.WithClientIP("112.123.100.99")
.WithHeader("h-key", "h-value")
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt
index def84b6f..5a4b9e1b 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt
@@ -1,7 +1,7 @@
builder
.Given(Request.Create()
.UsingMethod("GET")
- .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
+ .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
.WithClientIP("112.123.100.99")
.WithHeader("h-key", "h-value", true)
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt
index b7dc2b58..b85622a1 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt
@@ -2,7 +2,7 @@
builder
.Given(Request.Create()
.UsingMethod("GET")
- .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
+ .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
.WithClientIP("112.123.100.99")
.WithHeader("h-key", "h-value", true)
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt
index 93304d65..80754949 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt
@@ -1,7 +1,7 @@
server
.Given(Request.Create()
.UsingMethod("GET")
- .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
+ .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
.WithClientIP("112.123.100.99")
.WithHeader("h-key", "h-value", true)
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt
index cb224cd2..a2589a27 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt
@@ -2,7 +2,7 @@
server
.Given(Request.Create()
.UsingMethod("GET")
- .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or))
+ .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or))
.WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42"))
.WithClientIP("112.123.100.99")
.WithHeader("h-key", "h-value", true)
diff --git a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt
index 1e023f54..cda79f01 100644
--- a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt
+++ b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt
@@ -9,7 +9,7 @@
Matchers: [
{
Name: WildcardMatcher,
- Pattern: x,
+ Pattern: /x,
IgnoreCase: false
}
]
diff --git a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs
index dad9fe67..c8126cee 100644
--- a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs
+++ b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs
@@ -56,7 +56,7 @@ public class ProxyMappingConverterTests
var request = Request.Create()
.UsingPost()
- .WithPath("x")
+ .WithPath("/x")
.WithParam("p1", "p1-v")
.WithParam("p2", "p2-v")
.WithHeader("Content-Type", new ContentTypeMatcher("text/plain"))
diff --git a/test/WireMock.Net.Tests/Validators/PathValidatorTests.cs b/test/WireMock.Net.Tests/Validators/PathValidatorTests.cs
new file mode 100644
index 00000000..136f7c34
--- /dev/null
+++ b/test/WireMock.Net.Tests/Validators/PathValidatorTests.cs
@@ -0,0 +1,42 @@
+// Copyright © WireMock.Net
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using FluentAssertions;
+using WireMock.Validators;
+using Xunit;
+
+namespace WireMock.Net.Tests.Validators;
+
+[ExcludeFromCodeCoverage]
+public class PathValidatorTests
+{
+ [Fact]
+ public void ValidateAndThrow_ValidPath_DoesNotThrow()
+ {
+ Action act = () => PathValidator.ValidateAndThrow("/valid/path");
+ act.Should().NotThrow();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("\r")]
+ [InlineData("\n")]
+ [InlineData("\t")]
+ public void ValidateAndThrow_InvalidPath_ThrowsArgumentException_WithDefaultParamName(string? path)
+ {
+ Action act = () => PathValidator.ValidateAndThrow(path);
+ var ex = act.Should().Throw().Which;
+ ex.Message.Should().StartWith("Path must start with a '/' and cannot be null, empty or whitespace.");
+ ex.ParamName.Should().Be("path");
+ }
+
+ [Fact]
+ public void ValidateAndThrow_NoLeadingSlash_ThrowsArgumentException_WithProvidedParamName()
+ {
+ Action act = () => PathValidator.ValidateAndThrow("noSlash", "myParam");
+ var ex = act.Should().Throw().Which;
+ ex.ParamName.Should().Be("myParam");
+ }
+}
\ No newline at end of file