Skip to content

Commit 16ec10e

Browse files
Florian KroenertFlorian Kroenert
authored andcommitted
Implemented 'With' function to add a means of saving variables
1 parent d8d4017 commit 16ec10e

4 files changed

Lines changed: 100 additions & 1 deletion

File tree

src/lib/Xrm.Oss.XTL.Interpreter/FunctionHandlers.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,64 @@ private static Entity FetchSnippet(string name, string filter, Entity primary, I
14131413
return new ValueExpression(value?.ToString(), value);
14141414
};
14151415

1416+
/// <summary>
1417+
/// Defines temporary variables for use in an expression.
1418+
/// Usage: With({ x: Value("amount"), y: 10 }, (x, y) => FormatString("$0 + $1", x, y))
1419+
/// </summary>
1420+
public static FunctionHandler With = (primary, service, tracing, interpreterConfig, parameters) =>
1421+
{
1422+
if (parameters.Count < 2)
1423+
{
1424+
throw new InvalidPluginExecutionException("With needs a dictionary of variables and a lambda expression");
1425+
}
1426+
1427+
// First parameter: dictionary of variables
1428+
var variables = CheckedCast<Dictionary<string, object>>(
1429+
parameters[0].Value,
1430+
"First parameter of With must be a dictionary"
1431+
);
1432+
1433+
// Second parameter: lambda expression
1434+
var lambdaParam = parameters[1];
1435+
var lambdaFunc = lambdaParam.Value as Func<List<ValueExpression>, ValueExpression>;
1436+
1437+
if (lambdaFunc == null)
1438+
{
1439+
throw new InvalidPluginExecutionException(
1440+
"Second parameter of With must be a lambda expression"
1441+
);
1442+
}
1443+
1444+
// Get lambda parameter names
1445+
if (lambdaParam.Args == null)
1446+
{
1447+
throw new InvalidPluginExecutionException(
1448+
"Lambda expression must have named parameters"
1449+
);
1450+
}
1451+
1452+
var lambdaParamNames = lambdaParam.Args.Keys.ToList();
1453+
1454+
// Check all required variables are provided
1455+
var missingVars = lambdaParamNames.Where(name => !variables.ContainsKey(name)).ToList();
1456+
if (missingVars.Any())
1457+
{
1458+
throw new InvalidPluginExecutionException(
1459+
$"Lambda parameters {string.Join(", ", missingVars.Select(v => $"'{v}'"))} not found in With dictionary"
1460+
);
1461+
}
1462+
1463+
// Map dictionary values to lambda parameters in correct order
1464+
var args = lambdaParamNames
1465+
.Select(name => new ValueExpression(
1466+
variables[name]?.ToString() ?? string.Empty,
1467+
variables[name]
1468+
))
1469+
.ToList();
1470+
1471+
return lambdaFunc(args);
1472+
};
1473+
14161474
public static FunctionHandler GptPrompt = (primary, service, tracing, interpreterConfig, parameters) =>
14171475
{
14181476
if (interpreterConfig == null || string.IsNullOrEmpty(interpreterConfig.OpenAIAccessToken))

src/lib/Xrm.Oss.XTL.Interpreter/ValueExpression.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public class ValueExpression
88
{
99
private Lazy<string> _text;
1010
private Lazy<object> _value;
11+
public Dictionary<string, ValueExpression> Args { get; private set; }
1112

1213
public string Text
1314
{
@@ -41,6 +42,7 @@ public ValueExpression(Func<List<ValueExpression>, ValueExpression> expression,
4142
{
4243
_text = new Lazy<string>(() => string.Empty);
4344
_value = new Lazy<object>(() => expression);
45+
Args = args;
4446
}
4547
}
4648
}

src/lib/Xrm.Oss.XTL.Interpreter/XTLInterpreter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ public class XTLInterpreter
6767
{ "Static", FunctionHandlers.Static },
6868
{ "Substring", FunctionHandlers.Substring },
6969
{ "Union", FunctionHandlers.Union },
70-
{ "Value", FunctionHandlers.GetValue }
70+
{ "Value", FunctionHandlers.GetValue },
71+
{ "With", FunctionHandlers.With }
7172
};
7273

7374
public XTLInterpreter(string input, Entity primary, InterpreterConfig interpreterConfig, IOrganizationService service, ITracingService tracing)

src/test/Xrm.Oss.XTL.Interpreter.Tests/XTLInterpreterTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,44 @@ public void It_Should_Execute_Lambdas_On_Filter()
468468
Assert.That(result, Is.EqualTo("Lord of"));
469469
}
470470

471+
[Test]
472+
public void It_Should_Make_Variables_Available_In_With()
473+
{
474+
var context = new XrmFakedContext();
475+
var service = context.GetFakedOrganizationService();
476+
var tracing = context.GetFakeTracingService();
477+
478+
var formula = "With({ question: 'What is the answer to the Ultimate Question of Life, the Universe, and Everything', answer: 42 }, ( question, answer ) => FormatString('$0?\n- $1', question, answer))";
479+
var result = new XTLInterpreter(formula, new Entity(), null, service, tracing).Produce();
480+
481+
Assert.That(result, Is.EqualTo("What is the answer to the Ultimate Question of Life, the Universe, and Everything?\n- 42"));
482+
}
483+
484+
[Test]
485+
public void It_Should_Not_Rely_On_Parameter_Order_In_With()
486+
{
487+
var context = new XrmFakedContext();
488+
var service = context.GetFakedOrganizationService();
489+
var tracing = context.GetFakeTracingService();
490+
491+
var formula = "With({ question: 'What is the answer to the Ultimate Question of Life, the Universe, and Everything', answer: 42 }, ( answer, question ) => FormatString('$0?\n- $1', question, answer))";
492+
var result = new XTLInterpreter(formula, new Entity(), null, service, tracing).Produce();
493+
494+
Assert.That(result, Is.EqualTo("What is the answer to the Ultimate Question of Life, the Universe, and Everything?\n- 42"));
495+
}
496+
497+
[Test]
498+
public void It_Should_Throw_On_Missing_Parameter_In_With()
499+
{
500+
var context = new XrmFakedContext();
501+
var service = context.GetFakedOrganizationService();
502+
var tracing = context.GetFakeTracingService();
503+
504+
var formula = "With({ answer: 42 }, ( answer, question ) => FormatString('$0?\n- $1', question, answer))";
505+
506+
Assert.That(() => new XTLInterpreter(formula, new Entity(), null, service, tracing).Produce(), Throws.Exception);
507+
}
508+
471509
[Test]
472510
public void Coalesce_Should_Return_First_Non_Null_Value()
473511
{

0 commit comments

Comments
 (0)