Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/new fix and feature #29

Merged
merged 2 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
203 changes: 203 additions & 0 deletions src/WireMockInspector/CodeGenerators/CSharpFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace WireMockInspector.ViewModels;

internal static class CSharpFormatter
{
#region Reserved Keywords

private static readonly HashSet<string> CSharpReservedKeywords = new(new[]
{
"abstract",
"as",
"base",
"bool",
"break",
"byte",
"case",
"catch",
"char",
"checked",
"class",
"const",
"continue",
"decimal",
"default",
"delegate",
"do",
"double",
"else",
"enum",
"event",
"explicit",
"extern",
"false",
"finally",
"fixed",
"float",
"for",
"foreach",
"goto",
"if",
"implicit",
"in",
"int",
"interface",
"internal",
"is",
"lock",
"long",
"namespace",
"new",
"null",
"object",
"operator",
"out",
"override",
"params",
"private",
"protected",
"public",
"readonly",
"ref",
"return",
"sbyte",
"sealed",
"short",
"sizeof",
"stackalloc",
"static",
"string",
"struct",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"uint",
"ulong",
"unchecked",
"unsafe",
"ushort",
"using",
"virtual",
"void",
"volatile",
"while"
});

#endregion

private const string Null = "null";


public static string? TryToConvertJsonToAnonymousObject(object input, int ind = 0)
{
try
{
return input switch
{
JToken token => ConvertJsonToAnonymousObjectDefinition(token, ind),
string text => ConvertJsonToAnonymousObjectDefinition(JToken.Parse(text), ind),
_ => null
};
}
catch (Exception e)
{
return null;
}
}

public static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0)
{
return token switch
{
JArray jArray => FormatArray(jArray, ind),
JObject jObject => FormatObject(jObject, ind),
JValue jValue => jValue.Type switch
{
JTokenType.None => Null,
JTokenType.Integer => jValue.Value != null
? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value)
: Null,
JTokenType.Float => jValue.Value != null
? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value)
: Null,
JTokenType.String => ToCSharpStringLiteral(jValue.Value?.ToString()),
JTokenType.Boolean => jValue.Value != null
? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value).ToLower()
: Null,
JTokenType.Null => Null,
JTokenType.Undefined => Null,
JTokenType.Date when jValue.Value is DateTime dateValue =>
$"DateTime.Parse({ToCSharpStringLiteral(dateValue.ToString("s"))})",
_ => $"UNHANDLED_CASE: {jValue.Type}"
},
_ => $"UNHANDLED_CASE: {token}"
};
}

public static string ToCSharpStringLiteral(string? value)
{
if (string.IsNullOrEmpty(value))
{
return "\"\"";
}

if (value.Contains('\n'))
{
var escapedValue = value?.Replace("\"", "\"\"") ?? string.Empty;
return $"@\"{escapedValue}\"";
}
else
{
var escapedValue = value?.Replace("\"", "\\\"") ?? string.Empty;
return $"\"{escapedValue}\"";
}
}

public static string FormatPropertyName(string propertyName)
{
return CSharpReservedKeywords.Contains(propertyName) ? "@" + propertyName : propertyName;
}

private static string FormatObject(JObject jObject, int ind)
{

var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
var shouldBeDictionary = jObject.Properties().Any(x => Char.IsDigit(x.Name[0]));

if (shouldBeDictionary)
{
var items = jObject.Properties().Select(x => $"[\"{x.Name}\"] = {ConvertJsonToAnonymousObjectDefinition(x.Value, ind + 1)}");
return $"new Dictionary<string, object>\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
else
{
var items = jObject.Properties().Select(x => $"{FormatPropertyName(x.Name)} = {ConvertJsonToAnonymousObjectDefinition(x.Value, ind + 1)}");
return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}


}

private static 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)} }}";
}
}
149 changes: 149 additions & 0 deletions src/WireMockInspector/CodeGenerators/MappingCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.IO;
using System.Linq;
using System.Reflection;
using Fluid;
using Fluid.Values;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Admin.Requests;
using WireMockInspector.ViewModels;

namespace WireMockInspector.CodeGenerators;

public static class MappingCodeGenerator
{

class JsonDataSourceReader
{
object? ConvertJsonToObject(JToken xDocument)
{
return xDocument switch
{
JArray jArray => jArray.Select(ConvertJsonToObject).ToArray(),
JObject jObject => jObject.Properties().ToDictionary(x => x.Name, x => ConvertJsonToObject(x.Value)),
JValue jValue => jValue.Value,
_ => null
};
}

public object? Read(string content)
{
var json = JToken.Parse(content);

if (json is JObject jo && jo.ContainsKey("$schema"))
{
jo.Remove("$schema");
}

return ConvertJsonToObject(json);
}
}

public static string EscapeStringForCSharp(string value) => CSharpFormatter.ToCSharpStringLiteral(value);

private static string ReadEmbeddedResource(string resourceName)
{
// Get the current assembly
Assembly assembly = Assembly.GetExecutingAssembly();

// Using stream to read the embedded file.
using var stream = assembly.GetManifestResourceStream(resourceName);
// Make sure the resource is available
if (stream == null) throw new FileNotFoundException("The specified embedded resource cannot be found.", resourceName);
using StreamReader reader = new StreamReader(stream);
return reader.ReadToEnd();
}
public const string DefaultTemplateName = "(default)";
public static string GenerateCSharpCode(LogRequestModel logRequest, LogResponseModel logResponse, MappingCodeGeneratorConfigViewModel config)
{
var options = new TemplateOptions();
options.ValueConverters.Add(o => o is JToken t? t.ToString(): null );
options.Filters.AddFilter("escape_string_for_csharp", (input, arguments, templateContext) => new StringValue(EscapeStringForCSharp(input.ToStringValue()) ));
options.Filters.AddFilter("format_as_anonymous_object", (input, arguments, templateContext) =>
{
var ind = arguments.Values.FirstOrDefault() is NumberValue nv ? (int)nv.ToNumberValue() : 0;

return input switch
{
StringValue dv => CSharpFormatter.TryToConvertJsonToAnonymousObject(dv.ToStringValue(), ind) switch
{
{ } s => new StringValue(s),
_ => NilValue.Instance,
},
_ => input
};
});
var parser = new FluidParser();

var templateCode ="";
if (config.SelectedTemplate == DefaultTemplateName)
{
templateCode = ReadEmbeddedResource("WireMockInspector.CodeGenerators.default_template.liquid");
}
else if(string.IsNullOrWhiteSpace(config.SelectedTemplate) == false)
{
var templatePath = Path.Combine(PathHelper.GetTemplateDir(), config.SelectedTemplate);
if (File.Exists(templatePath))
{
templateCode = File.ReadAllText(templatePath);
}
}


if (parser.TryParse(templateCode, out var ftemplate, out var error))
{
var reader = new JsonDataSourceReader();

var data = reader.Read(JsonConvert.SerializeObject(
new
{
request = new
{
ClientIP = logRequest.ClientIP,
DateTime = logRequest.DateTime,
Path = logRequest.Path,
AbsolutePath = logRequest.AbsolutePath,
Url = logRequest.Url,
AbsoluteUrl = logRequest.AbsoluteUrl,
ProxyUrl = logRequest.ProxyUrl,
Query = logRequest.Query,
Method = logRequest.Method,
Headers = logRequest.Headers,
Cookies = logRequest.Cookies,
Body = logRequest.Body,
BodyAsJson = logRequest.BodyAsJson?.ToString(),
BodyAsBytes = logRequest.BodyAsBytes,
BodyEncoding = logRequest.BodyEncoding,
DetectedBodyType = logRequest.DetectedBodyType,
DetectedBodyTypeFromContentType = logRequest.DetectedBodyTypeFromContentType
},
response = new
{
StatusCode = logResponse.StatusCode,
Headers = logResponse.Headers,
BodyDestination = logResponse.BodyDestination,
Body = logResponse.Body,
BodyAsJson = logResponse.BodyAsJson?.ToString(),
BodyAsBytes = logResponse.BodyAsBytes,
BodyAsFile = logResponse.BodyAsFile,
BodyAsFileIsCached = logResponse.BodyAsFileIsCached,
BodyOriginal = logResponse.BodyOriginal,
BodyEncoding = logResponse.BodyEncoding,
DetectedBodyType = logResponse.DetectedBodyType,
DetectedBodyTypeFromContentType = logResponse.DetectedBodyTypeFromContentType,
FaultType = logResponse.FaultType,
FaultPercentage = logResponse.FaultPercentage
},
config
}));
var result = ftemplate.Render(new TemplateContext(new
{
data = data
}, options));
return result;
}

return error;

}
}