Skip to content

Commit

Permalink
Add FormUrlEncodedMatcher (#1147)
Browse files Browse the repository at this point in the history
* FormUrlEncodedMatcher

* .

* Fix

* new

* support wildcard
  • Loading branch information
StefH authored Jul 27, 2024
1 parent 926eaae commit 3353be6
Show file tree
Hide file tree
Showing 10 changed files with 530 additions and 30 deletions.
153 changes: 153 additions & 0 deletions src/WireMock.Net/Matchers/FormUrlEncodedMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright © WireMock.Net

using System.Collections.Generic;
using AnyOfTypes;
using Stef.Validation;
using WireMock.Models;
using WireMock.Util;

namespace WireMock.Matchers;

/// <summary>
/// FormUrl Encoded fields Matcher
/// </summary>
/// <inheritdoc cref="IStringMatcher"/>
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;

/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }

private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = [];

/// <summary>
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public FormUrlEncodedMatcher(
AnyOf<string, StringPattern> pattern,
bool ignoreCase = false,
MatchOperator matchOperator = MatchOperator.Or) :
this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, matchOperator)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public FormUrlEncodedMatcher(
MatchBehaviour matchBehaviour,
AnyOf<string, StringPattern> pattern,
bool ignoreCase = false,
MatchOperator matchOperator = MatchOperator.Or) :
this(matchBehaviour, [pattern], ignoreCase, matchOperator)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public FormUrlEncodedMatcher(
AnyOf<string, StringPattern>[] patterns,
bool ignoreCase = false,
MatchOperator matchOperator = MatchOperator.Or) :
this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase, matchOperator)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public FormUrlEncodedMatcher(
MatchBehaviour matchBehaviour,
AnyOf<string, StringPattern>[] patterns,
bool ignoreCase = false,
MatchOperator matchOperator = MatchOperator.Or)
{
_patterns = Guard.NotNull(patterns);
IgnoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;

foreach (var pattern in _patterns)
{
if (QueryStringParser.TryParse(pattern, IgnoreCase, out var nameValueCollection))
{
foreach (var nameValue in nameValueCollection)
{
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator);
var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator);
_pairs.Add((keyMatcher, valueMatcher));
}
}
}
}

/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
// Input is null or empty and if no patterns defined, return Perfect match.
if (string.IsNullOrEmpty(input) && _patterns.Length == 0)
{
return new MatchResult(MatchScores.Perfect);
}

if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection))
{
return new MatchResult(MatchScores.Mismatch);
}

var matches = new List<bool>();
foreach (var inputKeyValuePair in inputNameValueCollection)
{
var match = false;
foreach (var pair in _pairs)
{
var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect();
if (keyMatchResult)
{
match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
if (match)
{
break;
}
}
}

matches.Add(match);
}

var score = MatchScores.ToScore(matches.ToArray(), MatchOperator);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
}

/// <inheritdoc />
public virtual AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}

/// <inheritdoc />
public virtual string Name => nameof(FormUrlEncodedMatcher);

/// <inheritdoc />
public bool IgnoreCase { get; }

/// <inheritdoc />
public MatchOperator MatchOperator { get; }
}
25 changes: 14 additions & 11 deletions src/WireMock.Net/Serialization/MappingConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,22 @@ public string ToCSharpCode(IMapping mapping, MappingConverterSettings? settings
{
var firstMatcher = requestMessageBodyMatcher.Matchers.FirstOrDefault();

if (firstMatcher is WildcardMatcher wildcardMatcher && wildcardMatcher.GetPatterns().Any())
switch (firstMatcher)
{
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
}
case IStringMatcher stringMatcher when stringMatcher.GetPatterns().Length > 0:
sb.AppendLine($" .WithBody({GetString(stringMatcher)})");
break;

if (firstMatcher is JsonMatcher jsonMatcher)
{
var matcherType = jsonMatcher.GetType().Name;
sb.AppendLine($" .WithBody(new {matcherType}(");
sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
sb.AppendLine($" regex: {ToCSharpBooleanLiteral(jsonMatcher.Regex)}");
sb.AppendLine(@" ))");
case JsonMatcher jsonMatcher:
{
var matcherType = jsonMatcher.GetType().Name;
sb.AppendLine($" .WithBody(new {matcherType}(");
sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
sb.AppendLine($" regex: {ToCSharpBooleanLiteral(jsonMatcher.Regex)}");
sb.AppendLine(@" ))");
break;
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/WireMock.Net/Serialization/MatcherMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public MatcherMapper(WireMockServerSettings settings)
case nameof(ContentTypeMatcher):
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase);

case nameof(FormUrlEncodedMatcher):
return new FormUrlEncodedMatcher(matchBehaviour, stringPatterns, ignoreCase);

case nameof(SimMetricsMatcher):
SimMetricType type = SimMetricType.Levenstein;
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
Expand Down Expand Up @@ -224,7 +227,7 @@ private AnyOf<string, StringPattern>[] ParseStringPatterns(MatcherModel matcher)
{
if (matcher.Pattern is string patternAsString)
{
return new[] { new AnyOf<string, StringPattern>(patternAsString) };
return [new AnyOf<string, StringPattern>(patternAsString)];
}

if (matcher.Pattern is IEnumerable<string> patternAsStringArray)
Expand All @@ -241,7 +244,7 @@ private AnyOf<string, StringPattern>[] ParseStringPatterns(MatcherModel matcher)
{
var patternAsFile = matcher.PatternAsFile!;
var pattern = _settings.FileSystemHandler.ReadFileAsString(patternAsFile);
return new[] { new AnyOf<string, StringPattern>(new StringPattern { Pattern = pattern, PatternAsFile = patternAsFile }) };
return [new AnyOf<string, StringPattern>(new StringPattern { Pattern = pattern, PatternAsFile = patternAsFile })];
}

return EmptyArray<AnyOf<string, StringPattern>>.Value;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
Guid: Guid_1,
Guid: 41372914-1838-4c67-916b-b9aacdd096ce,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Expand Down Expand Up @@ -33,7 +33,7 @@
}
},
{
Guid: Guid_2,
Guid: 98fae52e-76df-47d9-876f-2ee32e931002,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Expand Down Expand Up @@ -61,5 +61,77 @@
}
},
Response: {}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931003,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /form-urlencoded,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Headers: [
{
Name: Content-Type,
Matchers: [
{
Name: WildcardMatcher,
Pattern: application/x-www-form-urlencoded,
IgnoreCase: true
}
],
IgnoreCase: true
}
],
Body: {
Matcher: {
Name: FormUrlEncodedMatcher,
Patterns: [
name=John Doe,
[email protected]
],
IgnoreCase: false,
MatchOperator: Or
}
}
},
Response: {}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931001,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /users/post1,
IgnoreCase: false
}
]
},
Methods: [
POST
],
Body: {
Matcher: {
Name: JsonMatcher,
Pattern: {
Request: Hello?
},
IgnoreCase: false,
Regex: false
}
}
},
Response: {}
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,35 @@ builder
regex: false
))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931d9b")
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931002")
.RespondWith(Response.Create()
);

builder
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/form-urlencoded")
.WithHeader("Content-Type", "application/x-www-form-urlencoded", true)
.WithBody("name=John Doe")
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931003")
.RespondWith(Response.Create()
);

builder
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/users/post1")
.WithBody(new JsonMatcher(
value: new
{
Request = "Hello?"
},
ignoreCase: false,
regex: false
))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931001")
.RespondWith(Response.Create()
);

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,35 @@ server
regex: false
))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931d9b")
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931002")
.RespondWith(Response.Create()
);

server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/form-urlencoded")
.WithHeader("Content-Type", "application/x-www-form-urlencoded", true)
.WithBody("name=John Doe")
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931003")
.RespondWith(Response.Create()
);

server
.Given(Request.Create()
.UsingMethod("POST")
.WithPath("/users/post1")
.WithBody(new JsonMatcher(
value: new
{
Request = "Hello?"
},
ignoreCase: false,
regex: false
))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931001")
.RespondWith(Response.Create()
);

Loading

0 comments on commit 3353be6

Please sign in to comment.