diff --git a/src/WireMock/Matchers/IMatcher.cs b/src/WireMock/Matchers/IMatcher.cs
new file mode 100644
index 000000000..398f2508a
--- /dev/null
+++ b/src/WireMock/Matchers/IMatcher.cs
@@ -0,0 +1,17 @@
+namespace WireMock.Matchers
+{
+ ///
+ /// IMatcher
+ ///
+ public interface IMatcher
+ {
+ ///
+ /// Determines whether the specified input is match.
+ ///
+ /// The input.
+ ///
+ /// true if the specified input is match; otherwise, false.
+ ///
+ bool IsMatch(string input);
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock/Matchers/RegexMatcher.cs b/src/WireMock/Matchers/RegexMatcher.cs
new file mode 100644
index 000000000..657a137df
--- /dev/null
+++ b/src/WireMock/Matchers/RegexMatcher.cs
@@ -0,0 +1,38 @@
+using System.Text.RegularExpressions;
+using JetBrains.Annotations;
+using WireMock.Validation;
+
+namespace WireMock.Matchers
+{
+ ///
+ /// Regular Expression Matcher
+ ///
+ ///
+ public class RegexMatcher : IMatcher
+ {
+ private readonly Regex _expression;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pattern.
+ public RegexMatcher([NotNull] string pattern)
+ {
+ Check.NotNull(pattern, nameof(pattern));
+
+ _expression = new Regex(pattern, RegexOptions.Compiled);
+ }
+
+ ///
+ /// Determines whether the specified input is match.
+ ///
+ /// The input.
+ ///
+ /// true if the specified input is match; otherwise, false.
+ ///
+ public bool IsMatch(string input)
+ {
+ return input != null && _expression.IsMatch(input);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock/Matchers/XPathMatcher.cs b/src/WireMock/Matchers/XPathMatcher.cs
new file mode 100644
index 000000000..1334b74d3
--- /dev/null
+++ b/src/WireMock/Matchers/XPathMatcher.cs
@@ -0,0 +1,42 @@
+using System.Xml;
+using JetBrains.Annotations;
+using WireMock.Validation;
+using Wmhelp.XPath2;
+
+namespace WireMock.Matchers
+{
+ ///
+ /// XPath2Matcher
+ ///
+ ///
+ public class XPathMatcher : IMatcher
+ {
+ private readonly string _pattern;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pattern.
+ public XPathMatcher([NotNull] string pattern)
+ {
+ Check.NotNull(pattern, nameof(pattern));
+
+ _pattern = pattern;
+ }
+
+ ///
+ /// Determines whether the specified input is match.
+ ///
+ /// The input.
+ ///
+ /// true if the specified input is match; otherwise, false.
+ ///
+ public bool IsMatch(string input)
+ {
+ var nav = new XmlDocument { InnerXml = input }.CreateNavigator();
+ object result = nav.XPath2Evaluate($"boolean({_pattern})");
+
+ return true.Equals(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock/RequestBodySpec.cs b/src/WireMock/RequestBodySpec.cs
index bd12ff8ba..117748ff4 100644
--- a/src/WireMock/RequestBodySpec.cs
+++ b/src/WireMock/RequestBodySpec.cs
@@ -2,22 +2,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
+using WireMock.Matchers;
using WireMock.Validation;
-[module:
- SuppressMessage("StyleCop.CSharp.ReadabilityRules",
- "SA1101:PrefixLocalCallsWithThis",
- Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")]
-[module:
- SuppressMessage("StyleCop.CSharp.NamingRules",
- "SA1309:FieldNamesMustNotBeginWithUnderscore",
- Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")]
-[module:
- SuppressMessage("StyleCop.CSharp.DocumentationRules",
- "SA1633:FileMustHaveHeader",
- Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")]
-// ReSharper disable ArrangeThisQualifier
-// ReSharper disable InconsistentNaming
namespace WireMock
{
///
@@ -28,22 +15,22 @@ public class RequestBodySpec : ISpecifyRequests
///
/// The bodyRegex.
///
- private readonly byte[] bodyData;
+ private readonly byte[] _bodyData;
///
- /// The bodyRegex.
+ /// The matcher.
///
- private readonly Regex bodyRegex;
+ private readonly IMatcher _matcher;
///
/// The body function
///
- private readonly Func bodyFunc;
+ private readonly Func _bodyFunc;
///
/// The body data function
///
- private readonly Func bodyDataFunc;
+ private readonly Func _bodyDataFunc;
///
/// Initializes a new instance of the class.
@@ -54,7 +41,7 @@ public class RequestBodySpec : ISpecifyRequests
public RequestBodySpec([NotNull, RegexPattern] string body)
{
Check.NotNull(body, nameof(body));
- bodyRegex = new Regex(body);
+ _matcher = new RegexMatcher(body);
}
///
@@ -66,7 +53,7 @@ public RequestBodySpec([NotNull, RegexPattern] string body)
public RequestBodySpec([NotNull] byte[] body)
{
Check.NotNull(body, nameof(body));
- bodyData = body;
+ _bodyData = body;
}
///
@@ -78,7 +65,7 @@ public RequestBodySpec([NotNull] byte[] body)
public RequestBodySpec([NotNull] Func func)
{
Check.NotNull(func, nameof(func));
- bodyFunc = func;
+ _bodyFunc = func;
}
///
@@ -90,7 +77,19 @@ public RequestBodySpec([NotNull] Func func)
public RequestBodySpec([NotNull] Func func)
{
Check.NotNull(func, nameof(func));
- bodyDataFunc = func;
+ _bodyDataFunc = func;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The body matcher.
+ ///
+ public RequestBodySpec([NotNull] IMatcher matcher)
+ {
+ Check.NotNull(matcher, nameof(matcher));
+ _matcher = matcher;
}
///
@@ -104,17 +103,17 @@ public RequestBodySpec([NotNull] Func func)
///
public bool IsSatisfiedBy(RequestMessage requestMessage)
{
- if (bodyRegex != null)
- return bodyRegex.IsMatch(requestMessage.BodyAsString);
+ if (_matcher != null)
+ return _matcher.IsMatch(requestMessage.BodyAsString);
- if (bodyData != null)
- return requestMessage.Body == bodyData;
+ if (_bodyData != null)
+ return requestMessage.Body == _bodyData;
- if (bodyFunc != null)
- return bodyFunc(requestMessage.BodyAsString);
+ if (_bodyFunc != null)
+ return _bodyFunc(requestMessage.BodyAsString);
- if (bodyDataFunc != null)
- return bodyDataFunc(requestMessage.Body);
+ if (_bodyDataFunc != null)
+ return _bodyDataFunc(requestMessage.Body);
return false;
}
diff --git a/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs
index 2412c1bef..262107159 100644
--- a/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs
+++ b/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs
@@ -1,4 +1,6 @@
using System;
+using JetBrains.Annotations;
+using WireMock.Matchers;
namespace WireMock.RequestBuilders
{
@@ -7,6 +9,17 @@ namespace WireMock.RequestBuilders
///
public interface IBodyRequestBuilder
{
+ ///
+ /// The with body.
+ ///
+ ///
+ /// The matcher.
+ ///
+ ///
+ /// The .
+ ///
+ ISpecifyRequests WithBody([NotNull] IMatcher matcher);
+
///
/// The with body.
///
diff --git a/src/WireMock/RequestBuilders/Request.cs b/src/WireMock/RequestBuilders/Request.cs
index 1bccb8fb9..4078d58ae 100644
--- a/src/WireMock/RequestBuilders/Request.cs
+++ b/src/WireMock/RequestBuilders/Request.cs
@@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
+using WireMock.Matchers;
[module:
SuppressMessage("StyleCop.CSharp.ReadabilityRules",
@@ -255,6 +256,19 @@ public ISpecifyRequests WithBody(Func func)
return this;
}
+ ///
+ /// The with body.
+ ///
+ /// The matcher.
+ ///
+ /// The .
+ ///
+ public ISpecifyRequests WithBody(IMatcher matcher)
+ {
+ _requestSpecs.Add(new RequestBodySpec(matcher));
+ return this;
+ }
+
///
/// The with parameters.
///
diff --git a/src/WireMock/project.json b/src/WireMock/project.json
index a9e0dd196..385fac96d 100644
--- a/src/WireMock/project.json
+++ b/src/WireMock/project.json
@@ -25,7 +25,8 @@
"JetBrains.Annotations": {
"version": "10.2.1",
"type": "build"
- }
+ },
+ "XPath2": "1.0.3.1"
},
"frameworks": {
diff --git a/test/WireMock.Net.Tests/RequestTests.cs b/test/WireMock.Net.Tests/RequestTests.cs
index 85abd3be4..e07799c4f 100644
--- a/test/WireMock.Net.Tests/RequestTests.cs
+++ b/test/WireMock.Net.Tests/RequestTests.cs
@@ -1,20 +1,11 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Text;
using NFluent;
using NUnit.Framework;
using WireMock.RequestBuilders;
+using WireMock.Matchers;
-[module:
- SuppressMessage("StyleCop.CSharp.DocumentationRules",
- "SA1600:ElementsMustBeDocumented",
- Justification = "Reviewed. Suppression is OK here, as it's a tests class.")]
-[module:
- SuppressMessage("StyleCop.CSharp.DocumentationRules",
- "SA1633:FileMustHaveHeader",
- Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")]
-// ReSharper disable InconsistentNaming
namespace WireMock.Net.Tests
{
[TestFixture]
@@ -249,7 +240,7 @@ public void Should_specify_requests_matching_given_header_prefix()
public void Should_specify_requests_matching_given_body()
{
// given
- var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(".*Hello world!.*");
+ var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody("Hello world!");
// when
string bodyAsString = "Hello world!";
@@ -261,7 +252,7 @@ public void Should_specify_requests_matching_given_body()
}
[Test]
- public void Should_specify_requests_matching_given_body_as_wildcard()
+ public void Should_specify_requests_matching_given_body_as_regex()
{
// given
var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody("H.*o");
@@ -275,6 +266,61 @@ public void Should_specify_requests_matching_given_body_as_wildcard()
Check.That(spec.IsSatisfiedBy(request)).IsTrue();
}
+ [Test]
+ public void Should_specify_requests_matching_given_body_as_regexmatcher()
+ {
+ // given
+ var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(new RegexMatcher("H.*o"));
+
+ // when
+ string bodyAsString = "Hello world!";
+ byte[] body = Encoding.UTF8.GetBytes(bodyAsString);
+ var request = new RequestMessage(new Uri("http://localhost/foo"), "PUT", body, bodyAsString);
+
+ // then
+ Check.That(spec.IsSatisfiedBy(request)).IsTrue();
+ }
+
+ [Test]
+ public void Should_specify_requests_matching_given_body_as_xpathmatcher_true()
+ {
+ // given
+ var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 3]"));
+
+ // when
+ string xmlBodyAsString = @"
+
+ abc
+ def
+ xyz
+ ";
+ byte[] body = Encoding.UTF8.GetBytes(xmlBodyAsString);
+ var request = new RequestMessage(new Uri("http://localhost/foo"), "PUT", body, xmlBodyAsString);
+
+ // then
+ Check.That(spec.IsSatisfiedBy(request)).IsTrue();
+ }
+
+ [Test]
+ public void Should_specify_requests_matching_given_body_as_xpathmatcher_false()
+ {
+ // given
+ var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 99]"));
+
+ // when
+ string xmlBodyAsString = @"
+
+ abc
+ def
+ xyz
+ ";
+ byte[] body = Encoding.UTF8.GetBytes(xmlBodyAsString);
+ var request = new RequestMessage(new Uri("http://localhost/foo"), "PUT", body, xmlBodyAsString);
+
+ // then
+ Check.That(spec.IsSatisfiedBy(request)).IsFalse();
+ }
+
[Test]
public void Should_exclude_requests_not_matching_given_body()
{