diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index b741c5f9..d8f3e2d2 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net/Matchers/LinqMatcher.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Linq.Dynamic.Core; using JetBrains.Annotations; +using Newtonsoft.Json.Linq; +using WireMock.Util; namespace WireMock.Matchers { @@ -63,29 +65,33 @@ namespace WireMock.Matchers return MatchBehaviourHelper.Convert(MatchBehaviour, match); } - ///// - //public double IsMatch(object input) - //{ - // object value; - // switch (input) - // { - // case JObject valueAsJObject: - // value = valueAsJObject.ToObject(); - // break; + /// + public double IsMatch(object input) + { + JObject value; + switch (input) + { + case JObject valueAsJObject: + value = valueAsJObject; + break; - // default: - // value = input; - // break; - // } + default: + value = JObject.FromObject(input); + break; + } - // // Convert a single object to a Queryable object-list with 1 entry. - // IQueryable queryable = new[] { value }.AsQueryable().Select("new (it as x)"); + // Convert a single object to a Queryable JObject-list with 1 entry. + var queryable1 = new[] { value }.AsQueryable(); - // // Use the Any(...) method to check if the result matches - // double match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern))); + // Generate the dynamic linq select statement and generate a dynamic Queryable. + string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value); + var queryable2 = queryable1.Select(dynamicSelect); - // return MatchBehaviourHelper.Convert(MatchBehaviour, match); - //} + // Use the Any(...) method to check if the result matches + double match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern))); + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } /// public string[] GetPatterns() diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 098a7fdf..ff3eb8e0 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -7,9 +7,9 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using WireMock.Http; using WireMock.HttpsCertificate; using WireMock.Logging; +using WireMock.Util; using WireMock.Validation; namespace WireMock.Owin @@ -43,7 +43,7 @@ namespace WireMock.Owin { Urls.Add(uriPrefix); - PortUtil.TryExtractProtocolAndPort(uriPrefix, out string host, out int port); + PortUtils.TryExtractProtocolAndPort(uriPrefix, out string host, out int port); Ports.Add(port); } @@ -75,13 +75,13 @@ namespace WireMock.Owin // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?tabs=aspnetcore2x foreach (string url in _urls.Where(u => u.StartsWith("http://", StringComparison.OrdinalIgnoreCase))) { - PortUtil.TryExtractProtocolAndPort(url, out string host, out int port); + PortUtils.TryExtractProtocolAndPort(url, out string host, out int port); options.Listen(System.Net.IPAddress.Any, port); } foreach (string url in _urls.Where(u => u.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { - PortUtil.TryExtractProtocolAndPort(url, out string host, out int port); + PortUtils.TryExtractProtocolAndPort(url, out string host, out int port); options.Listen(System.Net.IPAddress.Any, port, listenOptions => { listenOptions.UseHttps(PublicCertificateHelper.GetX509Certificate2()); diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index c4f2acb9..8710926b 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using WireMock.Http; using WireMock.Logging; +using WireMock.Util; using WireMock.Validation; namespace WireMock.Owin @@ -30,7 +31,7 @@ namespace WireMock.Owin { Urls.Add(uriPrefix); - PortUtil.TryExtractProtocolAndPort(uriPrefix, out string host, out int port); + PortUtils.TryExtractProtocolAndPort(uriPrefix, out string host, out int port); Ports.Add(port); } diff --git a/src/WireMock.Net/Server/FluentMockServer.cs b/src/WireMock.Net/Server/FluentMockServer.cs index 559c1408..146c8da7 100644 --- a/src/WireMock.Net/Server/FluentMockServer.cs +++ b/src/WireMock.Net/Server/FluentMockServer.cs @@ -16,6 +16,7 @@ using WireMock.Owin; using WireMock.RequestBuilders; using WireMock.ResponseProviders; using WireMock.Settings; +using WireMock.Util; using WireMock.Validation; namespace WireMock.Server @@ -199,7 +200,7 @@ namespace WireMock.Server } else { - int port = settings.Port > 0 ? settings.Port.Value : PortUtil.FindFreeTcpPort(); + int port = settings.Port > 0 ? settings.Port.Value : PortUtils.FindFreeTcpPort(); Urls = new[] { $"{(settings.UseSSL == true ? "https" : "http")}://localhost:{port}" }; } diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs index 36178f62..f2aae675 100644 --- a/src/WireMock.Net/Util/JsonUtils.cs +++ b/src/WireMock.Net/Util/JsonUtils.cs @@ -1,4 +1,8 @@ using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace WireMock.Util { @@ -6,12 +10,138 @@ namespace WireMock.Util { public static T ParseJTokenToObject(object value) { - if (value == null) + switch (value) { - return default(T); + case JToken tokenValue: + return tokenValue.ToObject(); + + default: + return default(T); + } + } + + public static string GenerateDynamicLinqStatement(JObject jsonObject) + { + var lines = new List(); + WalkNode(jsonObject, null, null, lines); + + return lines.First(); + } + + private static void WalkNode(JToken node, string path, string propertyName, List lines) + { + if (node.Type == JTokenType.Object) + { + ProcessObject(node, propertyName, lines); + } + else if (node.Type == JTokenType.Array) + { + ProcessArray(node, propertyName, lines); + } + else + { + ProcessItem(node, path, propertyName, lines); + } + } + + private static void ProcessObject(JToken node, string propertyName, List lines) + { + var items = new List(); + var text = new StringBuilder("new ("); + + // In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions. + foreach (JProperty child in node.Children().ToArray()) + { + WalkNode(child.Value, child.Path, child.Name, items); } - return !(value is JToken token) ? default(T) : token.ToObject(); + text.Append(string.Join(", ", items)); + text.Append(")"); + + if (!string.IsNullOrEmpty(propertyName)) + { + text.AppendFormat(" as {0}", propertyName); + } + + lines.Add(text.ToString()); + } + + private static void ProcessArray(JToken node, string propertyName, List lines) + { + var items = new List(); + var text = new StringBuilder("(new [] { "); + + // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions. + int idx = 0; + foreach (JToken child in node.Children().ToArray()) + { + WalkNode(child, $"{node.Path}[{idx}]", null, items); + idx++; + } + + text.Append(string.Join(", ", items)); + text.Append("})"); + + if (!string.IsNullOrEmpty(propertyName)) + { + text.AppendFormat(" as {0}", propertyName); + } + + lines.Add(text.ToString()); + } + + private static void ProcessItem(JToken node, string path, string propertyName, List lines) + { + string castText = string.Empty; + switch (node.Type) + { + case JTokenType.Boolean: + castText = $"bool({path})"; + break; + + case JTokenType.Date: + castText = $"DateTime({path})"; + break; + + case JTokenType.Float: + castText = $"double({path})"; + break; + + case JTokenType.Guid: + castText = $"Guid({path})"; + break; + + case JTokenType.Integer: + castText = $"int({path})"; + break; + + case JTokenType.Null: + castText = "null"; + break; + + case JTokenType.String: + castText = $"string({path})"; + break; + + case JTokenType.TimeSpan: + castText = $"TimeSpan({path})"; + break; + + case JTokenType.Uri: + castText = $"Uri({path})"; + break; + + default: + throw new NotSupportedException( + $"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator."); + } + + if (!string.IsNullOrEmpty(propertyName)) + { + castText += $" as {propertyName}"; + } + + lines.Add(castText); } } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/PortUtil.cs b/src/WireMock.Net/Util/PortUtils.cs similarity index 95% rename from src/WireMock.Net/Http/PortUtil.cs rename to src/WireMock.Net/Util/PortUtils.cs index b92e23db..65861313 100644 --- a/src/WireMock.Net/Http/PortUtil.cs +++ b/src/WireMock.Net/Util/PortUtils.cs @@ -2,12 +2,12 @@ using System.Net.Sockets; using System.Text.RegularExpressions; -namespace WireMock.Http +namespace WireMock.Util { /// /// Port Utility class /// - public static class PortUtil + public static class PortUtils { private static readonly Regex UrlDetailsRegex = new Regex(@"^(?\w+)://[^/]+?(?\d+)/?", RegexOptions.Compiled); diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index e308795a..dddadb98 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -49,7 +49,7 @@ - + diff --git a/test/WireMock.Net.Tests/Matchers/LinqMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/LinqMatcherTests.cs index 429c310a..f023acf1 100644 --- a/test/WireMock.Net.Tests/Matchers/LinqMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/LinqMatcherTests.cs @@ -1,4 +1,5 @@ -using NFluent; +using Newtonsoft.Json.Linq; +using NFluent; using WireMock.Matchers; using Xunit; @@ -45,43 +46,41 @@ namespace WireMock.Net.Tests.Matchers Check.That(matcher.IsMatch(input)).IsEqualTo(0.0d); } - //[Fact] - //public void LinqMatcher_For_Object_IsMatch() - //{ - // // Assign - // var input = new - // { - // Id = 9, - // Name = "Test" - // }; + [Fact] + public void LinqMatcher_For_Object_IsMatch() + { + // Assign + var input = new + { + Id = 9, + Name = "Test" + }; - // // Act - // var matcher = new LinqMatcher("Id > 1 AND Name == \"Test\""); + // Act + var matcher = new LinqMatcher("Id > 1 AND Name == \"Test\""); + double match = matcher.IsMatch(input); - // double match = matcher.IsMatch(input); + // Assert + Assert.Equal(1.0, match); + } - // // Assert - // Assert.Equal(1.0, match); - //} + [Fact] + public void LinqMatcher_For_JObject_IsMatch() + { + // Assign + var input = new JObject + { + { "Id", new JValue(9) }, + { "Name", new JValue("Test") } + }; - //[Fact] - //public void LinqMatcher_For_JObject_IsMatch() - //{ - // // Assign - // var input = new JObject - // { - // { "Id", new JValue(9) }, - // { "Name", new JValue("Test") } - // }; + // Act + var matcher = new LinqMatcher("Id > 1 AND Name == \"Test\""); + double match = matcher.IsMatch(input); - // // Act - // var matcher = new LinqMatcher("it.Id > 1 AND it.Name == \"Test\""); - - // double match = matcher.IsMatch(input); - - // // Assert - // Assert.Equal(1.0, match); - //} + // Assert + Assert.Equal(1.0, match); + } [Fact] public void LinqMatcher_GetName() diff --git a/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs b/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs new file mode 100644 index 00000000..1bd1f59b --- /dev/null +++ b/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using NFluent; +using System.Linq.Dynamic.Core; +using WireMock.Util; +using Xunit; + +namespace WireMock.Net.Tests.Util +{ + public class JsonUtilsTests + { + [Fact] + public void JsonUtils_GenerateDynamicLinqStatement() + { + // Assign + var j = new JObject + { + {"U", new JValue(new Uri("http://localhost:80/abc?a=5"))}, + {"N", new JValue((object) null)}, + {"G", new JValue(Guid.NewGuid())}, + {"Flt", new JValue(10.0f)}, + {"Dbl", new JValue(Math.PI)}, + {"Check", new JValue(true)}, + {"Items", new JArray(new[] {new JValue(4), new JValue(8)})}, + { + "Child", new JObject + { + {"ChildId", new JValue(4)}, + {"ChildDateTime", new JValue(new DateTime(2018, 2, 17))}, + {"TS", new JValue(TimeSpan.FromMilliseconds(999))} + } + }, + {"Id", new JValue(9)}, + {"Name", new JValue("Test")} + }; + + // Act + string line = JsonUtils.GenerateDynamicLinqStatement(j); + + // Assert + var queryable = new[] {j}.AsQueryable().Select(line); + bool result = queryable.Any("Id > 4"); + Check.That(result).IsTrue(); + + Check.That(line).IsEqualTo("new (Uri(U) as U, null as N, Guid(G) as G, double(Flt) as Flt, double(Dbl) as Dbl, bool(Check) as Check, (new [] { int(Items[0]), int(Items[1])}) as Items, new (int(Child.ChildId) as ChildId, DateTime(Child.ChildDateTime) as ChildDateTime, TimeSpan(Child.TS) as TS) as Child, int(Id) as Id, string(Name) as Name)"); + } + + [Fact] + public void JsonUtils_GenerateDynamicLinqStatement_Throws() + { + // Assign + var j = new JObject + { + {"B", new JValue(new byte[] {48, 49})} + }; + + // Act and Assert + Check.ThatCode(() => JsonUtils.GenerateDynamicLinqStatement(j)).Throws(); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index ba2238a0..5b8b0eb2 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -24,7 +24,8 @@ - + +