Skip to content

Commit 3353be6

Browse files
authored
Add FormUrlEncodedMatcher (#1147)
* FormUrlEncodedMatcher * . * Fix * new * support wildcard
1 parent 926eaae commit 3353be6

10 files changed

+530
-30
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright © WireMock.Net
2+
3+
using System.Collections.Generic;
4+
using AnyOfTypes;
5+
using Stef.Validation;
6+
using WireMock.Models;
7+
using WireMock.Util;
8+
9+
namespace WireMock.Matchers;
10+
11+
/// <summary>
12+
/// FormUrl Encoded fields Matcher
13+
/// </summary>
14+
/// <inheritdoc cref="IStringMatcher"/>
15+
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
16+
public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
17+
{
18+
private readonly AnyOf<string, StringPattern>[] _patterns;
19+
20+
/// <inheritdoc />
21+
public MatchBehaviour MatchBehaviour { get; }
22+
23+
private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = [];
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
27+
/// </summary>
28+
/// <param name="pattern">The pattern.</param>
29+
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
30+
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
31+
public FormUrlEncodedMatcher(
32+
AnyOf<string, StringPattern> pattern,
33+
bool ignoreCase = false,
34+
MatchOperator matchOperator = MatchOperator.Or) :
35+
this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, matchOperator)
36+
{
37+
}
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
41+
/// </summary>
42+
/// <param name="matchBehaviour">The match behaviour.</param>
43+
/// <param name="pattern">The pattern.</param>
44+
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
45+
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
46+
public FormUrlEncodedMatcher(
47+
MatchBehaviour matchBehaviour,
48+
AnyOf<string, StringPattern> pattern,
49+
bool ignoreCase = false,
50+
MatchOperator matchOperator = MatchOperator.Or) :
51+
this(matchBehaviour, [pattern], ignoreCase, matchOperator)
52+
{
53+
}
54+
55+
/// <summary>
56+
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
57+
/// </summary>
58+
/// <param name="patterns">The patterns.</param>
59+
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
60+
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
61+
public FormUrlEncodedMatcher(
62+
AnyOf<string, StringPattern>[] patterns,
63+
bool ignoreCase = false,
64+
MatchOperator matchOperator = MatchOperator.Or) :
65+
this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase, matchOperator)
66+
{
67+
}
68+
69+
/// <summary>
70+
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
71+
/// </summary>
72+
/// <param name="matchBehaviour">The match behaviour.</param>
73+
/// <param name="patterns">The patterns.</param>
74+
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
75+
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
76+
public FormUrlEncodedMatcher(
77+
MatchBehaviour matchBehaviour,
78+
AnyOf<string, StringPattern>[] patterns,
79+
bool ignoreCase = false,
80+
MatchOperator matchOperator = MatchOperator.Or)
81+
{
82+
_patterns = Guard.NotNull(patterns);
83+
IgnoreCase = ignoreCase;
84+
MatchBehaviour = matchBehaviour;
85+
MatchOperator = matchOperator;
86+
87+
foreach (var pattern in _patterns)
88+
{
89+
if (QueryStringParser.TryParse(pattern, IgnoreCase, out var nameValueCollection))
90+
{
91+
foreach (var nameValue in nameValueCollection)
92+
{
93+
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator);
94+
var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator);
95+
_pairs.Add((keyMatcher, valueMatcher));
96+
}
97+
}
98+
}
99+
}
100+
101+
/// <inheritdoc />
102+
public MatchResult IsMatch(string? input)
103+
{
104+
// Input is null or empty and if no patterns defined, return Perfect match.
105+
if (string.IsNullOrEmpty(input) && _patterns.Length == 0)
106+
{
107+
return new MatchResult(MatchScores.Perfect);
108+
}
109+
110+
if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection))
111+
{
112+
return new MatchResult(MatchScores.Mismatch);
113+
}
114+
115+
var matches = new List<bool>();
116+
foreach (var inputKeyValuePair in inputNameValueCollection)
117+
{
118+
var match = false;
119+
foreach (var pair in _pairs)
120+
{
121+
var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect();
122+
if (keyMatchResult)
123+
{
124+
match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
125+
if (match)
126+
{
127+
break;
128+
}
129+
}
130+
}
131+
132+
matches.Add(match);
133+
}
134+
135+
var score = MatchScores.ToScore(matches.ToArray(), MatchOperator);
136+
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
137+
}
138+
139+
/// <inheritdoc />
140+
public virtual AnyOf<string, StringPattern>[] GetPatterns()
141+
{
142+
return _patterns;
143+
}
144+
145+
/// <inheritdoc />
146+
public virtual string Name => nameof(FormUrlEncodedMatcher);
147+
148+
/// <inheritdoc />
149+
public bool IgnoreCase { get; }
150+
151+
/// <inheritdoc />
152+
public MatchOperator MatchOperator { get; }
153+
}

src/WireMock.Net/Serialization/MappingConverter.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,22 @@ public string ToCSharpCode(IMapping mapping, MappingConverterSettings? settings
147147
{
148148
var firstMatcher = requestMessageBodyMatcher.Matchers.FirstOrDefault();
149149

150-
if (firstMatcher is WildcardMatcher wildcardMatcher && wildcardMatcher.GetPatterns().Any())
150+
switch (firstMatcher)
151151
{
152-
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
153-
}
152+
case IStringMatcher stringMatcher when stringMatcher.GetPatterns().Length > 0:
153+
sb.AppendLine($" .WithBody({GetString(stringMatcher)})");
154+
break;
154155

155-
if (firstMatcher is JsonMatcher jsonMatcher)
156-
{
157-
var matcherType = jsonMatcher.GetType().Name;
158-
sb.AppendLine($" .WithBody(new {matcherType}(");
159-
sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
160-
sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
161-
sb.AppendLine($" regex: {ToCSharpBooleanLiteral(jsonMatcher.Regex)}");
162-
sb.AppendLine(@" ))");
156+
case JsonMatcher jsonMatcher:
157+
{
158+
var matcherType = jsonMatcher.GetType().Name;
159+
sb.AppendLine($" .WithBody(new {matcherType}(");
160+
sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
161+
sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
162+
sb.AppendLine($" regex: {ToCSharpBooleanLiteral(jsonMatcher.Regex)}");
163+
sb.AppendLine(@" ))");
164+
break;
165+
}
163166
}
164167
}
165168

src/WireMock.Net/Serialization/MatcherMapper.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ public MatcherMapper(WireMockServerSettings settings)
111111
case nameof(ContentTypeMatcher):
112112
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase);
113113

114+
case nameof(FormUrlEncodedMatcher):
115+
return new FormUrlEncodedMatcher(matchBehaviour, stringPatterns, ignoreCase);
116+
114117
case nameof(SimMetricsMatcher):
115118
SimMetricType type = SimMetricType.Levenstein;
116119
if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type))
@@ -224,7 +227,7 @@ private AnyOf<string, StringPattern>[] ParseStringPatterns(MatcherModel matcher)
224227
{
225228
if (matcher.Pattern is string patternAsString)
226229
{
227-
return new[] { new AnyOf<string, StringPattern>(patternAsString) };
230+
return [new AnyOf<string, StringPattern>(patternAsString)];
228231
}
229232

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

247250
return EmptyArray<AnyOf<string, StringPattern>>.Value;

test/WireMock.Net.Tests/MappingBuilderTests.GetMappings.verified.txt

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
Guid: Guid_1,
3+
Guid: 41372914-1838-4c67-916b-b9aacdd096ce,
44
UpdatedAt: 2023-01-14 15:16:17,
55
Request: {
66
Path: {
@@ -33,7 +33,7 @@
3333
}
3434
},
3535
{
36-
Guid: Guid_2,
36+
Guid: 98fae52e-76df-47d9-876f-2ee32e931002,
3737
UpdatedAt: 2023-01-14 15:16:17,
3838
Request: {
3939
Path: {
@@ -61,5 +61,77 @@
6161
}
6262
},
6363
Response: {}
64+
},
65+
{
66+
Guid: 98fae52e-76df-47d9-876f-2ee32e931003,
67+
UpdatedAt: 2023-01-14 15:16:17,
68+
Request: {
69+
Path: {
70+
Matchers: [
71+
{
72+
Name: WildcardMatcher,
73+
Pattern: /form-urlencoded,
74+
IgnoreCase: false
75+
}
76+
]
77+
},
78+
Methods: [
79+
POST
80+
],
81+
Headers: [
82+
{
83+
Name: Content-Type,
84+
Matchers: [
85+
{
86+
Name: WildcardMatcher,
87+
Pattern: application/x-www-form-urlencoded,
88+
IgnoreCase: true
89+
}
90+
],
91+
IgnoreCase: true
92+
}
93+
],
94+
Body: {
95+
Matcher: {
96+
Name: FormUrlEncodedMatcher,
97+
Patterns: [
98+
name=John Doe,
99+
email=johndoe@example.com
100+
],
101+
IgnoreCase: false,
102+
MatchOperator: Or
103+
}
104+
}
105+
},
106+
Response: {}
107+
},
108+
{
109+
Guid: 98fae52e-76df-47d9-876f-2ee32e931001,
110+
UpdatedAt: 2023-01-14 15:16:17,
111+
Request: {
112+
Path: {
113+
Matchers: [
114+
{
115+
Name: WildcardMatcher,
116+
Pattern: /users/post1,
117+
IgnoreCase: false
118+
}
119+
]
120+
},
121+
Methods: [
122+
POST
123+
],
124+
Body: {
125+
Matcher: {
126+
Name: JsonMatcher,
127+
Pattern: {
128+
Request: Hello?
129+
},
130+
IgnoreCase: false,
131+
Regex: false
132+
}
133+
}
134+
},
135+
Response: {}
64136
}
65137
]

test/WireMock.Net.Tests/MappingBuilderTests.ToCSharpCode_Builder.verified.txt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,35 @@ builder
2424
regex: false
2525
))
2626
)
27-
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931d9b")
27+
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931002")
28+
.RespondWith(Response.Create()
29+
);
30+
31+
builder
32+
.Given(Request.Create()
33+
.UsingMethod("POST")
34+
.WithPath("/form-urlencoded")
35+
.WithHeader("Content-Type", "application/x-www-form-urlencoded", true)
36+
.WithBody("name=John Doe")
37+
)
38+
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931003")
39+
.RespondWith(Response.Create()
40+
);
41+
42+
builder
43+
.Given(Request.Create()
44+
.UsingMethod("POST")
45+
.WithPath("/users/post1")
46+
.WithBody(new JsonMatcher(
47+
value: new
48+
{
49+
Request = "Hello?"
50+
},
51+
ignoreCase: false,
52+
regex: false
53+
))
54+
)
55+
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931001")
2856
.RespondWith(Response.Create()
2957
);
3058

test/WireMock.Net.Tests/MappingBuilderTests.ToCSharpCode_Server.verified.txt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,35 @@ server
2424
regex: false
2525
))
2626
)
27-
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931d9b")
27+
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931002")
28+
.RespondWith(Response.Create()
29+
);
30+
31+
server
32+
.Given(Request.Create()
33+
.UsingMethod("POST")
34+
.WithPath("/form-urlencoded")
35+
.WithHeader("Content-Type", "application/x-www-form-urlencoded", true)
36+
.WithBody("name=John Doe")
37+
)
38+
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931003")
39+
.RespondWith(Response.Create()
40+
);
41+
42+
server
43+
.Given(Request.Create()
44+
.UsingMethod("POST")
45+
.WithPath("/users/post1")
46+
.WithBody(new JsonMatcher(
47+
value: new
48+
{
49+
Request = "Hello?"
50+
},
51+
ignoreCase: false,
52+
regex: false
53+
))
54+
)
55+
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931001")
2856
.RespondWith(Response.Create()
2957
);
3058

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy