Skip to content

Commit

Permalink
json matcher regex
Browse files Browse the repository at this point in the history
  • Loading branch information
StefH committed Apr 4, 2024
1 parent 22f9647 commit 53d51cc
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class MatcherModel
/// </summary>
public string? MatchOperator { get; set; }

#region JsonPartialMatcher and JsonPartialWildcardMatcher
#region JsonMatcher, JsonPartialMatcher and JsonPartialWildcardMatcher
/// <summary>
/// Support Regex.
/// </summary>
Expand Down
17 changes: 6 additions & 11 deletions src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,15 @@ 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="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) : base(value, ignoreCase)
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) :
base(value, ignoreCase, regex)
{
Regex = regex;
}

/// <summary>
Expand All @@ -32,9 +27,9 @@ 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="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) : base(value, ignoreCase)
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) :
base(value, ignoreCase, regex)
{
Regex = regex;
}

/// <summary>
Expand All @@ -44,9 +39,9 @@ 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="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) : base(matchBehaviour, value, ignoreCase)
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) :
base(matchBehaviour, value, ignoreCase, regex)
{
Regex = regex;
}

/// <inheritdoc />
Expand Down
85 changes: 80 additions & 5 deletions src/WireMock.Net/Matchers/JsonMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Stef.Validation;
Expand All @@ -23,6 +24,11 @@ public class JsonMatcher : IJsonMatcher
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
public bool IgnoreCase { get; }

/// <summary>
/// Support Regex
/// </summary>
public bool Regex { get; }

private readonly JToken _valueAsJToken;
private readonly Func<JToken, JToken> _jTokenConverter;

Expand All @@ -31,7 +37,8 @@ public class JsonMatcher : IJsonMatcher
/// </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>
public JsonMatcher(string value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase)
/// <param name="regex">Support Regex.</param>
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
{
}

Expand All @@ -40,7 +47,8 @@ public JsonMatcher(string value, bool ignoreCase = false) : this(MatchBehaviour.
/// </summary>
/// <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>
public JsonMatcher(object value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase)
/// <param name="regex">Support Regex.</param>
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
{
}

Expand All @@ -50,12 +58,14 @@ public JsonMatcher(object value, bool ignoreCase = false) : this(MatchBehaviour.
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false)
/// <param name="regex">Support Regex.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
{
Guard.NotNull(value);

MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Regex = regex;

Value = value;
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
Expand Down Expand Up @@ -93,9 +103,74 @@ public MatchResult IsMatch(object? input)
/// <param name="value">Matcher value</param>
/// <param name="input">Input value</param>
/// <returns></returns>
protected virtual bool IsMatch(JToken value, JToken input)
protected virtual bool IsMatch(JToken? value, JToken? input)
{
return JToken.DeepEquals(value, input);
// If both are null, return true.
if (input == null && value == null)
{
return true;
}

// If one of them is null, return false.
if (input == null || value == null)
{
return false;
}

// If not using Regex, use JToken.DeepEquals().
if (!Regex)
{
return JToken.DeepEquals(value, input);
}

// If using Regex, use the MatchRegex method.
if (Regex && value.Type == JTokenType.String)
{
var valueAsString = value.ToString();

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

// If the value is a Guid and the input is a string, or vice versa, convert them to strings and compare the string values.
if ((value.Type == JTokenType.Guid && input.Type == JTokenType.String) || (value.Type == JTokenType.String && input.Type == JTokenType.Guid))
{
return IsMatch(value.ToString(), input.ToString());
}

// If the types are different, return false.
if (value.Type != input.Type)
{
return false;
}

switch (value.Type)
{
// If the value is an object, compare the properties.
case JTokenType.Object:
var nestedValues = value.ToObject<Dictionary<string, JToken>>();
return nestedValues?.Any() != true ||
nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key)));

// If the value is an array, compare the elements.
case JTokenType.Array:
var valuesArray = value.ToObject<JToken[]>();
var tokenArray = input.ToObject<JToken[]>();

if (valuesArray?.Any() != true)
{
return true;
}

return tokenArray?.Any() == true && valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));

default:
// Last resort, convert the values to strings and compare them.
return IsMatch(value.ToString(), input.ToString());
}
}

private static string? ToUpper(string? input)
Expand Down
10 changes: 3 additions & 7 deletions src/WireMock.Net/Serialization/MatcherMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public MatcherMapper(WireMockServerSettings settings)

case nameof(JsonMatcher):
var valueForJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase);
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, useRegex);

case nameof(JsonPartialMatcher):
var valueForJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
Expand Down Expand Up @@ -152,12 +152,8 @@ public MatcherMapper(WireMockServerSettings settings)

switch (matcher)
{
case JsonPartialMatcher jsonPartialMatcher:
model.Regex = jsonPartialMatcher.Regex;
break;

case JsonPartialWildcardMatcher jsonPartialWildcardMatcher:
model.Regex = jsonPartialWildcardMatcher.Regex;
case JsonMatcher jsonMatcher:
model.Regex = jsonMatcher.Regex;
break;

case XPathMatcher xpathMatcher:
Expand Down
18 changes: 18 additions & 0 deletions test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,22 @@ public void JsonMatcher_IsMatch_EnumWithJsonConverter()
// Assert
match.Should().Be(1.0);
}

[Fact]
public void JsonMatcher_IsMatch_WithRegexTrue()
{
// Assign
var matcher = new JsonMatcher(new { Id = "^\\d+$", Name = "Test" }, false, true);

// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
var score = matcher.IsMatch(jObject).Score;

// Assert
Assert.Equal(1.0, score);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
Pattern: {
name: stef
},
IgnoreCase: false
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ message HelloReply {
Pattern: {
name: stef
},
IgnoreCase: false
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}
Expand Down

0 comments on commit 53d51cc

Please sign in to comment.