140 lines
4.8 KiB
Go
140 lines
4.8 KiB
Go
package rules
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
type WorkflowSimulationStep struct {
|
|
NodeID string `json:"node_id"`
|
|
NodeType string `json:"node_type"`
|
|
Handle string `json:"handle,omitempty"`
|
|
}
|
|
|
|
type WorkflowSimulationResult struct {
|
|
Matched bool `json:"matched"`
|
|
Steps []WorkflowSimulationStep `json:"steps,omitempty"`
|
|
Actions []SimulatedActionResult `json:"actions,omitempty"`
|
|
}
|
|
|
|
func (e *Engine) SimulateWorkflow(ctx context.Context, userID string, wf *Workflow, msg *Message, evt *EventContext) WorkflowSimulationResult {
|
|
if wf == nil || len(wf.Nodes) == 0 {
|
|
return WorkflowSimulationResult{Matched: false}
|
|
}
|
|
if wf.Kind != RuleKindFunction && !matchesTriggers(wf.Triggers, msg, evt) {
|
|
return WorkflowSimulationResult{Matched: false}
|
|
}
|
|
startID := wf.findStartNode()
|
|
if startID == "" {
|
|
return WorkflowSimulationResult{Matched: false}
|
|
}
|
|
execCtx := newExecContext(msg, userID, wf.Variables)
|
|
steps := make([]WorkflowSimulationStep, 0)
|
|
e.simulateWalk(ctx, userID, msg, wf, startID, execCtx, &steps, 0)
|
|
simActions := make([]SimulatedActionResult, 0, len(execCtx.Results))
|
|
for _, r := range execCtx.Results {
|
|
simActions = append(simActions, SimulatedActionResult{ActionResult: r})
|
|
}
|
|
return WorkflowSimulationResult{
|
|
Matched: true,
|
|
Steps: steps,
|
|
Actions: simActions,
|
|
}
|
|
}
|
|
|
|
func (e *Engine) simulateWalk(ctx context.Context, userID string, msg *Message, wf *Workflow, nodeID string, execCtx *ExecContext, steps *[]WorkflowSimulationStep, depth int) {
|
|
if depth > maxWorkflowDepth || nodeID == "" {
|
|
return
|
|
}
|
|
nodes := wf.nodeMap()
|
|
node, ok := nodes[nodeID]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
switch node.Type {
|
|
case "start":
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type})
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextDefault(nodeID), execCtx, steps, depth+1)
|
|
|
|
case "condition":
|
|
var data ConditionNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
handle := "false"
|
|
if matchCondition(Condition{Field: data.Field, Operator: data.Operator, Value: interpolateValue(data.Value, execCtx)}, msg) {
|
|
handle = "true"
|
|
}
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type, Handle: handle})
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextNode(nodeID, handle), execCtx, steps, depth+1)
|
|
|
|
case "label_check":
|
|
var data LabelCheckNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
op := "has"
|
|
if data.Operator == "not_has" {
|
|
op = "not_has"
|
|
}
|
|
handle := "false"
|
|
if matchCondition(Condition{Field: "label", Operator: op, Value: data.Label}, msg) {
|
|
handle = "true"
|
|
}
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type, Handle: handle})
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextNode(nodeID, handle), execCtx, steps, depth+1)
|
|
|
|
case "switch":
|
|
var data SwitchNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
fieldVal := workflowFieldValue(data.Field, msg, execCtx)
|
|
handle := "default"
|
|
for i, c := range data.Cases {
|
|
if fieldVal == c.Value {
|
|
handle = fmt.Sprintf("case-%d", i)
|
|
break
|
|
}
|
|
}
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type, Handle: handle})
|
|
next := wf.nextNode(nodeID, handle)
|
|
if next == "" {
|
|
next = wf.nextNode(nodeID, "default")
|
|
}
|
|
e.simulateWalk(ctx, userID, msg, wf, next, execCtx, steps, depth+1)
|
|
|
|
case "llm_check":
|
|
var data LLMCheckNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
handle := "false"
|
|
if e.evaluateLLMCheck(ctx, data, msg, execCtx) {
|
|
handle = "true"
|
|
}
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type, Handle: handle})
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextNode(nodeID, handle), execCtx, steps, depth+1)
|
|
|
|
case "actions":
|
|
var data ActionsNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type})
|
|
for _, item := range data.Actions {
|
|
action := Action{Type: item.Type, Value: interpolateValue(item.Value, execCtx)}
|
|
execCtx.Results = append(execCtx.Results, e.simulateAction(ctx, action, msg).ActionResult)
|
|
}
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextDefault(nodeID), execCtx, steps, depth+1)
|
|
|
|
case "set_var":
|
|
var data SetVarNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
execCtx.Variables[data.Name] = interpolateValue(data.Value, execCtx)
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type})
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextDefault(nodeID), execCtx, steps, depth+1)
|
|
|
|
case "call_function", "call_rule":
|
|
var data CallRuleNodeData
|
|
json.Unmarshal(node.Data, &data)
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type})
|
|
e.simulateWalk(ctx, userID, msg, wf, wf.nextDefault(nodeID), execCtx, steps, depth+1)
|
|
|
|
case "end":
|
|
*steps = append(*steps, WorkflowSimulationStep{NodeID: nodeID, NodeType: node.Type})
|
|
}
|
|
}
|