Skip to content

Commit

Permalink
Merge pull request #807 from Altinn/bug/backend-expressions-support-long
Browse files Browse the repository at this point in the history
Make backend expression "equals" support float and long data types
  • Loading branch information
ivarne authored Oct 3, 2024
2 parents 90042d7 + 98978b2 commit dca53d4
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 45 deletions.
76 changes: 31 additions & 45 deletions src/Altinn.App.Core/Internal/Expressions/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
using Altinn.App.Core.Models.Expressions;
using Altinn.App.Core.Models.Layout.Components;
Expand Down Expand Up @@ -473,54 +474,39 @@ bool ab
return a >= b; // Actual implementation
}

private static string? ToStringForEquals(object? value)
{
if (value is null)
{
return null;
}

if (value is bool bvalue)
{
return bvalue ? "true" : "false";
}

if (value is string svalue)
internal static string? ToStringForEquals(object? value) =>
value switch
{
null => null,
bool bValue => bValue ? "true" : "false",
// Special case for "TruE" to be equal to true
if ("true".Equals(svalue, StringComparison.OrdinalIgnoreCase))
{
return "true";
}
else if ("false".Equals(svalue, StringComparison.OrdinalIgnoreCase))
{
return "false";
}
else if ("null".Equals(svalue, StringComparison.OrdinalIgnoreCase))
{
return null;
}

return svalue;
}
else if (value is decimal decvalue)
{
return decvalue.ToString(CultureInfo.InvariantCulture);
}
else if (value is double doubvalue)
{
return doubvalue.ToString(CultureInfo.InvariantCulture);
}
else if (value is int intvalue)
{
return intvalue.ToString(CultureInfo.InvariantCulture);
}

//TODO: consider accepting more types that might be used in model (eg Datetime)
throw new NotImplementedException();
}
string sValue when "true".Equals(sValue, StringComparison.OrdinalIgnoreCase) => "true",
string sValue when "false".Equals(sValue, StringComparison.OrdinalIgnoreCase) => "false",
string sValue when "null".Equals(sValue, StringComparison.OrdinalIgnoreCase) => null,
string sValue => sValue,
decimal decValue => decValue.ToString(CultureInfo.InvariantCulture),
double doubleValue => doubleValue.ToString(CultureInfo.InvariantCulture),
float floatValue => floatValue.ToString(CultureInfo.InvariantCulture),
int intValue => intValue.ToString(CultureInfo.InvariantCulture),
uint uintValue => uintValue.ToString(CultureInfo.InvariantCulture),
short shortValue => shortValue.ToString(CultureInfo.InvariantCulture),
ushort ushortValue => ushortValue.ToString(CultureInfo.InvariantCulture),
long longValue => longValue.ToString(CultureInfo.InvariantCulture),
ulong ulongValue => ulongValue.ToString(CultureInfo.InvariantCulture),
byte byteValue => byteValue.ToString(CultureInfo.InvariantCulture),
sbyte sbyteValue => sbyteValue.ToString(CultureInfo.InvariantCulture),
// BigInteger bigIntValue => bigIntValue.ToString(CultureInfo.InvariantCulture), // Big integer not supported in json
DateTime dtValue => JsonSerializer.Serialize(dtValue),
DateOnly dateValue => JsonSerializer.Serialize(dateValue),
TimeOnly timeValue => JsonSerializer.Serialize(timeValue),
//TODO: Consider having JsonSerializer as a fallback for everything (including arrays and objects)
_
=> throw new NotImplementedException(
$"ToStringForEquals not implemented for type {value.GetType().Name}"
)
};

private static bool? EqualsImplementation(object?[] args)
internal static bool? EqualsImplementation(object?[] args)
{
if (args.Length != 2)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Numerics;
using System.Text.Json;
using Altinn.App.Core.Internal.Expressions;
using Xunit.Abstractions;

namespace Altinn.App.Core.Tests.LayoutExpressions.ExpressionEvaluatorTests;

public class EqualTests(ITestOutputHelper outputHelper)
{
public static TheoryData<object> GetNumericTestData(double value) =>
new()
{
value,
(byte)value,
(sbyte)value,
(short)value,
(ushort)value,
(int)value,
(uint)value,
(long)value,
(ulong)value,
(float)value,
(decimal)value,
// (BigInteger)value, // Not supported by JsonSerializer
};

public static TheoryData<object> GetExoticTypes =>
new()
{
"123",
true,
false,
"",
DateTime.Now,
DateOnly.FromDateTime(DateTime.Now),
TimeOnly.FromDateTime(DateTime.Now),
};

[Theory]
[MemberData(nameof(GetNumericTestData), 123.0)]
[MemberData(nameof(GetNumericTestData), 0.5)]
[MemberData(nameof(GetNumericTestData), -123.0)]
[MemberData(nameof(GetExoticTypes))]
public void ToStringForEquals_AgreesWithJsonSerializer(object? value)
{
outputHelper.WriteLine($"Object of type {value?.GetType().FullName ?? "null"}:");
outputHelper.WriteLine($" value:{value}");
outputHelper.WriteLine($" json: {JsonSerializer.Serialize(value)}");
// Verify that the EqualsToString method returns the same value as the JsonSerializer.
var json = value is string ? value : JsonSerializer.Serialize(value);
var toStringForEquals = ExpressionEvaluator.ToStringForEquals(value);
Assert.Equal(json, toStringForEquals);
}

public static TheoryData<object> GetNonsenseValues =>
new()
{
new BigInteger(123), // Not supported by JsonSerializer, but might make sense to support
new object[] { 1, 2, 3 },
new object(),
new
{
A = 1,
B = 2,
C = 3
},
new byte[] { 0x01, 0x02, 0x03 },
};

[Theory]
[MemberData(nameof(GetNonsenseValues))]
public void ToStringForEquals_NonsenseTypes_ThrowsException(object? value)
{
outputHelper.WriteLine($"Object of type {value?.GetType().FullName ?? "null"}:");
outputHelper.WriteLine($" value:{value}");
outputHelper.WriteLine($" json: {JsonSerializer.Serialize(value)}");
// Verify that the EqualsToString method throws an exception for unsupported types.
Assert.Throws<NotImplementedException>(() => ExpressionEvaluator.ToStringForEquals(value));
}

[Theory]
[InlineData(null, null)]
[InlineData("null", null)]
[InlineData("Null", null)]
[InlineData("true", "true")]
[InlineData("trUe", "true")]
[InlineData("True", "true")]
[InlineData(true, "true")]
[InlineData("false", "false")]
[InlineData("False", "false")]
[InlineData("falSe", "false")]
[InlineData(false, "false")]
public void ToStringForEquals_SpecialCases(object? value, string? expected)
{
// Verify that the EqualsToString method returns the expected value for special cases.
var toStringForEquals = ExpressionEvaluator.ToStringForEquals(value);
Assert.Equal(expected, toStringForEquals);
}
}

0 comments on commit dca53d4

Please sign in to comment.