Regex.Match (#201)

This commit is contained in:
Stef Heyenrath
2018-09-07 15:35:47 +02:00
parent cf75b67307
commit ec39d12cfe
5 changed files with 240 additions and 4 deletions

View File

@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.RegularExpressions;
using HandlebarsDotNet;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Utils;
using WireMock.Validation;
namespace WireMock.Transformers
@@ -11,9 +15,41 @@ namespace WireMock.Transformers
{
public static void Register()
{
Handlebars.RegisterHelper("Regex.Match", (writer, context, arguments) =>
{
(string stringToProcess, string regexPattern, object defaultValue) = ParseRegexArguments(arguments);
Match match = Regex.Match(stringToProcess, regexPattern);
if (match.Success)
{
writer.WriteSafeString(match.Value);
}
else if (defaultValue != null)
{
writer.WriteSafeString(defaultValue);
}
});
Handlebars.RegisterHelper("Regex.Match", (writer, options, context, arguments) =>
{
(string stringToProcess, string regexPattern, object defaultValue) = ParseRegexArguments(arguments);
var regex = new Regex(regexPattern);
var namedGroups = RegexUtils.GetNamedGroups(regex, stringToProcess);
if (namedGroups.Any())
{
options.Template(writer, namedGroups);
}
else if (defaultValue != null)
{
writer.WriteSafeString(defaultValue);
}
});
Handlebars.RegisterHelper("JsonPath.SelectToken", (writer, context, arguments) =>
{
(JObject valueToProcess, string jsonpath) = Parse(arguments);
(JObject valueToProcess, string jsonpath) = ParseJsonPathArguments(arguments);
JToken result = null;
try
@@ -34,7 +70,7 @@ namespace WireMock.Transformers
Handlebars.RegisterHelper("JsonPath.SelectTokens", (writer, options, context, arguments) =>
{
(JObject valueToProcess, string jsonpath) = Parse(arguments);
(JObject valueToProcess, string jsonpath) = ParseJsonPathArguments(arguments);
IEnumerable<JToken> values = null;
try
@@ -61,7 +97,7 @@ namespace WireMock.Transformers
});
}
private static (JObject valueToProcess, string jsonpath) Parse(object[] arguments)
private static (JObject valueToProcess, string jsonpath) ParseJsonPathArguments(object[] arguments)
{
Check.Condition(arguments, args => args.Length == 2, nameof(arguments));
Check.NotNull(arguments[0], "arguments[0]");
@@ -80,10 +116,29 @@ namespace WireMock.Transformers
break;
default:
throw new NotSupportedException($"The value '{arguments[0]}' with type {arguments[0].GetType()} cannot be used in Handlebars JsonPath.");
throw new NotSupportedException($"The value '{arguments[0]}' with type '{arguments[0]?.GetType()}' cannot be used in Handlebars JsonPath.");
}
return (valueToProcess, arguments[1] as string);
}
private static (string stringToProcess, string regexPattern, object defaultValue) ParseRegexArguments(object[] arguments)
{
Check.Condition(arguments, args => args.Length >= 2, nameof(arguments));
string ParseAsString(object arg)
{
if (arg is string)
{
return arg as string;
}
else
{
throw new NotSupportedException($"The value '{arg}' with type '{arg?.GetType()}' cannot be used in Handlebars Regex.");
}
}
return (ParseAsString(arguments[0]), ParseAsString(arguments[1]), arguments.Length == 3 ? arguments[2] : null);
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Util
{
public class IndexableDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
/// <summary>
/// Gets the value associated with the specified index.
/// </summary>
/// <param name="index"> The index of the value to get.</param>
/// <returns>The value associated with the specified index.</returns>
public TValue this[int index]
{
get
{
// get the item for that index.
if (index < 0 || index > Count)
{
throw new KeyNotFoundException();
}
return Values.Cast<TValue>().ToArray()[index];
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace WireMock.Utils
{
internal static class RegexUtils
{
public static Dictionary<string, string> GetNamedGroups(Regex regex, string input)
{
var namedGroupsDictionary = new Dictionary<string, string>();
GroupCollection groups = regex.Match(input).Groups;
foreach (string groupName in regex.GetGroupNames())
{
if (groups[groupName].Captures.Count > 0)
{
namedGroupsDictionary.Add(groupName, groups[groupName].Value);
}
}
return namedGroupsDictionary;
}
}
}

View File

@@ -37,6 +37,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Remove="Util\IndexableDictionary.cs" />
<Compile Remove="Util\NamedReaderWriterLocker.cs" />
</ItemGroup>

View File

@@ -494,6 +494,136 @@ namespace WireMock.Net.Tests.ResponseBuilderTests
Check.ThatAsyncCode(() => response.ProvideResponseAsync(request)).Throws<ArgumentNullException>();
}
[Fact]
public async void Response_ProvideResponse_Handlebars_RegexMatch1()
{
// Assign
var body = new BodyData { BodyAsString = "abc" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{Regex.Match request.body \"^(?<x>\\w+)$\"}}")
.WithTransformer();
// Act
var responseMessage = await response.ProvideResponseAsync(request);
// assert
Check.That(responseMessage.Body).Equals("abc");
}
[Fact]
public async void Response_ProvideResponse_Handlebars_RegexMatch1_NoMatch()
{
// Assign
var body = new BodyData { BodyAsString = "abc" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{Regex.Match request.body \"^?0$\"}}")
.WithTransformer();
// Act
var responseMessage = await response.ProvideResponseAsync(request);
// assert
Check.That(responseMessage.Body).Equals("");
}
[Fact]
public async void Response_ProvideResponse_Handlebars_RegexMatch1_NoMatch_WithDefaultValue()
{
// Assign
var body = new BodyData { BodyAsString = "abc" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{Regex.Match request.body \"^?0$\" \"d\"}}")
.WithTransformer();
// Act
var responseMessage = await response.ProvideResponseAsync(request);
// assert
Check.That(responseMessage.Body).Equals("d");
}
[Fact]
public async void Response_ProvideResponse_Handlebars_RegexMatch2()
{
// Assign
var body = new BodyData { BodyAsString = "https://localhost:5000/" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{#Regex.Match request.body \"^(?<proto>\\w+)://[^/]+?(?<port>\\d+)/?\"}}{{this.port}}-{{this.proto}}{{/Regex.Match}}")
.WithTransformer();
// Act
var responseMessage = await response.ProvideResponseAsync(request);
// assert
Check.That(responseMessage.Body).Equals("5000-https");
}
[Fact]
public async void Response_ProvideResponse_Handlebars_RegexMatch2_NoMatch()
{
// Assign
var body = new BodyData { BodyAsString = "{{\\test" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{#Regex.Match request.body \"^(?<proto>\\w+)://[^/]+?(?<port>\\d+)/?\"}}{{this}}{{/Regex.Match}}")
.WithTransformer();
// Act
var responseMessage = await response.ProvideResponseAsync(request);
// assert
Check.That(responseMessage.Body).Equals("");
}
[Fact]
public async void Response_ProvideResponse_Handlebars_RegexMatch2_NoMatch_WithDefaultValue()
{
// Assign
var body = new BodyData { BodyAsString = "{{\\test" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{#Regex.Match request.body \"^(?<proto>\\w+)://[^/]+?(?<port>\\d+)/?\" \"x\"}}{{this}}{{/Regex.Match}}")
.WithTransformer();
// Act
var responseMessage = await response.ProvideResponseAsync(request);
// assert
Check.That(responseMessage.Body).Equals("x");
}
[Fact]
public void Response_ProvideResponse_Handlebars_RegexMatch_Throws()
{
// Assign
var body = new BodyData { BodyAsString = "{{\\test" };
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
var response = Response.Create()
.WithBody("{{#Regex.Match request.bodyAsJson \"^(?<proto>\\w+)://[^/]+?(?<port>\\d+)/?\"}}{{/Regex.Match}}")
.WithTransformer();
// Act and Assert
Check.ThatAsyncCode(() => response.ProvideResponseAsync(request)).Throws<NotSupportedException>();
}
[Fact]
public async Task Response_ProvideResponse_Handlebars_WithBodyAsJson_ResultAsArray()
{