Skip to content

Commit

Permalink
Sync backend expression functions with frontend (#277)
Browse files Browse the repository at this point in the history
* Implement missing functions in backend

* add some testcases for functions that should be evaluated before implemented in the backend code

* Simplify some functions
  • Loading branch information
tjololo authored Aug 8, 2023
1 parent 37be204 commit 26aa1da
Show file tree
Hide file tree
Showing 97 changed files with 998 additions and 100 deletions.
157 changes: 143 additions & 14 deletions src/Altinn.App.Core/Internal/Expressions/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;

using Altinn.App.Core.Models.Expressions;
Expand Down Expand Up @@ -76,6 +75,15 @@ public static bool EvaluateBooleanExpression(LayoutEvaluatorState state, Compone
ExpressionFunction.and => And(args),
ExpressionFunction.or => Or(args),
ExpressionFunction.not => Not(args),
ExpressionFunction.contains => Contains(args),
ExpressionFunction.notContains => !Contains(args),
ExpressionFunction.commaContains => CommaContains(args),
ExpressionFunction.endsWith => EndsWith(args),
ExpressionFunction.startsWith => StartsWith(args),
ExpressionFunction.stringLength => StringLength(args),
ExpressionFunction.round => Round(args),
ExpressionFunction.upperCase => UpperCase(args),
ExpressionFunction.lowerCase => LowerCase(args),
_ => throw new ExpressionEvaluatorTypeErrorException($"Function \"{expr.Function}\" not implemented"),
};
return ret;
Expand Down Expand Up @@ -119,6 +127,127 @@ public static bool EvaluateBooleanExpression(LayoutEvaluatorState state, Compone
return string.Join("", args.Select(a => a switch { string s => s, _ => ToStringForEquals(a) }));
}

private static bool Contains(object?[] args)
{
if (args.Length != 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 2 argument(s), got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
}

return stringOne.Contains(stringTwo, StringComparison.InvariantCulture);
}

private static bool EndsWith(object?[] args)
{
if (args.Length != 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 2 argument(s), got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
}

return stringOne.EndsWith(stringTwo, StringComparison.InvariantCulture);
}

private static bool StartsWith(object?[] args)
{
if (args.Length != 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 2 argument(s), got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
}

return stringOne.StartsWith(stringTwo, StringComparison.InvariantCulture);
}

private static bool CommaContains(object?[] args)
{
if (args.Length != 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 2 arguments, got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
}

return stringOne.Split(",").Select(s => s.Trim()).Contains(stringTwo, StringComparer.InvariantCulture);
}

private static int StringLength(object?[] args)
{
if (args.Length != 1)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 1 argument, got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
return stringOne?.Length ?? 0;
}

private static string Round(object?[] args)
{
if (args.Length < 1 || args.Length> 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 1-2 argument(s), got {args.Length}");
}

var number = PrepareNumericArg(args[0]);

if(number is null)
{
number = 0;
}

int precision = 0;
if (args.Length == 2 && args[1] is not null)
{
precision = Convert.ToInt32(args[1]);
}

return number.Value.ToString($"N{precision}", CultureInfo.InvariantCulture);
}

private static string? UpperCase(object?[] args)
{
if (args.Length != 1)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 1 argument, got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
return stringOne?.ToUpperInvariant();
}

private static string? LowerCase(object?[] args)
{
if (args.Length != 1)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 1 argument, got {args.Length}");
}
string? stringOne = ToStringForEquals(args[0]);
return stringOne?.ToLowerInvariant();
}

private static bool PrepareBooleanArg(object? arg)
{
return arg switch
Expand Down Expand Up @@ -187,23 +316,23 @@ private static (double?, double?) PrepareNumericArgs(object?[] args)
{
throw new ExpressionEvaluatorTypeErrorException("Invalid number of args for compare");
}
var a = args[0] switch
{
bool ab => throw new ExpressionEvaluatorTypeErrorException($"Expected number, got value {(ab ? "true" : "false")}"),
string s => parseNumber(s),
object o => o as double?, // assume all relevant numers are representable as double (as in frontend)
_ => null,
};

var b = args[1] switch
var a = PrepareNumericArg(args[0]);

var b = PrepareNumericArg(args[1]);

return (a, b);
}

private static double? PrepareNumericArg(object? arg)
{
return arg switch
{
bool bb => throw new ExpressionEvaluatorTypeErrorException($"Expected number, got value {(bb ? "true" : "false")}"),
bool ab => throw new ExpressionEvaluatorTypeErrorException($"Expected number, got value {(ab ? "true" : "false")}"),
string s => parseNumber(s),
object o => o as double?, // assume all relevant numers are representable as double (as in frontend)
_ => null,
object o => o as double?, // assume all relevant numbers are representable as double (as in frontend)
_ => null
};

return (a, b);
}

private static object? IfImpl(object?[] args)
Expand Down
36 changes: 36 additions & 0 deletions src/Altinn.App.Core/Models/Expressions/ExpressionFunctionEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ public enum ExpressionFunction
/// </summary>
concat,
/// <summary>
/// Turn characters to upper case
/// </summary>
upperCase,
/// <summary>
/// Turn characters to lower case
/// </summary>
lowerCase,
/// <summary>
/// Check if a string contains another string
/// </summary>
contains,
/// <summary>
/// Check if a string does not contain another string
/// </summary>
notContains,
/// <summary>
/// Check if a comma separated string contains a value
/// </summary>
commaContains,
/// <summary>
/// Check if a string ends with another string
/// </summary>
endsWith,
/// <summary>
/// Check if a string starts with another string
/// </summary>
startsWith,
/// <summary>
/// Check if values are equal
/// </summary>
equals,
Expand All @@ -58,6 +86,14 @@ public enum ExpressionFunction
/// </summary>
greaterThan,
/// <summary>
/// Return the length of a string
/// </summary>
stringLength,
/// <summary>
/// Rounds a number to an integer, or optionally a decimal with a configurable amount of decimal points
/// </summary>
round,
/// <summary>
/// Return true if all the expressions evaluate to true
/// </summary>
and,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,27 @@ public TestFunctions(ITestOutputHelper output)
[Theory]
[SharedTest("component")]
public void Component_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("commaContains")]
public void CommaContains_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("concat")]
public void Concat_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("contains")]
public void Contains_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("dataModel")]
public void DataModel_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("endsWith")]
public void EndsWith_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("equals")]
public void Equals_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);
Expand All @@ -59,6 +71,10 @@ public TestFunctions(ITestOutputHelper output)
[Theory]
[SharedTest("not")]
public void Not_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("notContains")]
public void NotContains_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("instanceContext")]
Expand All @@ -83,6 +99,26 @@ public TestFunctions(ITestOutputHelper output)
[Theory]
[SharedTest("unknown")]
public void Unknown_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("upperCase")]
public void UpperCase_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("lowerCase")]
public void LowerCase_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("startsWith")]
public void StartsWith_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("stringLength")]
public void StringLength_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

[Theory]
[SharedTest("round")]
public void Round_Theory(ExpressionTestCaseRoot test) => RunTestCase(test);

private void RunTestCase(ExpressionTestCaseRoot test)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"layout": [
{
"id": "comp1",
"type": "Heading"
"type": "Header"
},
{
"id": "group0",
Expand Down
Loading

0 comments on commit 26aa1da

Please sign in to comment.