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

Random.GenerateAsOutputWithType #83

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,4 @@
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions src/Handlebars.Net.Helpers.Core/Utils/SimpleJsonUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using HandlebarsDotNet.Helpers.Json;

namespace HandlebarsDotNet.Helpers.Utils;

public static class SimpleJsonUtils
{
public static string? SerializeObject(object? value)
{
return SimpleJson.SerializeObject(value);
}

public static T? DeserializeObject<T>(string? json)
{
return SimpleJson.DeserializeObject<T>(json);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.17" />
</ItemGroup>

Expand Down
73 changes: 59 additions & 14 deletions src/Handlebars.Net.Helpers.Random/Helpers/RandomHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
using HandlebarsDotNet.Helpers.Attributes;
using HandlebarsDotNet.Helpers.Enums;
using HandlebarsDotNet.Helpers.Helpers;
using HandlebarsDotNet.Helpers.Models;
using HandlebarsDotNet.Helpers.Parsers;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;

// ReSharper disable once CheckNamespace
namespace HandlebarsDotNet.Helpers;

internal class RandomHelpers : BaseHelpers, IHelpers
Expand All @@ -19,14 +21,39 @@ public RandomHelpers(IHandlebars context) : base(context)
/// <summary>
/// For backwards compatibility with WireMock.Net
/// </summary>
[HandlebarsWriter(WriterType.Value, "Random")]
[HandlebarsWriter(WriterType.String, "Random")]
public object? Random(IDictionary<string, object?> hash)
{
return Generate(hash);
}

/// <summary>
/// For backwards compatibility with WireMock.Net
/// </summary>
[HandlebarsWriter(WriterType.String, "RandomKeepType")]
public string? RandomKeepType(IDictionary<string, object?> hash)
{
return GenerateAsOutputWithType(hash);
}

[HandlebarsWriter(WriterType.Value)]
public object? Generate(IDictionary<string, object?> hash)
{
var keepType = hash.TryGetValue("KeepType", out var value) && value is true;

return GenerateInternal(hash, keepType);
}

/// <summary>
/// It returns a JSON string.
/// </summary>
[HandlebarsWriter(WriterType.String)]
public string? GenerateAsOutputWithType(IDictionary<string, object?> hash)
{
return GenerateInternal(hash, true) is not OutputWithType outputWithType ? null : outputWithType.Serialize();
}

private object? GenerateInternal(IDictionary<string, object?> hash, bool outputWithType)
{
var fieldOptions = GetFieldOptionsFromHash(hash);
dynamic randomizer = RandomizerFactory.GetRandomizerAsDynamic(fieldOptions);
Expand All @@ -35,34 +62,52 @@ public RandomHelpers(IHandlebars context) : base(context)
if (fieldOptions is IFieldOptionsDateTime)
{
DateTime? date = randomizer.Generate();
return date?.ToString("s", Context.Configuration.FormatProvider);
return GetRandomValue(outputWithType, () => date?.ToString("s", Context.Configuration.FormatProvider), () => date);
}

// If the IFieldOptionsGuid defines Uppercase, use the 'GenerateAsString' method.
if (fieldOptions is IFieldOptionsGuid fieldOptionsGuid)
{
return fieldOptionsGuid.Uppercase ? randomizer.GenerateAsString() : randomizer.Generate();
return GetRandomValue(outputWithType,
() => fieldOptionsGuid.Uppercase ? randomizer.GenerateAsString() : randomizer.Generate(),
() => randomizer.Generate()
);
}

return randomizer.Generate();
return GetRandomValue(outputWithType, () => randomizer.Generate(), null);
}

private FieldOptionsAbstract GetFieldOptionsFromHash(IDictionary<string, object?> hash)
{
if (hash.TryGetValue("Type", out var value) && value is string randomType)
if (!hash.TryGetValue("Type", out var value) || value is not string randomType)
{
throw new HandlebarsException("The Type argument is missing.");
}

var newProperties = new Dictionary<string, object?>();
foreach (var item in hash.Where(p => p.Key != "Type"))
{
var newProperties = new Dictionary<string, object?>();
foreach (var item in hash.Where(p => p.Key != "Type"))
{
bool convertObjectArrayToStringList = randomType == "StringList";
var parsedArgumentValue = ArgumentsParser.Parse(Context, item.Value, convertObjectArrayToStringList);
var convertObjectArrayToStringList = randomType == "StringList";
var parsedArgumentValue = ArgumentsParser.Parse(Context, item.Value, convertObjectArrayToStringList);

newProperties.Add(item.Key, parsedArgumentValue);
}

newProperties.Add(item.Key, parsedArgumentValue);
}
return FieldOptionsFactory.GetFieldOptions(randomType, newProperties!);
}

return FieldOptionsFactory.GetFieldOptions(randomType, newProperties!);
private static object? GetRandomValue(bool outputWithType, Func<object?> funcNormal, Func<object?>? funcWithType)
{
object? value;
if (outputWithType && funcWithType != null)
{
value = funcWithType();
}
else
{
value = funcNormal();
}

throw new HandlebarsException("The Type argument is missing.");
return outputWithType ? OutputWithType.Parse(value) : value;
}
}
7 changes: 6 additions & 1 deletion src/Handlebars.Net.Helpers/HandlebarsHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,12 @@ private static void RegisterValueHelper(IHandlebars handlebarsContext, object in
{
HandlebarsReturnWithOptionsHelper helper = (in HelperOptions options, in Context context, in Arguments arguments) =>
{
return InvokeMethod(passContext ? context : null, false, handlebarsContext, helperName, methodInfo, arguments, instance, options);
var value = InvokeMethod(passContext ? context : null, false, handlebarsContext, helperName, methodInfo, arguments, instance, options);

var keepType = arguments.Hash.TryGetValue("KeepType", out var keepTypeValue) &&
(keepTypeValue is true || keepTypeValue is string keepTypeAsString && string.Equals(keepTypeAsString, bool.TrueString, StringComparison.OrdinalIgnoreCase));

return keepType ? OutputWithType.Parse(value).Serialize() : value;
};

handlebarsContext.RegisterHelper(helperName, helper);
Expand Down
196 changes: 196 additions & 0 deletions src/Handlebars.Net.Helpers/Models/OutputWithType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using HandlebarsDotNet.Helpers.Json;
using HandlebarsDotNet.Helpers.Utils;

namespace HandlebarsDotNet.Helpers.Models;

public class OutputWithType
{
public object? Value { get; set; }

public string? TypeName { get; set; }

public string? FullTypeName { get; set; }

public static OutputWithType Parse(object? value)
{
var typeName = value?.GetType().Name;
var fullTypeName = value?.GetType().FullName;

if (value is IEnumerable<object> array)
{
value = ArrayUtils.ToArray(array);
}

Check warning on line 25 in src/Handlebars.Net.Helpers/Models/OutputWithType.cs

View check run for this annotation

Codecov / codecov/patch

src/Handlebars.Net.Helpers/Models/OutputWithType.cs#L23-L25

Added lines #L23 - L25 were not covered by tests

return new OutputWithType
{
Value = value,
FullTypeName = fullTypeName,
TypeName = typeName
};
}

public override string ToString()
{
return Serialize() ?? string.Empty;
}

public string? Serialize()
{
return SimpleJsonUtils.SerializeObject(this);
}

public static OutputWithType Deserialize(string? json)
{
var jsonObject = SimpleJsonUtils.DeserializeObject<JsonObject>(json);

if (jsonObject == null)
{
throw new InvalidOperationException();
}

if (!TryGetValue(jsonObject, out var value))
{
throw new MissingMemberException($"Property '{nameof(Value)}' is not found on type '{nameof(OutputWithType)}'.");
}

if (!TryGetTypeName(jsonObject, out var typeName))
{
throw new MissingMemberException($"Property '{nameof(TypeName)}' is not found on type '{nameof(OutputWithType)}'.");
}

if (!TryGetFullTypeName(jsonObject, out var fullTypeName))
{
throw new MissingMemberException($"Property '{nameof(FullTypeName)}' is not found on type '{nameof(OutputWithType)}'.");
}

if (!TryGetType(fullTypeName, out var fullType))
{
throw new TypeLoadException($"Unable to load Type with FullTypeName '{fullTypeName}'.");
}

return new OutputWithType
{
Value = TryConvert(value, fullType, out var convertedValue) ? convertedValue : value,
TypeName = typeName,
FullTypeName = fullTypeName
};
}

public static bool TryDeserialize(string? json, [NotNullWhen(true)] out OutputWithType? outputWithType)
{
JsonObject? jsonObject;
try
{
jsonObject = SimpleJsonUtils.DeserializeObject<JsonObject>(json);
}
catch
{
outputWithType = null;
return false;
}

if (jsonObject != null &&
TryGetValue(jsonObject, out var value) &&
TryGetTypeName(jsonObject, out var typeName) &&
TryGetFullTypeName(jsonObject, out var fullTypeName) &&
TryGetType(fullTypeName, out var fullType)
)
{
outputWithType = new OutputWithType
{
Value = TryConvert(value, fullType, out var convertedValue) ? convertedValue : value,
TypeName = typeName,
FullTypeName = fullTypeName
};
return true;
}

outputWithType = default;
return false;
}

private static bool TryGetValue(JsonObject jsonObject, out object value)
{
return jsonObject.TryGetValue(nameof(Value), out value);
}

private static bool TryGetTypeName(JsonObject jsonObject, [NotNullWhen(true)] out string? value)
{
if (jsonObject.TryGetValue(nameof(TypeName), out var typeName) && typeName is string typeNameAsString)
{
value = typeNameAsString;
return true;
}

value = default;
return false;
}

private static bool TryGetFullTypeName(JsonObject jsonObject, [NotNullWhen(true)] out string? value)
{
if (jsonObject.TryGetValue(nameof(FullTypeName), out var fullTypeName) && fullTypeName is string fullTypeNameAsString)
{
value = fullTypeNameAsString;
return true;
}

value = default;
return false;
}

private static bool TryGetType(string fullTypeName, [NotNullWhen(true)] out Type? type)
{
type = Type.GetType(fullTypeName) ?? Type.GetType($"{fullTypeName}, System");
return type != null;
}

private static bool TryConvert(object? value, Type fullType, [NotNullWhen(true)] out object? result)
{
try
{
if (fullType == typeof(Guid) && value is string guidAsString)
{
result = new Guid(guidAsString);
return true;
}

if (fullType == typeof(Uri) && value is string uriAsString)
{
result = new Uri(uriAsString);
return true;
}

if (fullType == typeof(TimeSpan) && value is JsonObject timeSpanAsJsonObject)
{
result = TimeSpan.FromTicks((long)timeSpanAsJsonObject["Ticks"]);
return true;
}

if (fullType.IsArray && value is JsonArray jsonArray)
{
var elementType = fullType.GetElementType()!;
var newArray = Array.CreateInstance(elementType, jsonArray.Count);
for (var i = 0; i < jsonArray.Count; i++)
{
newArray.SetValue(Convert.ChangeType(jsonArray[i], elementType), i);
}

result = newArray;
}
else
{
result = Convert.ChangeType(value, fullType);
}

return true;
}
catch
{
result = default;
return false;
}
}
}
Loading