Skip to content

Commit a9d1da4

Browse files
Merge pull request #19 from thygesteffensen/issue/18/apply
Issue/18/apply
2 parents ce8a97e + 6fc1095 commit a9d1da4

19 files changed

+662
-35
lines changed

PowerAutomateMockUp/ExpressionParser/ExpressionEngine.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
public interface IExpressionEngine
44
{
55
string Parse(string input);
6+
ValueContainer ParseToValueContainer(string input);
67
}
78

89
public class ExpressionEngine : IExpressionEngine
@@ -16,7 +17,12 @@ public ExpressionEngine(ExpressionGrammar grammar)
1617

1718
public string Parse(string input)
1819
{
19-
return _expressionGrammar.Evaluate(input);
20+
return _expressionGrammar.EvaluateToString(input);
21+
}
22+
23+
public ValueContainer ParseToValueContainer(string input)
24+
{
25+
return _expressionGrammar.EvaluateToValueContainer(input);
2026
}
2127
}
2228
}

PowerAutomateMockUp/ExpressionParser/ExpressionGrammar.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Parser.ExpressionParser
99
public class ExpressionGrammar
1010
{
1111
private readonly Parser<IRule> _method;
12-
private readonly Parser<string> _input;
12+
private readonly Parser<ValueContainer> _input;
1313

1414
public ExpressionGrammar(IEnumerable<IFunction> functions)
1515
{
@@ -95,43 +95,48 @@ from indexes in indices.Many()
9595
select (IRule) new AccessValueRule(func, indexes));
9696
// .Or(simpleStringRule);
9797

98-
Parser<string> enclosedExpression =
98+
Parser<ValueContainer> enclosedExpression =
9999
_method.Contained(
100100
Parse.String("@{"),
101101
Parse.Char('}'))
102-
.Select(x => x.Evaluate().GetValue<string>());
102+
.Select(x => x.Evaluate());
103103

104-
Parser<string> expression =
104+
Parser<ValueContainer> expression =
105105
from at in Parse.Char('@')
106106
from method in _method
107-
select method.Evaluate().GetValue<string>();
107+
select method.Evaluate();
108108

109109

110110
Parser<string> allowedString =
111111
from t in simpleString.Or(allowedCharacters).Many()
112112
select string.Concat(t);
113113

114-
Parser<string> joinedString =
114+
Parser<ValueContainer> joinedString =
115115
from e in (
116116
from preFix in allowedString
117117
from exp in enclosedExpression.Optional()
118118
select exp.IsEmpty ? preFix : preFix + exp.Get())
119119
.Many()
120-
select string.Concat(e);
120+
select new ValueContainer(string.Concat(e));
121121

122-
Parser<string> charPrefixedString =
122+
Parser<ValueContainer> charPrefixedString =
123123
from at in Parse.Char('@')
124124
from str in Parse.LetterOrDigit.Many().Text().Except(Parse.Chars('{', '@'))
125-
select str;
125+
select new ValueContainer(str);
126126

127127
_input = expression.Or(charPrefixedString).Or(joinedString);
128128
}
129129

130-
public string Evaluate(string input)
130+
public string EvaluateToString(string input)
131131
{
132132
var output = _input.Parse(input);
133133

134-
return output;
134+
return output.GetValue<string>();
135+
}
136+
137+
public ValueContainer EvaluateToValueContainer(string input)
138+
{
139+
return _input.Parse(input);
135140
}
136141
}
137142
}

PowerAutomateMockUp/ExpressionParser/Functions/Base/FlowRunnerDependencyExtension.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static void AddFlowRunner(this IServiceCollection services)
1616
services.AddSingleton<FlowRunner>();
1717

1818
services.AddSingleton<ActionExecutorFactory>();
19-
services.AddSingleton<ScopeDepthManager>();
19+
services.AddSingleton<IScopeDepthManager, ScopeDepthManager>();
2020

2121
services.AddScoped<IState, State>();
2222
services.AddScoped<IVariableRetriever>(x => x.GetRequiredService<IState>());
@@ -28,15 +28,19 @@ public static void AddFlowRunner(this IServiceCollection services)
2828
services.AddTransient<IFunction, ConcatFunction>();
2929
services.AddTransient<IFunction, ToLower>();
3030
services.AddTransient<IFunction, ToUpperFunction>();
31+
3132
services.AddTransient<IFunction, VariablesFunction>();
3233
services.AddTransient<IFunction, OutputsFunction>();
3334
services.AddTransient<IFunction, TriggerOutputsFunctions>();
35+
services.AddTransient<IFunction, ItemsFunction>();
36+
3437
services.AddTransient<IFunction, TrimFunction>();
3538
services.AddTransient<IFunction, LengthFunction>();
3639

3740
services.AddFlowActionByFlowType<IfActionExecutor>("If");
3841
services.AddFlowActionByFlowType<ScopeActionExecutor>("Scope");
3942
services.AddFlowActionByFlowType<TerminateActionExecutor>("Terminate");
43+
services.AddFlowActionByFlowType<ForEachActionExecutor>("Foreach");
4044

4145
services.AddLogging();
4246
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using Parser.ExpressionParser.Functions.Base;
3+
using Parser.ExpressionParser.Functions.CustomException;
4+
5+
namespace Parser.ExpressionParser.Functions.Storage
6+
{
7+
public class ItemsFunction : Function
8+
{
9+
private readonly IState _variableRetriever;
10+
11+
public ItemsFunction(IState variableRetriever) : base("items")
12+
{
13+
_variableRetriever = variableRetriever ?? throw new ArgumentNullException(nameof(variableRetriever));
14+
}
15+
16+
public override ValueContainer ExecuteFunction(params ValueContainer[] parameters)
17+
{
18+
if (parameters.Length != 1)
19+
{
20+
throw new ArgumentError(parameters.Length > 1 ? "Too many arguments" : "Too few arguments");
21+
}
22+
23+
var variableName = parameters[0].GetValue<string>();
24+
// TODO: Maybe implement another storage option?
25+
var value = _variableRetriever.GetOutputs($"item_{variableName}");
26+
27+
return value;
28+
}
29+
}
30+
}

PowerAutomateMockUp/ExpressionParser/ValueContainer.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public ValueContainer(float floatValue)
6363
_type = ValueType.Float;
6464
}
6565

66+
public ValueContainer(double floatValue)
67+
{
68+
_value = floatValue;
69+
_type = ValueType.Float;
70+
}
71+
6672
public ValueContainer(int intValue)
6773
{
6874
_value = intValue;
@@ -211,12 +217,13 @@ public Dictionary<string, ValueContainer> AsDict()
211217
{
212218
return GetValue<Dictionary<string, ValueContainer>>();
213219
}
220+
214221
throw new PowerAutomateMockUpException("Can't get none object value container as dict.");
215222
}
216223

217224
private ValueContainer JsonToValueContainer(JToken json)
218225
{
219-
if (json.GetType() == typeof(JObject))
226+
if (json is JObject jObject)
220227
{
221228
var dictionary = json.ToDictionary(pair => ((JProperty) pair).Name, token =>
222229
{
@@ -236,9 +243,49 @@ private ValueContainer JsonToValueContainer(JToken json)
236243
return new ValueContainer(dictionary);
237244
}
238245

246+
if (json is JArray jArray)
247+
{
248+
return jArray.Count > 0 ? new ValueContainer() : JArrayToValueContainer(jArray);
249+
}
250+
239251
throw new Exception();
240252
}
241253

254+
private ValueContainer JArrayToValueContainer(JArray json)
255+
{
256+
var list = new List<ValueContainer>();
257+
258+
foreach (var jToken in json)
259+
{
260+
if (jToken.GetType() != typeof(JValue))
261+
{
262+
throw new PowerAutomateMockUpException("Json can only contain arrays of primitive types.");
263+
}
264+
265+
var t = (JValue) jToken;
266+
switch (t.Value)
267+
{
268+
case int i:
269+
list.Add(new ValueContainer(i));
270+
break;
271+
case string s:
272+
list.Add(new ValueContainer(s));
273+
break;
274+
case bool b:
275+
list.Add(new ValueContainer(b));
276+
break;
277+
case double d:
278+
list.Add(new ValueContainer(d));
279+
break;
280+
default:
281+
throw new PowerAutomateMockUpException(
282+
$"Type {t.Value.GetType()} is not recognized when converting Json to ValueContainer.");
283+
}
284+
}
285+
286+
return new ValueContainer(list);
287+
}
288+
242289

243290
public override string ToString()
244291
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Threading.Tasks;
2+
3+
namespace Parser.FlowParser.ActionExecutors
4+
{
5+
public interface IScopeActionExecutor
6+
{
7+
public Task<ActionResult> ExitScope(ActionStatus scopeStatus);
8+
}
9+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Logging;
6+
using Newtonsoft.Json.Linq;
7+
using Parser.ExpressionParser;
8+
9+
namespace Parser.FlowParser.ActionExecutors.Implementations
10+
{
11+
public class ForEachActionExecutor : DefaultBaseActionExecutor, IScopeActionExecutor
12+
{
13+
private readonly IState _state;
14+
private readonly IScopeDepthManager _scopeDepthManager;
15+
private readonly ILogger<ForEachActionExecutor> _logger;
16+
private readonly IExpressionEngine _expressionEngine;
17+
18+
private JProperty[] _actionDescriptions;
19+
private string _firstScopeActionName;
20+
21+
private List<ValueContainer> _items;
22+
23+
public ForEachActionExecutor(
24+
IState state,
25+
IScopeDepthManager scopeDepthManager,
26+
ILogger<ForEachActionExecutor> logger,
27+
IExpressionEngine expressionEngine)
28+
{
29+
_state = state ?? throw new ArgumentNullException(nameof(state));
30+
_scopeDepthManager = scopeDepthManager ?? throw new ArgumentNullException(nameof(scopeDepthManager));
31+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
32+
_expressionEngine = expressionEngine ?? throw new ArgumentNullException(nameof(expressionEngine));
33+
34+
_items = new List<ValueContainer>();
35+
}
36+
37+
public override Task<ActionResult> Execute()
38+
{
39+
_logger.LogInformation("Entered foreach...");
40+
41+
if (Json == null)
42+
{
43+
throw new PowerAutomateMockUpException($"Json cannot be null - cannot execute {ActionName}.");
44+
}
45+
46+
var runOn = (Json.SelectToken("$..foreach") ??
47+
throw new InvalidOperationException("Json must contain foreach token.")).Value<string>();
48+
var values = _expressionEngine.ParseToValueContainer(runOn);
49+
50+
if (values.Type() != ValueContainer.ValueType.Array)
51+
return Task.FromResult(new ActionResult
52+
{
53+
// TODO: Figure out what happens when you apply for each on non array values
54+
ActionStatus = ActionStatus.Failed
55+
});
56+
57+
SetupForEach(values);
58+
59+
UpdateScopeAndSetItemValue();
60+
61+
return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
62+
}
63+
64+
private void SetupForEach(ValueContainer values)
65+
{
66+
var scopeActionDescriptions = (Json.SelectToken("$.actions") ??
67+
throw new InvalidOperationException("Json must contain actions token."))
68+
.OfType<JProperty>();
69+
_actionDescriptions = scopeActionDescriptions as JProperty[] ?? scopeActionDescriptions.ToArray();
70+
_firstScopeActionName = _actionDescriptions.First(ad =>
71+
!(ad.Value.SelectToken("$.runAfter") ??
72+
throw new InvalidOperationException("Json must contain runAfter token.")).Any()).Name;
73+
74+
// TODO: Add scope relevant storage to store stuff like this, which cannot interfere with the state.
75+
_items = values.GetValue<IEnumerable<ValueContainer>>().ToList();
76+
}
77+
78+
private void UpdateScopeAndSetItemValue()
79+
{
80+
_scopeDepthManager.Push(ActionName, _actionDescriptions, this);
81+
82+
_state.AddOutputs($"item_{ActionName}", _items.First());
83+
_items = _items.Skip(1).ToList();
84+
}
85+
86+
87+
public Task<ActionResult> ExitScope(ActionStatus scopeStatus)
88+
{
89+
if (_items.Count > 0)
90+
{
91+
_logger.LogInformation("Continuing foreach.");
92+
93+
UpdateScopeAndSetItemValue();
94+
95+
return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
96+
}
97+
else
98+
{
99+
_logger.LogInformation("Exited foreach...");
100+
return Task.FromResult(new ActionResult());
101+
}
102+
}
103+
}
104+
}

PowerAutomateMockUp/FlowParser/ActionExecutors/Implementations/ScopeActionExecutor.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace Parser.FlowParser.ActionExecutors.Implementations
77
{
88
public class ScopeActionExecutor : DefaultBaseActionExecutor
99
{
10-
private readonly ScopeDepthManager _scopeDepthManager;
10+
private readonly IScopeDepthManager _scopeDepthManager;
1111

12-
public ScopeActionExecutor(ScopeDepthManager scopeDepthManager)
12+
public ScopeActionExecutor(IScopeDepthManager scopeDepthManager)
1313
{
1414
_scopeDepthManager = scopeDepthManager ?? throw new ArgumentNullException(nameof(scopeDepthManager));
1515
}
@@ -26,9 +26,4 @@ public override Task<ActionResult> Execute()
2626
return Task.FromResult(new ActionResult {NextAction = firstScopeAction.Name});
2727
}
2828
}
29-
30-
public interface IScopeActionExecutor
31-
{
32-
public Task<ActionResult> ExitScope(ActionStatus scopeStatus);
33-
}
3429
}

PowerAutomateMockUp/FlowParser/ActionExecutors/OpenApiConnectionBaseActionExecutor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ protected override void ProcessJson()
3030
Parameters = new ValueContainer(new Dictionary<string, ValueContainer>());
3131
foreach (var keyValuePar in content.Parameters)
3232
{
33-
// TODO: Here is an use case where the engine could have just returned a Value Container instead!
34-
Parameters[keyValuePar.Key] = new ValueContainer(_expressionEngine.Parse(keyValuePar.Value), true);
33+
Parameters[keyValuePar.Key] = _expressionEngine.ParseToValueContainer(keyValuePar.Value);
3534
}
3635
}
3736

PowerAutomateMockUp/FlowParser/FlowRunner.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ public class FlowRunner
1515
{
1616
private readonly IState _state;
1717
private readonly FlowSettings _flowRunnerSettings;
18-
private readonly ScopeDepthManager _scopeManager;
18+
private readonly IScopeDepthManager _scopeManager;
1919
private readonly ActionExecutorFactory _actionExecutorFactory;
2020
private JProperty _trigger;
2121

2222
public FlowRunner(
2323
IState state,
24-
ScopeDepthManager scopeDepthManager,
24+
IScopeDepthManager scopeDepthManager,
2525
IOptions<FlowSettings> flowRunnerSettings,
2626
ActionExecutorFactory actionExecutorFactory)
2727
{

0 commit comments

Comments
 (0)