From 7d9e45081439da02750ba2dcbf05a0bab6d4fc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20Pi=C4=85tek?= Date: Sun, 7 May 2023 14:37:48 +0200 Subject: [PATCH] C# code generator improvements (#933) * Escape quotes in generated C# strings * Handle response with JSON body in C# code generator --- .../Serialization/MappingConverter.cs | 66 ++++++++++++++++++- ...eMockAdminApi_GetMappingsCode.verified.txt | 29 ++++++++ .../WireMockAdminApiTests.cs | 4 +- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 7b55033c..6731eaf0 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -5,6 +5,7 @@ using System.Net; using System.Text; using System.Threading; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Constants; @@ -143,13 +144,26 @@ internal class MappingConverter } } - if (response.ResponseMessage.BodyData is { }) + if (response.ResponseMessage.BodyData is { } bodyData) { switch (response.ResponseMessage.BodyData.DetectedBodyType) { case BodyType.String: case BodyType.FormUrlEncoded: - sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")"); + sb.AppendLine($" .WithBody(\"{EscapeCSharpString(bodyData.BodyAsString)}\")"); + break; + case BodyType.Json: + if (bodyData.BodyAsJson is string bodyStringValue) + { + sb.AppendLine($" .WithBody(\"{EscapeCSharpString(bodyStringValue)}\")"); + } + else + { + var serializedBody = JsonConvert.SerializeObject(bodyData.BodyAsJson); + var deserializedBody = JToken.Parse(serializedBody); + sb.AppendLine($" .WithBodyAsJson({ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2)})"); + } + break; } } @@ -173,6 +187,50 @@ internal class MappingConverter return sb.ToString(); } + private string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0) + { + string FormatObject(JObject jObject) + { + var indStr = new string(' ', 4 * ind); + var indStrSub = new string(' ', 4 * (ind + 1)); + var items = jObject.Properties().Select(x => ConvertJsonToAnonymousObjectDefinition(x, ind + 1)); + return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}"; + } + + string FormatArray(JArray jArray, int ind) + { + var hasComplexItems = jArray.FirstOrDefault() is JObject or JArray; + var items = jArray.Select(x => ConvertJsonToAnonymousObjectDefinition(x, hasComplexItems ? ind + 1 : ind)); + if (hasComplexItems) + { + var indStr = new string(' ', 4 * ind); + var indStrSub = new string(' ', 4 * (ind + 1)); + return $"new []\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}"; + } + + return $"new [] {{ {string.Join(", ", items)} }}"; + } + + return token switch + { + JArray jArray => FormatArray(jArray, ind), + JObject jObject => FormatObject(jObject), + JProperty jProperty => $"{jProperty.Name} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}", + JValue jValue => jValue.Type switch + { + JTokenType.None => "null", + JTokenType.Integer => jValue.Value?.ToString() ?? "null", + JTokenType.Float => jValue.Value?.ToString() ?? "null", + JTokenType.String => $"\"{EscapeCSharpString(jValue.Value?.ToString())}\"", + JTokenType.Boolean => jValue.Value?.ToString().ToLower() ?? "null", + JTokenType.Null => "null", + JTokenType.Undefined => "null", + _ => $"UNHANDLED_CASE: {jValue.Type}" + }, + _ => $"UNHANDLED_CASE: {token}" + }; + } + public MappingModel ToMappingModel(IMapping mapping) { var request = (Request)mapping.RequestMatcher; @@ -445,9 +503,11 @@ internal class MappingConverter private static string ToValueArguments(string[]? values, string defaultValue = "") { - return values is { } ? string.Join(", ", values.Select(v => $"\"{v}\"")) : $"\"{defaultValue}\""; + return values is { } ? string.Join(", ", values.Select(v => $"\"{EscapeCSharpString(v)}\"")) : $"\"{EscapeCSharpString(defaultValue)}\""; } + private static string? EscapeCSharpString(string? value) => value?.Replace("\"", "\\\""); + private static WebProxyModel? MapWebProxy(WebProxySettings? settings) { return settings != null ? new WebProxyModel diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt index 1f3fa24b..50764a68 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt @@ -16,11 +16,13 @@ server .UsingMethod("POST") .WithPath("/foo2") .WithParam("p2", "abc") + .WithHeader("h1", "W/\"234f2q3r\"", true) ) .WithGuid("1b731398-4a5b-457f-a6e3-d65e541c428f") .RespondWith(Response.Create() .WithStatusCode("201") .WithHeader("hk", "hv") + .WithHeader("ETag", "W/\"168d8e\"") .WithBody("2") ); @@ -32,5 +34,32 @@ server .WithGuid("f74fd144-df53-404f-8e35-da22a640bd5f") .RespondWith(Response.Create() .WithStatusCode(208) + .WithBodyAsJson(new + { + a = 1, + b = 1.2, + d = true, + e = false, + f = new [] { 1, 2, 3, 4 }, + g = new + { + z1 = 1, + z2 = 2, + z3 = new [] { "a", "b", "c" }, + z4 = new [] + { + new + { + a = 1, + b = 2 + }, + new + { + a = 2, + b = 3 + } + } + } + }) ); diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs index f923648a..d8be86e4 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs @@ -740,6 +740,7 @@ public class WireMockAdminApiTests Request.Create() .WithPath("/foo2") .WithParam("p2", "abc") + .WithHeader("h1", "W/\"234f2q3r\"") .UsingPost() ) .WithGuid(guid2) @@ -747,6 +748,7 @@ public class WireMockAdminApiTests Response.Create() .WithStatusCode("201") .WithHeader("hk", "hv") + .WithHeader("ETag", "W/\"168d8e\"") .WithBody("2") ); @@ -760,7 +762,7 @@ public class WireMockAdminApiTests .RespondWith( Response.Create() .WithStatusCode(HttpStatusCode.AlreadyReported) - .WithBodyAsJson(new { x = 1 }) + .WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}} }) ); // Act