Skip to content

Commit

Permalink
Add Api Call Rule
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemoresca committed Nov 16, 2023
1 parent 6f2c3c7 commit 8c3564f
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 37 deletions.
11 changes: 10 additions & 1 deletion ActionFlow.Tests/Engine/RuleEngineWrapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ public void When_running_rule_engine_with_basic_workflow_it_should_execute()
var workflowProvider = Substitute.For<IWorkflowProvider>();
workflowProvider.GetAllWorkflows().Returns(CreateFakeWorkflows());

var rulesEngine = new RuleEngineWrapper(workflowProvider);
var resettingsProvider = Substitute.For<IReSettingsProvider>();
resettingsProvider.GetReSettings().Returns(CreateFakeResettings());

var rulesEngine = new RuleEngineWrapper(workflowProvider, resettingsProvider);

//Act
dynamic datas = new ExpandoObject();
Expand Down Expand Up @@ -52,5 +55,11 @@ private List<Workflow> CreateFakeWorkflows()

return workflows;
}

private ReSettings CreateFakeResettings()
{
var reSettingsWithCustomTypes = new ReSettings();
return reSettingsWithCustomTypes;
}
}
}
154 changes: 154 additions & 0 deletions ActionFlow.Tests/Rules/ApiCallRuleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using ActionFlow.Engine;
using ActionFlow.Rules;
using NSubstitute;
using RulesEngine.Models;

namespace ActionFlow.Tests.Rules
{
[TestClass]
public class ApiCallRuleTests
{
[TestMethod]
public void When_running_api_call_rule_with_get_method_it_should_execute()
{
//Arrange
var workflowProvider = Substitute.For<IWorkflowProvider>();
workflowProvider.GetAllWorkflows().Returns(CreateGetApiCallRuleWorkflow());

var rulesEngine = new RuleEngineWrapper(workflowProvider, new ReSettingsProvider());

//Act
var resultList = rulesEngine.ExecuteAllRulesAsync("Test Workflow Rule 1", new object[] { }).Result;

//Assert
Assert.IsTrue(resultList.TrueForAll(x => x.IsSuccess));
}

[TestMethod]
public void When_running_api_call_rule_with_post_method_it_should_execute()
{
//Arrange
var workflowProvider = Substitute.For<IWorkflowProvider>();
workflowProvider.GetAllWorkflows().Returns(CreatePostApiCallRuleWorkflow());

var rulesEngine = new RuleEngineWrapper(workflowProvider, new ReSettingsProvider());

//Act
var resultList = rulesEngine.ExecuteAllRulesAsync("Test Workflow Rule 1", new object[] { }).Result;

//Assert
Assert.IsTrue(resultList.TrueForAll(x => x.IsSuccess));
}

[TestMethod]
public void When_running_api_call_rule_with_headers_it_should_execute_and_return_200_status_code()
{
//Arrange
var workflowProvider = Substitute.For<IWorkflowProvider>();
workflowProvider.GetAllWorkflows().Returns(CreateApiCallRuleWithHeadersWorkflow());

var rulesEngine = new RuleEngineWrapper(workflowProvider, new ReSettingsProvider());

//Act
var resultList = rulesEngine.ExecuteAllRulesAsync("Test Workflow Rule 1", new object[] { }).Result;

//Assert
Assert.IsTrue(resultList.TrueForAll(x => x.IsSuccess));
}

private List<Workflow> CreateGetApiCallRuleWorkflow()
{
List<Workflow> workflows = new List<Workflow>();
Workflow workflow = new Workflow();
workflow.WorkflowName = "Test Workflow Rule 1";

List<Rule> rules = new List<Rule>();

var apiCallRule = new ApiCallRule();
apiCallRule.Name = "Call test api";
apiCallRule.Url = "http://httpbin.org/get";
apiCallRule.ReturnVariableName = "apiResult";

var variableRule2 = new VariableRule();
variableRule2.Name = "Test api call result";
variableRule2.ConditionExpression = "apiResult.Body != null";

apiCallRule.Rules.Add(variableRule2);

rules.Add(apiCallRule.AsRuleEngineRule());
workflow.Rules = rules;
workflows.Add(workflow);

return workflows;
}

private List<Workflow> CreateApiCallRuleWithHeadersWorkflow()
{
List<Workflow> workflows = new List<Workflow>();
Workflow workflow = new Workflow();
workflow.WorkflowName = "Test Workflow Rule 1";

List<Rule> rules = new List<Rule>();

var apiCallRule = new ApiCallRule();
apiCallRule.Name = "Call test api";
apiCallRule.Url = "http://httpbin.org/get";
apiCallRule.Headers = new Dictionary<string, string>
{
{ "test", "value" }
};
apiCallRule.ReturnVariableName = "apiResult";

var variableRule2 = new VariableRule();
variableRule2.Name = "Test api call result";
variableRule2.ConditionExpression = "apiResult.StatusCode == 200";

apiCallRule.Rules.Add(variableRule2);

rules.Add(apiCallRule.AsRuleEngineRule());
workflow.Rules = rules;
workflows.Add(workflow);

return workflows;
}

private List<Workflow> CreatePostApiCallRuleWorkflow()
{
List<Workflow> workflows = new List<Workflow>();
Workflow workflow = new Workflow();
workflow.WorkflowName = "Test Workflow Rule 1";

List<Rule> rules = new List<Rule>();

var apiCallRule = new ApiCallRule();
apiCallRule.Name = "Call test api";
apiCallRule.Method = "POST";
apiCallRule.Url = "http://httpbin.org/post";
apiCallRule.ReturnVariableName = "apiResult";

string jsonString = "{ \"test\": true }";
apiCallRule.Content = jsonString;

var variableRule2 = new VariableRule();
variableRule2.Name = "Test api call result";
variableRule2.Variables = new Dictionary<string, string>
{
{ "apiBodyJson", "bool.Parse(apiResult.Body.json.test.ToString())" }
};
variableRule2.ConditionExpression = "apiResult.Body.json != null";

var variableRule3 = new VariableRule();
variableRule3.Name = "Test apiBodyJson";
variableRule3.ConditionExpression = "apiBodyJson == \"true\"";

variableRule2.Rules.Add(variableRule3);
apiCallRule.Rules.Add(variableRule2);

rules.Add(apiCallRule.AsRuleEngineRule());
workflow.Rules = rules;
workflows.Add(workflow);

return workflows;
}
}
}
8 changes: 6 additions & 2 deletions ActionFlow.Tests/Rules/VariableRuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public void When_running_rule_engine_with_basic_workflow_it_should_execute()
var workflowProvider = Substitute.For<IWorkflowProvider>();
workflowProvider.GetAllWorkflows().Returns(CreateBasicWorkflow());

var rulesEngine = new RuleEngineWrapper(workflowProvider);
var resettingsProvider = Substitute.For<IReSettingsProvider>();

var rulesEngine = new RuleEngineWrapper(workflowProvider, resettingsProvider);

//Act
var resultList = rulesEngine.ExecuteAllRulesAsync("Test Workflow Rule 1", new object[] { }).Result;
Expand All @@ -33,7 +35,9 @@ public void When_running_rule_engine_with_false_condition_on_variable_rule_child
var workflowProvider = Substitute.For<IWorkflowProvider>();
workflowProvider.GetAllWorkflows().Returns(CreateConditionExpressionWorkflow());

var rulesEngine = new RuleEngineWrapper(workflowProvider);
var resettingsProvider = Substitute.For<IReSettingsProvider>();

var rulesEngine = new RuleEngineWrapper(workflowProvider, resettingsProvider);

//Act
var resultList = rulesEngine.ExecuteAllRulesAsync("Test Workflow Rule 1", new object[] { }).Result;
Expand Down
18 changes: 18 additions & 0 deletions ActionFlow/Domain/ApiCallResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Text.Json.Nodes;

namespace ActionFlow.Domain
{
public class ApiCallResult
{
public ApiCallResult(JsonNode body, Dictionary<string, IEnumerable<string>> headers, int statusCode)
{
Body = body;
Headers = headers;
StatusCode = statusCode;
}

public JsonNode Body { get; }
public int StatusCode { get; }
public Dictionary<string, IEnumerable<string>> Headers { get; } = new Dictionary<string, IEnumerable<string>>();
}
}
9 changes: 9 additions & 0 deletions ActionFlow/Engine/IReSettingsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using RulesEngine.Models;

namespace ActionFlow.Engine
{
public interface IReSettingsProvider
{
ReSettings GetReSettings();
}
}
19 changes: 19 additions & 0 deletions ActionFlow/Engine/ReSettingsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using ActionFlow.Domain;
using ActionFlow.Helpers;
using RulesEngine.Models;

namespace ActionFlow.Engine
{
public class ReSettingsProvider : IReSettingsProvider
{
public ReSettings GetReSettings()
{
var reSettingsWithCustomTypes = new ReSettings
{
CustomTypes = new Type[] { typeof(ApiClient), typeof(ApiCallResult) }
};

return reSettingsWithCustomTypes;
}
}
}
4 changes: 2 additions & 2 deletions ActionFlow/Engine/RuleEngineWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public class RuleEngineWrapper : IRuleEngineWrapper
{
private readonly IRulesEngine _rulesEngine;

public RuleEngineWrapper(IWorkflowProvider workflowProvider)
public RuleEngineWrapper(IWorkflowProvider workflowProvider, IReSettingsProvider reSettingsProvider)
{
_rulesEngine = new RulesEngine.RulesEngine(workflowProvider.GetAllWorkflows().ToArray(), null);
_rulesEngine = new RulesEngine.RulesEngine(workflowProvider.GetAllWorkflows().ToArray(), reSettingsProvider.GetReSettings());
}

public ValueTask<List<RuleResultTree>> ExecuteAllRulesAsync(string workflowName, params object[] inputs)
Expand Down
52 changes: 52 additions & 0 deletions ActionFlow/Helpers/ApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using ActionFlow.Domain;
using Newtonsoft.Json;
using System.Text.Json.Nodes;

namespace ActionFlow.Helpers
{
public static class ApiClient
{
public static async Task<ApiCallResult> CallGet(string url, string? requestHeaders = null)
{
var httpClient = new HttpClient();
AddRequestHeaders(requestHeaders, httpClient);

var result = await httpClient.GetAsync(url);
return await CreateApiCallResult(result);
}

public static async Task<ApiCallResult> CallPost(string url, string data, string? requestHeaders = null)
{
var httpClient = new HttpClient();
AddRequestHeaders(requestHeaders, httpClient);

var json = JsonNode.Parse(data);
var content = new StringContent(json!.ToJsonString());
var result = await httpClient.PostAsync(url, content);

return await CreateApiCallResult(result);
}

private static void AddRequestHeaders(string? requestHeaders, HttpClient httpClient)
{
if (requestHeaders != null)
{
var headerDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(requestHeaders);
foreach (var header in headerDictionary!)
{
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
}

private static async Task<ApiCallResult> CreateApiCallResult(HttpResponseMessage result)
{
var jsonBody = JsonNode.Parse(await result.Content.ReadAsStringAsync());
var headers = result.Headers.ToDictionary(x => x.Key, x => x.Value);
var statusCode = (int)result.StatusCode;

var apiCallResult = new ApiCallResult(jsonBody!, headers, statusCode);
return apiCallResult;
}
}
}
44 changes: 44 additions & 0 deletions ActionFlow/Rules/ApiCallRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Newtonsoft.Json;
using RulesEngine.Models;

namespace ActionFlow.Rules
{
public class ApiCallRule : RuleWrapper
{
public override string RuleType => "ApiCall";
public string? Url { get; set; }
public string Method { get; set; } = "GET";
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
public string? ReturnVariableName { get; set; }
public string? Content { get; set; }

public override Rule AsRuleEngineRule()
{
var rule = base.AsRuleEngineRule();
var serializedHeaders = JsonConvert.SerializeObject(JsonConvert.SerializeObject(Headers));
//JsonConvert.SerializeObject(Headers);

switch (Method)
{
case "GET":
rule.LocalParams = new List<ScopedParam>
{
{ new ScopedParam { Name = ReturnVariableName!, Expression = $"ApiClient.CallGet(\"{Url}\", {serializedHeaders}).Result" } }
};
break;

case "POST":
var content = JsonConvert.SerializeObject(Content);

rule.LocalParams = new List<ScopedParam>
{
{ new ScopedParam { Name = ReturnVariableName!, Expression = $"ApiClient.CallPost(\"{Url}\", {content}, {serializedHeaders}).Result" } }
};
break;
}


return rule;
}
}
}
1 change: 0 additions & 1 deletion ActionFlow/Rules/IRuleWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public interface IRuleWrapper
string Name { get; set; }
string RuleType { get; }
string SuccessEvent { get; set; }
Dictionary<string, string> Variables { get; set; }

Rule AsRuleEngineRule();
}
Expand Down
Loading

0 comments on commit 8c3564f

Please sign in to comment.