Skip to content

Commit 3a6cd46

Browse files
authored
Merge pull request #1340 from iceljc/features/refine-rule-graph
add node alias
2 parents 051c45f + 16755eb commit 3a6cd46

5 files changed

Lines changed: 40 additions & 38 deletions

File tree

src/Infrastructure/BotSharp.Abstraction/Rules/RuleGraph.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
114114
Type = payload.Type,
115115
Labels = [.. payload.Labels ?? []],
116116
Weight = payload.Weight,
117-
Purpose = payload.Purpose,
117+
Alias = payload.Alias,
118118
Description = payload.Description,
119119
Config = new(payload.Config ?? [])
120120
});
@@ -234,9 +234,15 @@ public class GraphItem
234234
public virtual string Type { get; set; } = null!;
235235
public virtual IEnumerable<string> Labels { get; set; } = [];
236236
public virtual double Weight { get; set; } = 1.0;
237-
public virtual string? Purpose { get; set; }
238237
public virtual string? Description { get; set; }
239238
public virtual Dictionary<string, string?> Config { get; set; } = [];
239+
240+
private string? _alias;
241+
public virtual string Alias
242+
{
243+
get => string.IsNullOrEmpty(_alias) ? Name : _alias;
244+
set => _alias = value;
245+
}
240246
}
241247

242248
public class NodeItem : GraphItem

src/Infrastructure/BotSharp.Core.Rules/Conditions/LogicGateCondition.cs

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Text.Json;
21
using BotSharp.Core.Rules.Models;
32

43
namespace BotSharp.Core.Rules.Conditions;
@@ -16,11 +15,13 @@ namespace BotSharp.Core.Rules.Conditions;
1615
/// (A AND B) OR (C AND NOT D)
1716
///
1817
/// Leaf node format:
19-
/// { "node": "node_name", "key": "data_key" }
20-
/// - "node": The Name of a parent condition node whose result to inspect.
21-
/// - "key": The key in the parent node's RuleNodeResult.Data dictionary that holds
22-
/// a boolean string ("true"/"false"). If omitted, falls back to the parent
23-
/// node's RuleNodeResult.Success flag.
18+
/// { "node_alias": "node_alias", "key": "data_key" }
19+
/// - "node_alias": The Alias of a parent condition node whose result to inspect.
20+
/// Using Alias instead of Name avoids collisions when multiple nodes
21+
/// share the same Name (e.g. several "http_request" nodes).
22+
/// - "key": The key in the parent node's RuleNodeResult.Data dictionary that holds
23+
/// a boolean string ("true"/"false"). If omitted, falls back to the parent
24+
/// node's RuleNodeResult.Success flag.
2425
///
2526
/// Node config:
2627
/// "expression" - A JSON-encoded LogicExpression tree.
@@ -30,20 +31,20 @@ namespace BotSharp.Core.Rules.Conditions;
3031
/// Example: work_order_valid AND (client_name_valid OR NOT affiliate_name_valid)
3132
///
3233
/// Given three parent condition nodes:
33-
/// - Node A ("check_work_order") returns Data["work_order_valid"] = "true"
34-
/// - Node B ("check_client") returns Data["client_name_valid"] = "false"
35-
/// - Node C ("check_affiliate") returns Data["affiliate_name_valid"] = "false"
34+
/// - Node A (node_alias "check_work_order") returns Data["work_order_valid"] = "true"
35+
/// - Node B (node_alias "check_client") returns Data["client_name_valid"] = "false"
36+
/// - Node C (node_alias "check_affiliate") returns Data["affiliate_name_valid"] = "false"
3637
///
3738
/// The gate node config would be:
3839
/// {
3940
/// "expression": {
4041
/// "op": "and",
4142
/// "children": [
42-
/// { "node": "check_work_order", "key": "work_order_valid" },
43+
/// { "node_alias": "check_work_order", "key": "work_order_valid" },
4344
/// { "op": "or", "children": [
44-
/// { "node": "check_client", "key": "client_name_valid" },
45+
/// { "node_alias": "check_client", "key": "client_name_valid" },
4546
/// { "op": "not", "children": [
46-
/// { "node": "check_affiliate", "key": "affiliate_name_valid" }
47+
/// { "node_alias": "check_affiliate", "key": "affiliate_name_valid" }
4748
/// ]}
4849
/// ]}
4950
/// ]
@@ -142,10 +143,10 @@ public async Task<RuleNodeResult> EvaluateAsync(
142143

143144
var defaultValue = currentNode.Config?.GetValueOrDefault("default_value") ?? "false";
144145

145-
// 3. Build lookup: parent node name → its latest RuleFlowStepResult
146+
// 3. Build lookup: parent node alias → its latest RuleFlowStepResult
146147
var parentResults = (context.PrevStepResults ?? [])
147148
.Where(r => parentNodeIds.Contains(r.Node.Id))
148-
.GroupBy(r => r.Node.Name, StringComparer.OrdinalIgnoreCase)
149+
.GroupBy(r => r.Node.Alias, StringComparer.OrdinalIgnoreCase)
149150
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);
150151

151152
// 4. Evaluate the expression tree
@@ -167,13 +168,13 @@ private bool Evaluate(
167168
Dictionary<string, RuleFlowStepResult> parentResults,
168169
string defaultValue)
169170
{
170-
// Leaf node: look up a specific parent's result
171-
if (!string.IsNullOrEmpty(expr.Node))
171+
// Leaf node: look up a specific parent's result by alias
172+
if (!string.IsNullOrEmpty(expr.NodeAlias))
172173
{
173-
if (!parentResults.TryGetValue(expr.Node, out var stepResult))
174+
if (!parentResults.TryGetValue(expr.NodeAlias, out var stepResult))
174175
{
175-
_logger.LogWarning("Logic gate: parent node '{Node}' not found in results, using default '{Default}'.",
176-
expr.Node, defaultValue);
176+
_logger.LogWarning("Logic gate: parent node alias '{Alias}' not found in results, using default '{Default}'.",
177+
expr.NodeAlias, defaultValue);
177178
return ParseBool(defaultValue);
178179
}
179180

src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ private async Task ExecuteGraphTraversal(
234234
Edge = nextEdge,
235235
Graph = graph,
236236
Text = text,
237-
Parameters = BuildParameters(nextNode.Config, states, innerData),
237+
Parameters = BuildParameters(states, innerData),
238238
PrevStepResults = results,
239239
JsonOptions = options?.JsonOptions
240240
};
@@ -702,17 +702,11 @@ private void ValidateGraphSchema(RuleGraph graph)
702702

703703
#region Private methods
704704
private Dictionary<string, string?> BuildParameters(
705-
Dictionary<string, string?>? config,
706705
IEnumerable<MessageState>? states,
707706
Dictionary<string, string?>? param = null)
708707
{
709708
var dict = new Dictionary<string, string?>();
710709

711-
if (config != null)
712-
{
713-
dict = new(config);
714-
}
715-
716710
if (!states.IsNullOrEmpty())
717711
{
718712
foreach (var state in states!)

src/Infrastructure/BotSharp.Core.Rules/Models/LogicExpression.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ namespace BotSharp.Core.Rules.Models;
44

55
/// <summary>
66
/// Represents a node in a logic expression tree used by LogicGateCondition.
7-
///
8-
/// Leaf node: references a parent node's result by name and an optional custom data key.
9-
/// e.g. { "node": "check_work_order", "key": "work_order_valid" }
10-
///
7+
///
8+
/// Leaf node: references a parent node's result by alias and an optional custom data key.
9+
/// e.g. { "node_alias": "check_work_order", "key": "work_order_valid" }
10+
///
1111
/// Operator node: combines children with "and", "or", or "not".
1212
/// e.g. { "op": "and", "children": [ ... ] }
1313
/// </summary>
1414
public class LogicExpression
1515
{
1616
/// <summary>
17-
/// For leaf nodes: the parent node name whose result to inspect.
17+
/// For leaf nodes: the parent node alias whose result to inspect.
18+
/// Using Alias avoids collisions when multiple nodes share the same Name.
1819
/// </summary>
19-
[JsonPropertyName("node")]
20+
[JsonPropertyName("node_alias")]
2021
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
21-
public string? Node { get; set; }
22+
public string? NodeAlias { get; set; }
2223

2324
/// <summary>
2425
/// For leaf nodes: the key in the parent node's Data dictionary that holds the boolean value.

src/Plugins/BotSharp.Plugin.Membase/Services/DemoRuleGraph.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ private RuleGraph BuildGraph(GraphQueryResult result)
168168
Weight = sourceNodeWeight,
169169
Name = GetGraphItemAttribute(sourceNodeProps, key: "name", defaultValue: "node"),
170170
Type = GetGraphItemAttribute(sourceNodeProps, key: "type", defaultValue: "action"),
171-
Purpose = GetGraphItemAttribute(sourceNodeProps, key: "purpose", defaultValue: ""),
171+
Alias = GetGraphItemAttribute(sourceNodeProps, key: "alias", defaultValue: ""),
172172
Description = GetGraphItemAttribute(sourceNodeProps, key: "description", defaultValue: ""),
173173
Config = GetConfig(sourceNodeProps)
174174
};
@@ -181,7 +181,7 @@ private RuleGraph BuildGraph(GraphQueryResult result)
181181
Weight = targetNodeWeight,
182182
Name = GetGraphItemAttribute(targetNodeProps, key: "name", defaultValue: "node"),
183183
Type = GetGraphItemAttribute(targetNodeProps, key: "type", defaultValue: "action"),
184-
Purpose = GetGraphItemAttribute(targetNodeProps, key: "purpose", defaultValue: ""),
184+
Alias = GetGraphItemAttribute(targetNodeProps, key: "alias", defaultValue: ""),
185185
Description = GetGraphItemAttribute(targetNodeProps, key: "description", defaultValue: ""),
186186
Config = GetConfig(targetNodeProps)
187187
};
@@ -192,7 +192,7 @@ private RuleGraph BuildGraph(GraphQueryResult result)
192192
Id = edgeId ?? Guid.NewGuid().ToString(),
193193
Name = GetGraphItemAttribute(edgeProps, key: "name", defaultValue: "edge"),
194194
Type = GetGraphItemAttribute(edgeProps, key: "type", defaultValue: "next"),
195-
Purpose = GetGraphItemAttribute(edgeProps, key: "purpose", defaultValue: ""),
195+
Alias = GetGraphItemAttribute(edgeProps, key: "alias", defaultValue: ""),
196196
Description = GetGraphItemAttribute(edgeProps, key: "description", defaultValue: ""),
197197
Weight = edgeWeight,
198198
Config = GetConfig(edgeProps)

0 commit comments

Comments
 (0)