Skip to content

Commit

Permalink
When ThrowOnUnresolvedBindingExpression is False, do not throw when U…
Browse files Browse the repository at this point in the history
…ndefinedBindingResult but use default value (#95)

* When ThrowOnUnresolvedBindingExpression is False, do not throw when UndefinedBindingResult but use default value

* tst

* tst

* Equal_WhenInputIsUnresolvedBinding_And_ThrowOnUnresolvedBindingExpressionIsFalse_ShouldReturnEmpty
Equal_WhenInputIsUnresolvedBinding_And_ThrowOnUnresolvedBindingExpressionIsTrue_ShouldThrow
  • Loading branch information
StefH authored May 12, 2024
1 parent 588ef30 commit aa84cf0
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 33 deletions.
36 changes: 26 additions & 10 deletions src/Handlebars.Net.Helpers.Core/Parsers/ArgumentsParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,56 @@ public static class ArgumentsParser

public static List<object?> Parse(IHandlebars context, ParameterInfo[] parameters, Arguments arguments)
{
var result = new List<object?>();
var result = new List<(object? Argument, object? DefaultValue)>();

for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].IsParam())
{
result.Add(arguments.Skip(i).ToArray());
result.Add((arguments.Skip(i).ToArray(), DefaultValueCache.GetDefaultValue(parameters[i].ParameterType)));
}
else if (i < arguments.Length)
{
result.Add(arguments[i]);
var defaultValue = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : DefaultValueCache.GetDefaultValue(parameters[i].ParameterType);
result.Add((arguments[i], defaultValue));
}
}

return result.Select(argument => Parse(argument)).ToList();
return result.Select(x => Parse(context, x.Argument, x.DefaultValue)).ToList();
}

public static object? Parse(object? argument, bool convertObjectArrayToStringList = false)
public static object? Parse(IHandlebars context, object? argument, object? defaultValue, bool convertObjectArrayToStringList = false)
{
if (argument is UndefinedBindingResult valueAsUndefinedBindingResult &&
TryParseUndefinedBindingResult(valueAsUndefinedBindingResult, out var parsedAsObjectList))
if (argument is UndefinedBindingResult argumentAsUndefinedBindingResult)
{
if (convertObjectArrayToStringList)
if (TryParseUndefinedBindingResult(argumentAsUndefinedBindingResult, out var parsedAsObjectList))
{
return parsedAsObjectList.Cast<string?>().ToList();
if (convertObjectArrayToStringList)
{
return parsedAsObjectList.Cast<string?>().ToList();
}

return parsedAsObjectList;
}

return parsedAsObjectList;
// In case it's an UndefinedBindingResult and ThrowOnUnresolvedBindingExpression is set to false, return the default value.
if (!context.Configuration.ThrowOnUnresolvedBindingExpression)
{
return defaultValue;
}
}

return argument;
}

public static object ParseAsIntLongOrDouble(IHandlebars context, object? value)
{
// In case the value is null and ThrowOnUnresolvedBindingExpression is set to false, return the default value (0).
if (value is null && !context.Configuration.ThrowOnUnresolvedBindingExpression)
{
return 0;
}

var parsedValue = StringValueParser.Parse(context, value as string ?? value?.ToString() ?? string.Empty);

if (SupportedTypes.Contains(parsedValue.GetType()))
Expand Down
33 changes: 16 additions & 17 deletions src/Handlebars.Net.Helpers.Core/Parsers/StringValueParser.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
using System.Globalization;

namespace HandlebarsDotNet.Helpers.Parsers
namespace HandlebarsDotNet.Helpers.Parsers;

public static class StringValueParser
{
public static class StringValueParser
public static object Parse(IHandlebars context, string valueAsString)
{
public static object Parse(IHandlebars context, string valueAsString)
if (int.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out var valueAsInt))
{
if (int.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out int valueAsInt))
{
return valueAsInt;
}
return valueAsInt;
}

if (long.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out long valueAsLong))
{
return valueAsLong;
}
if (long.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out var valueAsLong))
{
return valueAsLong;
}

if (double.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out double valueAsDouble))
{
return valueAsDouble;
}

return valueAsString;
if (double.TryParse(valueAsString, NumberStyles.Any, context.Configuration.FormatProvider, out var valueAsDouble))
{
return valueAsDouble;
}

return valueAsString;
}
}
36 changes: 36 additions & 0 deletions src/Handlebars.Net.Helpers.Core/Utils/DefaultValueCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Concurrent;
using System.Reflection;

namespace HandlebarsDotNet.Helpers.Utils;

internal static class DefaultValueCache
{
private static readonly ConcurrentDictionary<Type, object?> Cache = new();

public static object? GetDefaultValue(Type type)
{
// Try to get the value from the cache
if (!Cache.TryGetValue(type, out var value))
{
// Value not found in cache, compute and add to cache
value = CreateDefaultValue(type);
Cache.TryAdd(type, value);
}

return value;
}

private static object? CreateDefaultValue(Type type)
{
// Uses reflection to create a generic method call
return typeof(DefaultValueCreator<>)
.MakeGenericType(type)
.GetMethod("GetDefault")!
.Invoke(null, null);
}

private static class DefaultValueCreator<T>
{
public static T? GetDefault() => default(T);
}
}
10 changes: 5 additions & 5 deletions src/Handlebars.Net.Helpers.Random/Helpers/RandomHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,20 @@ public RandomHelpers(IHandlebars context) : base(context)
return randomizer.Generate();
}

private static FieldOptionsAbstract GetFieldOptionsFromHash(IDictionary<string, object?> hash)
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 string randomTypeAsString)
{
var newProperties = new Dictionary<string, object?>();
foreach (var item in hash.Where(p => p.Key != "Type"))
{
bool convertObjectArrayToStringList = randomType == "StringList";
var parsedArgumentValue = ArgumentsParser.Parse(item.Value, convertObjectArrayToStringList);
bool convertObjectArrayToStringList = randomTypeAsString == "StringList";
var parsedArgumentValue = ArgumentsParser.Parse(Context, item.Value, default(string), convertObjectArrayToStringList);

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

return FieldOptionsFactory.GetFieldOptions(randomType, newProperties!);
return FieldOptionsFactory.GetFieldOptions(randomTypeAsString, newProperties!);
}

throw new HandlebarsException("The Type argument is missing.");
Expand Down
2 changes: 1 addition & 1 deletion src/Handlebars.Net.Helpers/Helpers/StringHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public string Capitalize(string value)
[HandlebarsWriter(WriterType.Value)]
public object? Coalesce(params object?[] arguments)
{
foreach (var arg in arguments.Where(a => a is { } and not UndefinedBindingResult))
foreach (var arg in arguments.Where(a => a is not null and not UndefinedBindingResult))
{
if (arg is string stringValue)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Globalization;
using FluentAssertions;
using HandlebarsDotNet.Helpers.Parsers;
using Moq;
using Xunit;

namespace HandlebarsDotNet.Helpers.Tests.Helpers.Parsers;

public class StringValueParserTests
{
private readonly Mock<IHandlebars> _handlebarsContext;

public StringValueParserTests()
{
var configuration = new HandlebarsConfiguration
{
FormatProvider = CultureInfo.InvariantCulture
};

_handlebarsContext = new Mock<IHandlebars>();
_handlebarsContext.SetupGet(c => c.Configuration).Returns(configuration);
}

[Theory]
[InlineData("123", 123)]
[InlineData("-1", -1)]
[InlineData("2147483647", int.MaxValue)]
[InlineData("-2147483648", int.MinValue)]
public void Parse_ShouldReturnInt_WhenInputIsInt(string input, int expected)
{
var result = StringValueParser.Parse(_handlebarsContext.Object, input);
result.Should().BeOfType<int>().And.Be(expected);
}

[Theory]
[InlineData("9223372036854775807", long.MaxValue)]
[InlineData("-9223372036854775808", long.MinValue)]
public void Parse_ShouldReturnLong_WhenInputIsLong(string input, long expected)
{
var result = StringValueParser.Parse(_handlebarsContext.Object, input);
result.Should().BeOfType<long>().And.Be(expected);
}

[Theory]
[InlineData("123.456", 123.456)]
[InlineData("0.0001", 0.0001)]
[InlineData("-1.42", -1.42)]
public void Parse_ShouldReturnDouble_WhenInputIsDouble(string input, double expected)
{
var result = StringValueParser.Parse(_handlebarsContext.Object, input);
result.Should().BeOfType<double>().And.Be(expected);
}

[Theory]
[InlineData("")]
[InlineData("test")]
[InlineData("123test")]
public void Parse_ShouldReturnString_WhenInputIsNonNumericString(string input)
{
var result = StringValueParser.Parse(_handlebarsContext.Object, input);
result.Should().BeOfType<string>().And.Be(input);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ public void Add(string template, string expected)
result.Should().StartWith(expected);
}

[Fact]
public void Add_ThrowOnUnresolvedBindingExpression_False()
{
// Arrange
var handlebarsContext = Handlebars.Create();
handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture;
handlebarsContext.Configuration.ThrowOnUnresolvedBindingExpression = false;

HandlebarsHelpers.Register(handlebarsContext, Category.Math);
var action = handlebarsContext.Compile("{{[Math.Add] viewData.page 42}}");
var viewData = new
{
abc = "xyz"
};

// Act
var result = action(viewData);

// Assert 1
result.Should().Be("42");
}

[Theory]
[InlineData("{{[Math.LessThan] 2 1}}", "False")]
[InlineData("{{[Math.LessThan] 1 2}}", "True")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using FluentAssertions;
using HandlebarsDotNet.Compiler;
using HandlebarsDotNet.Helpers.Models;
using HandlebarsDotNet.Helpers.Utils;
using Moq;
Expand Down Expand Up @@ -64,6 +65,72 @@ public void Coalesce(string template, string expected)
result.Should().Be(expected);
}

[Fact]
public void Equal_WhenInputIsUnresolvedBinding_And_ThrowOnUnresolvedBindingExpressionIsFalse_ShouldReturnEmpty()
{
// Arrange
var handlebarsContext = Handlebars.Create();
handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture;
handlebarsContext.Configuration.ThrowOnUnresolvedBindingExpression = false;

HandlebarsHelpers.Register(handlebarsContext, o =>
{
o.DateTimeService = _dateTimeServiceMock.Object;
});
var action0 = handlebarsContext.Compile("x{{viewData.page}}y");
var action1 = handlebarsContext.Compile("x{{viewData.page}}y");
var action2 = handlebarsContext.Compile("{{#String.Equal viewData.page \"home\"}}yes{{else}}no{{/String.Equal}}");

// Act 0
var result0 = action0(null);

// Assert 0
result0.Should().Be("xy");

var viewData = new
{
abc = "xyz"
};

// Act 1
var result1 = action1(viewData);

// Assert 1
result1.Should().Be("xy");

// Act 2
var result2 = action2(viewData);

// Assert 2
result2.Should().Be("no");
}

[Fact]
public void Equal_WhenInputIsUnresolvedBinding_And_ThrowOnUnresolvedBindingExpressionIsTrue_ShouldThrow()
{
// Arrange
var handlebarsContext = Handlebars.Create();
handlebarsContext.Configuration.FormatProvider = CultureInfo.InvariantCulture;
handlebarsContext.Configuration.ThrowOnUnresolvedBindingExpression = true;

HandlebarsHelpers.Register(handlebarsContext, o =>
{
o.DateTimeService = _dateTimeServiceMock.Object;
});
var template = handlebarsContext.Compile("x{{viewData.page}}y");

var viewData = new
{
abc = "xyz"
};

// Act
Action action = () => template(viewData);

// Assert
action.Should().Throw<HandlebarsUndefinedBindingException>().WithMessage("viewData is undefined");
}

[Theory]
[InlineData("{{[String.Equal] \"foo\" \"bar\"}}", "False")]
[InlineData("{{[String.Equal] \"foo\" 'b'}}", "False")]
Expand Down

0 comments on commit aa84cf0

Please sign in to comment.