From f3c395833a5915c4ba66f66d0517990fa0abef3b Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 6 Sep 2018 19:25:09 +0200 Subject: [PATCH 1/4] LinqMatcher and JsonUtils --- src/WireMock.Net/Matchers/LinqMatcher.cs | 44 ++++--- src/WireMock.Net/Util/JsonUtils.cs | 121 +++++++++++++++++- src/WireMock.Net/WireMock.Net.csproj | 2 +- .../Matchers/LinqMatcherTests.cs | 65 +++++----- .../WireMock.Net.Tests/Util/JsonUtilsTests.cs | 62 +++++++++ .../WireMock.Net.Tests.csproj | 3 +- 6 files changed, 239 insertions(+), 58 deletions(-) create mode 100644 test/WireMock.Net.Tests/Util/JsonUtilsTests.cs diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index b741c5f9..371ad9a6 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; // valueAsJObject.ToObject(); + 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/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs index 36178f62..a56d7969 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,121 @@ namespace WireMock.Util { public static T ParseJTokenToObject(object value) { - if (value == null) + switch (value) { - return default(T); - } + case JToken tokenValue: + return tokenValue.ToObject(); - return !(value is JToken token) ? default(T) : token.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) + { + var childLines = 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, childLines); + } + text.Append(string.Join(", ", childLines)); + text.Append(")"); + + if (!string.IsNullOrEmpty(propertyName)) + { + text.AppendFormat(" as {0}", propertyName); + } + + lines.Add(text.ToString()); + } + else if (node.Type == JTokenType.Array) + { + 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()); + } + else + { + 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}'."); + } + + if (!string.IsNullOrEmpty(propertyName)) + { + castText += $" as {propertyName}"; + } + + lines.Add(castText); + } } } } \ No newline at end of file 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 @@ - + + From 39b1eb8f43a9668fb616d1a7137ee886f55a0f76 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 6 Sep 2018 19:27:38 +0200 Subject: [PATCH 2/4] JsonUtils : update error message --- src/WireMock.Net/Util/JsonUtils.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs index a56d7969..c35a4984 100644 --- a/src/WireMock.Net/Util/JsonUtils.cs +++ b/src/WireMock.Net/Util/JsonUtils.cs @@ -40,6 +40,7 @@ namespace WireMock.Util { WalkNode(child.Value, child.Path, child.Name, childLines); } + text.Append(string.Join(", ", childLines)); text.Append(")"); @@ -115,7 +116,7 @@ namespace WireMock.Util break; default: - throw new NotSupportedException($"JTokenType '{node.Type}'."); + throw new NotSupportedException($"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator."); } if (!string.IsNullOrEmpty(propertyName)) From 33b96c6af908cbcfb2fb7fe76fed3e676dcbf8fb Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 6 Sep 2018 21:00:33 +0200 Subject: [PATCH 3/4] Fix Sonar issues --- src/WireMock.Net/Matchers/LinqMatcher.cs | 2 +- src/WireMock.Net/Util/JsonUtils.cs | 190 ++++++++++++----------- 2 files changed, 104 insertions(+), 88 deletions(-) diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index 371ad9a6..d8f3e2d2 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net/Matchers/LinqMatcher.cs @@ -72,7 +72,7 @@ namespace WireMock.Matchers switch (input) { case JObject valueAsJObject: - value = valueAsJObject; // valueAsJObject.ToObject(); + value = valueAsJObject; break; default: diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs index c35a4984..f2aae675 100644 --- a/src/WireMock.Net/Util/JsonUtils.cs +++ b/src/WireMock.Net/Util/JsonUtils.cs @@ -32,100 +32,116 @@ namespace WireMock.Util { if (node.Type == JTokenType.Object) { - var childLines = 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, childLines); - } - - text.Append(string.Join(", ", childLines)); - text.Append(")"); - - if (!string.IsNullOrEmpty(propertyName)) - { - text.AppendFormat(" as {0}", propertyName); - } - - lines.Add(text.ToString()); + ProcessObject(node, propertyName, lines); } else if (node.Type == JTokenType.Array) { - 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()); + ProcessArray(node, propertyName, lines); } else { - 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); + 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); + } + + 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 From 913f60599362824b019e705eaff81c2db97e1a67 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 7 Sep 2018 08:59:09 +0200 Subject: [PATCH 4/4] Move PortUtils.cs --- src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 8 ++++---- src/WireMock.Net/Owin/OwinSelfHost.cs | 3 ++- src/WireMock.Net/Server/FluentMockServer.cs | 3 ++- src/WireMock.Net/{Http/PortUtil.cs => Util/PortUtils.cs} | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) rename src/WireMock.Net/{Http/PortUtil.cs => Util/PortUtils.cs} (95%) 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/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);