Swagger support (#749)

* r

* fix

* sw

* x

* s

* .

* .

* .

* CreateTypeFromJObject

* .

* .

* f

* c

* .

* .

* .

* .

* .

* .

* ok

* ,

* .

* .

* .

* .

* n

* pact

* fix

* schema

* null

* fluent

* r

* -p

* .

* .

* refs

* .
This commit is contained in:
Stef Heyenrath
2022-05-13 22:01:46 +02:00
committed by GitHub
parent 0d8b3b1438
commit 5e301fd74b
45 changed files with 2371 additions and 1123 deletions

View File

@@ -5,18 +5,49 @@ using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Pact.Models.V2;
using WireMock.Serialization;
namespace WireMock.Util;
internal static class JsonUtils
{
public static bool TryParseAsComplexObject(string strInput, [NotNullWhen(true)] out JToken? token)
public static Type CreateTypeFromJObject(JObject instance, string? fullName = null)
{
token = null;
static Type ConvertType(JToken value, string? propertyName = null)
{
var type = value.Type;
return type switch
{
JTokenType.Array => value.HasValues ? ConvertType(value.First!, propertyName).MakeArrayType() : typeof(object).MakeArrayType(),
JTokenType.Boolean => typeof(bool),
JTokenType.Bytes => typeof(byte[]),
JTokenType.Date => typeof(DateTime),
JTokenType.Guid => typeof(Guid),
JTokenType.Float => typeof(float),
JTokenType.Integer => typeof(long),
JTokenType.Null => typeof(object),
JTokenType.Object => CreateTypeFromJObject((JObject)value, propertyName),
JTokenType.String => typeof(string),
JTokenType.TimeSpan => typeof(TimeSpan),
JTokenType.Uri => typeof(string),
_ => typeof(object)
};
}
if (string.IsNullOrWhiteSpace(strInput))
var properties = new Dictionary<string, Type>();
foreach (var item in instance.Properties())
{
properties.Add(item.Name, ConvertType(item.Value, item.Name));
}
return TypeBuilderUtils.BuildType(properties, fullName) ?? throw new InvalidOperationException();
}
public static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
{
value = null;
if (strInput == null || string.IsNullOrWhiteSpace(strInput))
{
return false;
}
@@ -30,7 +61,7 @@ internal static class JsonUtils
try
{
// Try to convert this string into a JToken
token = JToken.Parse(strInput);
value = JObject.Parse(strInput);
return true;
}
catch
@@ -105,17 +136,19 @@ internal static class JsonUtils
private static void WalkNode(JToken node, string? path, string? propertyName, List<string> lines)
{
if (node.Type == JTokenType.Object)
switch (node.Type)
{
ProcessObject(node, propertyName, lines);
}
else if (node.Type == JTokenType.Array)
{
ProcessArray(node, propertyName, lines);
}
else
{
ProcessItem(node, path ?? "it", propertyName, lines);
case JTokenType.Object:
ProcessObject(node, propertyName, lines);
break;
case JTokenType.Array:
ProcessArray(node, propertyName, lines);
break;
default:
ProcessItem(node, path ?? "it", propertyName, lines);
break;
}
}
@@ -125,7 +158,7 @@ internal static class JsonUtils
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<JProperty>().ToArray())
foreach (var child in node.Children<JProperty>().ToArray())
{
WalkNode(child.Value, child.Path, child.Name, items);
}
@@ -147,8 +180,8 @@ internal static class JsonUtils
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())
var idx = 0;
foreach (var child in node.Children().ToArray())
{
WalkNode(child, $"{node.Path}[{idx}]", null, items);
idx++;
@@ -165,50 +198,21 @@ internal static class JsonUtils
lines.Add(text.ToString());
}
private static void ProcessItem(JToken node, string path, string propertyName, List<string> lines)
private static void ProcessItem(JToken node, string path, string? propertyName, List<string> lines)
{
string castText;
switch (node.Type)
var castText = node.Type switch
{
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 = $"long({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.");
}
JTokenType.Boolean => $"bool({path})",
JTokenType.Date => $"DateTime({path})",
JTokenType.Float => $"double({path})",
JTokenType.Guid => $"Guid({path})",
JTokenType.Integer => $"long({path})",
JTokenType.Null => "null",
JTokenType.String => $"string({path})",
JTokenType.TimeSpan => $"TimeSpan({path})",
JTokenType.Uri => $"Uri({path})",
_ => throw new NotSupportedException($"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator.")
};
if (!string.IsNullOrEmpty(propertyName))
{

View File

@@ -0,0 +1,8 @@
using System.Reflection;
namespace WireMock.Util;
internal static class SystemUtils
{
public static readonly string Version = typeof(SystemUtils).GetTypeInfo().Assembly.GetName().Version.ToString();
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace WireMock.Util;
/// <summary>
/// Code based on https://stackoverflow.com/questions/40507909/convert-jobject-to-anonymous-object
/// </summary>
internal static class TypeBuilderUtils
{
private static readonly ConcurrentDictionary<IDictionary<string, Type>, Type> Types = new();
private static readonly ModuleBuilder ModuleBuilder =
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("WireMock.Net.Reflection.Module");
public static Type BuildType(IDictionary<string, Type> properties, string? name = null)
{
var keyExists = Types.Keys.FirstOrDefault(k => Compare(k, properties));
if (keyExists != null)
{
return Types[keyExists];
}
var typeBuilder = GetTypeBuilder(name ?? Guid.NewGuid().ToString());
foreach (var property in properties)
{
CreateGetSetMethods(typeBuilder, property.Key, property.Value);
}
var type = typeBuilder.CreateTypeInfo().AsType();
Types.TryAdd(properties, type);
return type;
}
/// <summary>
/// https://stackoverflow.com/questions/3804367/testing-for-equality-between-dictionaries-in-c-sharp
/// </summary>
private static bool Compare<TKey, TValue>(IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2)
{
if (dict1 == dict2)
{
return true;
}
if (dict1.Count != dict2.Count)
{
return false;
}
var valueComparer = EqualityComparer<TValue>.Default;
foreach (var kvp in dict1)
{
if (!dict2.TryGetValue(kvp.Key, out var value2))
{
return false;
}
if (!valueComparer.Equals(kvp.Value, value2))
{
return false;
}
}
return true;
}
private static TypeBuilder GetTypeBuilder(string name)
{
return ModuleBuilder.DefineType(name,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
}
private static void CreateGetSetMethods(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
var getPropertyMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
var getIl = getPropertyMethodBuilder.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
var setPropertyMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType });
var setIl = setPropertyMethodBuilder.GetILGenerator();
var modifyProperty = setIl.DefineLabel();
var exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropertyMethodBuilder);
propertyBuilder.SetSetMethod(setPropertyMethodBuilder);
}
}