Skip to content

Commit

Permalink
JsonPartialMatcher - support Regex (#771)
Browse files Browse the repository at this point in the history
* JsonPartialMatcher - support Regex

* .

* .

* more tests

* .

* .
  • Loading branch information
StefH authored Jul 24, 2022
1 parent 150b448 commit bdd421e
Show file tree
Hide file tree
Showing 20 changed files with 1,726 additions and 1,578 deletions.
94 changes: 49 additions & 45 deletions src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
namespace WireMock.Admin.Mappings
namespace WireMock.Admin.Mappings;

/// <summary>
/// MatcherModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class MatcherModel
{
/// <summary>
/// MatcherModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class MatcherModel
{
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }

/// <summary>
/// Gets or sets the pattern. Can be a string (default) or an object.
/// </summary>
public object? Pattern { get; set; }

/// <summary>
/// Gets or sets the patterns. Can be array of strings (default) or an array of objects.
/// </summary>
public object[]? Patterns { get; set; }

/// <summary>
/// Gets or sets the pattern as a file.
/// </summary>
public string? PatternAsFile { get; set; }

/// <summary>
/// Gets or sets the ignore case.
/// </summary>
public bool? IgnoreCase { get; set; }

/// <summary>
/// Reject on match.
/// </summary>
public bool? RejectOnMatch { get; set; }

/// <summary>
/// The Operator to use when multiple patterns are defined. Optional.
/// - null = Same as "or".
/// - "or" = Only one pattern should match.
/// - "and" = All patterns should match.
/// - "average" = The average value from all patterns.
/// </summary>
public string? MatchOperator { get; set; }
}
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = null!;

/// <summary>
/// Gets or sets the pattern. Can be a string (default) or an object.
/// </summary>
public object? Pattern { get; set; }

/// <summary>
/// Gets or sets the patterns. Can be array of strings (default) or an array of objects.
/// </summary>
public object[]? Patterns { get; set; }

/// <summary>
/// Gets or sets the pattern as a file.
/// </summary>
public string? PatternAsFile { get; set; }

/// <summary>
/// Gets or sets the ignore case.
/// </summary>
public bool? IgnoreCase { get; set; }

/// <summary>
/// Reject on match.
/// </summary>
public bool? RejectOnMatch { get; set; }

/// <summary>
/// The Operator to use when multiple patterns are defined. Optional.
/// - null = Same as "or".
/// - "or" = Only one pattern should match.
/// - "and" = All patterns should match.
/// - "average" = The average value from all patterns.
/// </summary>
public string? MatchOperator { get; set; }

/// <summary>
/// Support Regex, only used for JsonPartialMatcher.
/// </summary>
public bool? Regex { get; set; }
}
27 changes: 12 additions & 15 deletions src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
using JetBrains.Annotations;
namespace WireMock.Matchers.Request;

namespace WireMock.Matchers.Request
/// <summary>
/// The RequestMatcher interface.
/// </summary>
public interface IRequestMatcher
{
/// <summary>
/// The RequestMatcher interface.
/// Determines whether the specified RequestMessage is match.
/// </summary>
public interface IRequestMatcher
{
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult);
}
/// <param name="requestMessage">The RequestMessage.</param>
/// <param name="requestMatchResult">The RequestMatchResult.</param>
/// <returns>
/// A value between 0.0 - 1.0 of the similarity.
/// </returns>
double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult);
}
29 changes: 26 additions & 3 deletions src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using WireMock.Util;

namespace WireMock.Matchers;

Expand All @@ -9,15 +10,22 @@ namespace WireMock.Matchers;
/// </summary>
public abstract class AbstractJsonPartialMatcher : JsonMatcher
{
/// <summary>
/// Support Regex
/// </summary>
public bool Regex { get; }

/// <summary>
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false)
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException)
{
Regex = regex;
}

/// <summary>
Expand All @@ -26,9 +34,11 @@ protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false)
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException)
{
Regex = regex;
}

/// <summary>
Expand All @@ -38,9 +48,11 @@ protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false)
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, throwException)
{
Regex = regex;
}

/// <inheritdoc />
Expand All @@ -51,6 +63,17 @@ protected override bool IsMatch(JToken? value, JToken? input)
return true;
}

if (Regex && value.Type == JTokenType.String && input != null)
{
var valueAsString = value.ToString();

var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString());
if (valid)
{
return result;
}
}

if (input == null || value.Type != input.Type)
{
return false;
Expand Down
6 changes: 3 additions & 3 deletions src/WireMock.Net/Matchers/JsonMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ namespace WireMock.Matchers;
/// </summary>
public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
{
/// <inheritdoc cref="IValueMatcher.Value"/>
public object Value { get; }

/// <inheritdoc cref="IMatcher.Name"/>
public virtual string Name => "JsonMatcher";

/// <inheritdoc cref="IValueMatcher.Value"/>
public object Value { get; }

/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }

Expand Down
12 changes: 6 additions & 6 deletions src/WireMock.Net/Matchers/JsonPartialMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ public class JsonPartialMatcher : AbstractJsonPartialMatcher
public override string Name => nameof(JsonPartialMatcher);

/// <inheritdoc />
public JsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
public JsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
{
}

/// <inheritdoc />
public JsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
public JsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
{
}

/// <inheritdoc />
public JsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false)
: base(matchBehaviour, value, ignoreCase, throwException)
public JsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, throwException, regex)
{
}

Expand Down
14 changes: 6 additions & 8 deletions src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using JetBrains.Annotations;

namespace WireMock.Matchers;

/// <summary>
Expand All @@ -11,20 +9,20 @@ public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
public override string Name => nameof(JsonPartialWildcardMatcher);

/// <inheritdoc />
public JsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
public JsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
{
}

/// <inheritdoc />
public JsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool throwException = false)
: base(value, ignoreCase, throwException)
public JsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
{
}

/// <inheritdoc />
public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false)
: base(matchBehaviour, value, ignoreCase, throwException)
public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, throwException, regex)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
/// <summary>
/// The name
/// </summary>
public string? Name { get; }
public string Name { get; }

/// <value>
/// The matchers.
Expand Down Expand Up @@ -94,6 +94,7 @@ public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, MatchOperator
public RequestMessageHeaderMatcher(params Func<IDictionary<string, string[]>, bool>[] funcs)
{
Funcs = Guard.NotNull(funcs);
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
}

/// <inheritdoc />
Expand Down Expand Up @@ -121,15 +122,15 @@ private double IsMatch(IRequestMessage requestMessage)

if (Matchers != null)
{
if (!headers.ContainsKey(Name!))
if (!headers.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
}

var results = new List<double>();
foreach (var matcher in Matchers)
{
var resultsPerMatcher = headers[Name!].Select(v => matcher.IsMatch(v)).ToArray();
var resultsPerMatcher = headers[Name].Select(v => matcher.IsMatch(v)).ToArray();

results.Add(MatchScores.ToScore(resultsPerMatcher, MatchOperator.And));
}
Expand Down
12 changes: 6 additions & 6 deletions src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private double IsMatch(IRequestMessage requestMessage)
return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query)));
}

WireMockList<string> valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase ?? false);
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key!, IgnoreCase ?? false);
if (valuesPresentInRequestMessage == null)
{
// Key is not present at all, just return Mismatch
Expand All @@ -102,7 +102,7 @@ private double IsMatch(IRequestMessage requestMessage)
if (Matchers != null && Matchers.Any())
{
// Return the score based on Matchers and valuesPresentInRequestMessage
return CalculateScore(valuesPresentInRequestMessage);
return CalculateScore(Matchers, valuesPresentInRequestMessage);
}

if (Matchers == null || !Matchers.Any())
Expand All @@ -114,14 +114,14 @@ private double IsMatch(IRequestMessage requestMessage)
return MatchScores.Mismatch;
}

private double CalculateScore(WireMockList<string> valuesPresentInRequestMessage)
private double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
{
var total = new List<double>();

// If the total patterns in all matchers > values in message, use the matcher as base
if (Matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count)
if (matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count)
{
foreach (var matcher in Matchers)
foreach (var matcher in matchers)
{
double score = 0d;
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
Expand All @@ -136,7 +136,7 @@ private double CalculateScore(WireMockList<string> valuesPresentInRequestMessage
{
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
{
double score = Matchers.Max(m => m.IsMatch(valuePresentInRequestMessage));
double score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage));
total.Add(score);
}
}
Expand Down
Loading

0 comments on commit bdd421e

Please sign in to comment.