Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<PackageVersion Include="CSharpier.Core" Version="1.2.6" />
<PackageVersion Include="CSharpier.MsBuild" Version="1.2.6" />
<PackageVersion Include="FluentAssertions" Version="7.2.2" />
<PackageVersion Include="JmesPath.Net" Version="1.1.0" />
<PackageVersion Include="JsonPatch.Net" Version="4.0.1" />
<PackageVersion Include="JWTCookieAuthentication" Version="3.0.1" />
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
Expand Down Expand Up @@ -80,4 +81,4 @@
<PackageVersion Include="Polly.Extensions" Version="8.6.6" />
<PackageVersion Include="Polly.Testing" Version="8.6.6" />
</ItemGroup>
</Project>
</Project>
1 change: 1 addition & 0 deletions src/Altinn.App.Core/Altinn.App.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PackageReference Include="Altinn.Common.PEP"/>
<PackageReference Include="Altinn.Platform.Models"/>
<PackageReference Include="Altinn.Platform.Storage.Interface"/>
<PackageReference Include="JmesPath.Net"/>
<PackageReference Include="JsonPatch.Net"/>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore"/>
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ internal static async Task<ExpressionValue> EvaluateExpression_internal(
ExpressionFunction.divide => Divide(args),
ExpressionFunction.list => List(args),
ExpressionFunction.@object => Object(args),
ExpressionFunction.jmespath => Jmespath(args),
ExpressionFunction.INVALID => throw new ExpressionEvaluatorTypeErrorException(
$"Function {expr.Args.FirstOrDefault()} not implemented in backend {expr}"
),
Expand Down Expand Up @@ -1010,6 +1011,11 @@ private static JsonObject Object(ExpressionValue[] args)
return new ObjectFunctionEvaluator(args).Evaluate();
}

private static ExpressionValue Jmespath(ExpressionValue[] args)
{
return new JmespathFunctionEvaluator(args).Evaluate();
}

/// <summary>
/// Performs arithmetic operation using decimal precision to avoid floating point precision issues.
/// Converts doubles to decimal, performs the operation, and converts back to double.
Expand Down
17 changes: 17 additions & 0 deletions src/Altinn.App.Core/Internal/Expressions/ExpressionValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,23 @@ private static ExpressionValue ToJsonNodeOrNull(object? value)
};
}

/// <summary>Convert a JsonElement to ExpressionValue.</summary>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ikke så farlig, men lurer på om det er standard at man har linjeskift før og etter taggene. Mao:

Suggested change
/// <summary>Convert a JsonElement to ExpressionValue.</summary>
/// <summary>
/// Convert a JsonElement to ExpressionValue.
/// </summary>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det har du nok rett i. Jeg må si at jeg ikke er noen tilhenger av disse kommentarene i det hele tatt (navnet og signaturen til funksjonen burde være dokumenterende nok i seg selv), og disse XML-taggene gjør det bare enda mer rotete. Men jeg skjønner at dette er en konvensjon for metoder som er public. Ved å sette alt på én linje tenkte jeg i hvert fall at vi kunne bruke litt mindre vertikal plass, men jeg ser at det heller ikke er etablert praksis.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fikset.

public static ExpressionValue FromJsonElement(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.Null => Null,
JsonValueKind.Undefined => Undefined,
JsonValueKind.True => True,
JsonValueKind.False => False,
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.GetDouble(),
JsonValueKind.Object => element.Deserialize<JsonObject>() ?? Null,
JsonValueKind.Array => element.Deserialize<JsonArray>() ?? Null,
_ => throw new InvalidOperationException($"Invalid JsonElement with ValueKind {element.ValueKind}"),
};
}

/// <summary>
/// Convert the value to the relevant CLR type
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Text.Json;
using DevLab.JmesPath;

namespace Altinn.App.Core.Internal.Expressions;

internal sealed class JmespathFunctionEvaluator
{
private readonly ExpressionValue[] _args;

public JmespathFunctionEvaluator(ExpressionValue[] args) => _args = args;

public ExpressionValue Evaluate()
{
if (_args.Length != 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 2 argument(s), got {_args.Length}");
}
if (_args[1].ValueKind != JsonValueKind.String)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected argument to be string, got {_args[1]}");
}
return Implementation(_args[0], _args[1].String);
}

private static ExpressionValue Implementation(ExpressionValue data, string query)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation funker, men du kunne vell også brukt overloading her og kalt den det samme som metoden som kaller den, Evaluate. Syns kanskje det blir hakket bedre, men er jo litt smak og behag dette med navngivning, du får vurdere det selv.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Her valgte jeg samme format som i Equals, som har en EqualsImplementation etter at parametrene er sjekket, men jeg er enig i at vi bør jobbe mer med navngivningen her.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fikset. Valgte å kalle den EvaluateWithValidArguments, så det kommer frem at denne må ha verifiserte parametre.

{
string resultAsString = VerifyAndRunQuery(data, query);
JsonElement result = JsonSerializer.Deserialize<JsonElement>(resultAsString);
return ExpressionValue.FromJsonElement(result);
}

private static string VerifyAndRunQuery(ExpressionValue data, string query)
{
JmesPath jmesPath = new();
try
{
return jmesPath.Transform(data.ToString(), query);
Comment thread
olavsorl marked this conversation as resolved.
Outdated
}
catch (Exception exception)
{
throw new ExpressionEvaluatorTypeErrorException($"Jmespath error: \"{exception.Message}\"");
}
}
}
3 changes: 3 additions & 0 deletions src/Altinn.App.Core/Models/Expressions/ExpressionFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,7 @@ public enum ExpressionFunction
#pragma warning disable CA1720
@object,
#pragma warning restore CA1720

/// <summary>Run a Jmespath query on the arguments</summary>
jmespath,
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ public async Task List_Theory(string testName, ExpressionTestCaseRoot.TestCaseIt
public async Task Object_Theory(string testName, ExpressionTestCaseRoot.TestCaseItem testCaseItem) =>
await RunTestCase(testName, new ExpressionTestCaseRoot(testCaseItem));

[Theory]
[SharedTestCases("jmespath")]
public async Task Jmespath_Theory(string testName, ExpressionTestCaseRoot.TestCaseItem testCaseItem) =>
await RunTestCase(testName, new ExpressionTestCaseRoot(testCaseItem));

private static async Task<ExpressionTestCaseRoot> LoadTestCase(string file, string folder)
{
ExpressionTestCaseRoot testCase = new();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
{
"name": "jmespath",
"testCases": [
{
"name": "Works with a simple property query that returns a string",
"expression": ["jmespath", ["object", "name", "Kari"], "name"],
"expects": "Kari"
},
{
"name": "Works with a simple property query that returns a number",
"expression": ["jmespath", ["object", "ageInYears", 18], "ageInYears"],
"expects": 18
},
{
"name": "Works with a simple property query that returns a boolean",
"expression": ["jmespath", ["object", "likesIceCream", true], "likesIceCream"],
"expects": true
},
{
"name": "Works with a simple property query that returns null",
"expression": ["jmespath", ["object", "favouriteColour", null], "favouriteColour"],
"expects": null
},
{
"name": "Works with a simple property query that returns an object",
"expression": [
"jmespath",
[
"object",
"person",
[
"object",
"name", "Kari",
"ageInYears", 18,
"likesIceCream", true,
"favouriteColour", null
]
],
"person"
],
"expects": {
"name": "Kari",
"ageInYears": 18,
"likesIceCream": true,
"favouriteColour": null
}
},
{
"name": "Works with a simple property query that returns a list",
"expression": [
"jmespath",
[
"object",
"colours",
["list", "red", "green", "blue"]
],
"colours"
],
"expects": ["red", "green", "blue"]
},
{
"name": "Works with a simple property query on a list of objects",
"expression": [
"jmespath",
[
"list",
["object", "name", "Ola", "ageInYears", 30],
["object", "name", "Kari", "ageInYears", 18],
["object", "name", "Knut", "ageInYears", 16]
],
"[].name"
],
"expects": ["Ola", "Kari", "Knut"]
},
{
"name": "Works with filtering",
"expression": [
"jmespath",
[
"list",
["object", "name", "Ola", "ageInYears", 30],
["object", "name", "Kari", "ageInYears", 18],
["object", "name", "Knut", "ageInYears", 16]
],
"[?ageInYears>=`18`].name"
],
"expects": ["Ola", "Kari"]
},
{
"name": "Works with the current-node token",
"expression": ["jmespath", ["list", "something"], "@"],
"expects": ["something"]
},
{
"name": "Works with literal expressions",
"expression": ["jmespath", ["object"], "'Lorem ipsum'"],
"expects": "Lorem ipsum"
},
{
"name": "Works with functions",
"expression": ["jmespath", ["object"], "abs(`-1`)"],
"expects": 1
},
{
"name": "Returns null when no results are found",
"expression": ["jmespath", ["object", "name", "Kari", "ageInYears", 18], "city"],
"expects": null
},
{
"name": "Accepts string input",
"expression": ["jmespath", "This is a string", "@"],
"expects": "This is a string"
},
{
"name": "Accepts number input",
"expression": ["jmespath", 12, "@"],
"expects": 12
},
{
"name": "Accepts boolean input",
"expression": ["jmespath", true, "@"],
"expects": true
},
{
"name": "Accepts null input",
"expression": ["jmespath", null, "@"],
"expects": null
},
{
"name": "Fails when query is invalid",
"expression": ["jmespath", ["object", "name", "Kari", "ageInYears", 18], "#"],
"expectsFailure": "Jmespath error"
},
{
"name": "Fails when no arguments are present",
"expression": ["jmespath"],
"expectsFailure": "Expected 2 argument(s), got 0"
},
{
"name": "Fails when the query is null",
"expression": ["jmespath", ["object"], null],
"expectsFailure": "Expected argument to be string"
},
{
"name": "Fails when the query is neither a string nor null",
"expression": ["jmespath", ["object", "name", "Kari", "ageInYears", 18], ["object"]],
"expectsFailure": "Expected argument to be string"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3255,6 +3255,7 @@ namespace Altinn.App.Core.Internal.Expressions
public string? ToStringForText() { }
public bool TryDeserialize(System.Type type, out object? result) { }
public bool TryDeserialize<T>(out T? result) { }
public static Altinn.App.Core.Internal.Expressions.ExpressionValue FromJsonElement(System.Text.Json.JsonElement element) { }
public static Altinn.App.Core.Internal.Expressions.ExpressionValue FromObject(object? value) { }
public static Altinn.App.Core.Internal.Expressions.ExpressionValue op_Implicit(System.Text.Json.Nodes.JsonArray value) { }
public static Altinn.App.Core.Internal.Expressions.ExpressionValue op_Implicit(System.Text.Json.Nodes.JsonObject value) { }
Expand Down Expand Up @@ -4725,6 +4726,7 @@ namespace Altinn.App.Core.Models.Expressions
divide = 40,
list = 41,
@object = 42,
jmespath = 43,
}
}
namespace Altinn.App.Core.Models.Layout.Components.Base
Expand Down
Loading