rust_logic_graph/core/
graph.rs

1
2use serde::{Serialize, Deserialize};
3use std::collections::HashMap;
4
5use crate::node::NodeType;
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8pub struct Edge {
9    pub from: String,
10    pub to: String,
11    pub rule: Option<String>,
12}
13
14/// Configuration for a node in the graph
15#[derive(Debug, Serialize, Deserialize, Clone)]
16pub struct NodeConfig {
17    pub node_type: NodeType,
18    #[serde(default)]
19    pub condition: Option<String>,
20    #[serde(default)]
21    pub query: Option<String>,
22    #[serde(default)]
23    pub prompt: Option<String>,
24}
25
26impl NodeConfig {
27    pub fn rule_node(condition: impl Into<String>) -> Self {
28        Self {
29            node_type: NodeType::RuleNode,
30            condition: Some(condition.into()),
31            query: None,
32            prompt: None,
33        }
34    }
35
36    pub fn db_node(query: impl Into<String>) -> Self {
37        Self {
38            node_type: NodeType::DBNode,
39            condition: None,
40            query: Some(query.into()),
41            prompt: None,
42        }
43    }
44
45    pub fn ai_node(prompt: impl Into<String>) -> Self {
46        Self {
47            node_type: NodeType::AINode,
48            condition: None,
49            query: None,
50            prompt: Some(prompt.into()),
51        }
52    }
53    
54    /// Create a GrpcNode configuration
55    pub fn grpc_node(service_url: impl Into<String>, method: impl Into<String>) -> Self {
56        Self {
57            node_type: NodeType::GrpcNode,
58            query: Some(format!("{}#{}", service_url.into(), method.into())),
59            condition: None,
60            prompt: None,
61        }
62    }
63}
64
65#[derive(Debug, Serialize, Deserialize, Clone)]
66pub struct GraphDef {
67    pub nodes: HashMap<String, NodeConfig>,
68    pub edges: Vec<Edge>,
69}
70
71impl GraphDef {
72    /// Create a GraphDef from simple node types (backward compatibility helper)
73    pub fn from_node_types(
74        nodes: HashMap<String, NodeType>,
75        edges: Vec<Edge>,
76    ) -> Self {
77        let nodes = nodes
78            .into_iter()
79            .map(|(id, node_type)| {
80                let config = match node_type {
81                    NodeType::RuleNode => NodeConfig::rule_node("true"),
82                    NodeType::DBNode => NodeConfig::db_node(format!("SELECT * FROM {}", id)),
83                    NodeType::AINode => NodeConfig::ai_node(format!("Process data for {}", id)),
84                    NodeType::GrpcNode => NodeConfig::grpc_node(
85                        format!("http://localhost:50051"),
86                        format!("{}_method", id)
87                    ),
88                };
89                (id, config)
90            })
91            .collect();
92        
93        Self { nodes, edges }
94    }
95    
96    /// Validate graph structure
97    pub fn validate(&self) -> anyhow::Result<()> {
98        // Check for empty graph
99        if self.nodes.is_empty() {
100            return Err(anyhow::anyhow!("Graph has no nodes"));
101        }
102        
103        // Check for invalid edge references
104        for edge in &self.edges {
105            if !self.nodes.contains_key(&edge.from) {
106                return Err(anyhow::anyhow!(
107                    "Edge references non-existent source node: '{}'",
108                    edge.from
109                ));
110            }
111            if !self.nodes.contains_key(&edge.to) {
112                return Err(anyhow::anyhow!(
113                    "Edge references non-existent target node: '{}'",
114                    edge.to
115                ));
116            }
117        }
118        
119        Ok(())
120    }
121    
122    /// Check if graph has disconnected components
123    pub fn has_disconnected_components(&self) -> bool {
124        if self.nodes.is_empty() {
125            return false;
126        }
127        
128        use std::collections::HashSet;
129        let mut visited = HashSet::new();
130        let mut stack = Vec::new();
131        
132        // Start from first node
133        if let Some(first_node) = self.nodes.keys().next() {
134            stack.push(first_node.clone());
135        }
136        
137        // DFS traversal (undirected)
138        while let Some(node) = stack.pop() {
139            if visited.contains(&node) {
140                continue;
141            }
142            visited.insert(node.clone());
143            
144            // Add neighbors (both directions)
145            for edge in &self.edges {
146                if edge.from == node && !visited.contains(&edge.to) {
147                    stack.push(edge.to.clone());
148                }
149                if edge.to == node && !visited.contains(&edge.from) {
150                    stack.push(edge.from.clone());
151                }
152            }
153        }
154        
155        visited.len() < self.nodes.len()
156    }
157}
158
159#[derive(Default)]
160pub struct Context {
161    pub data: HashMap<String, serde_json::Value>,
162}
163
164impl Context {
165    pub fn new() -> Self {
166        Self {
167            data: HashMap::new(),
168        }
169    }
170
171    /// Set a value in the context
172    pub fn set(&mut self, key: impl Into<String>, value: serde_json::Value) {
173        self.data.insert(key.into(), value);
174    }
175
176    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
177        self.data.get(key)
178    }
179    
180    /// Check if key exists in context
181    pub fn contains_key(&self, key: &str) -> bool {
182        self.data.contains_key(key)
183    }
184    
185    /// Remove a value from context
186    pub fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
187        self.data.remove(key)
188    }
189    
190    /// Clear all context data
191    pub fn clear(&mut self) {
192        self.data.clear();
193    }
194}
195
196pub struct Graph {
197    pub def: GraphDef,
198    pub context: Context,
199}
200
201impl Graph {
202    pub fn new(def: GraphDef) -> Self {
203        Self {
204            def,
205            context: Context::default(),
206        }
207    }
208}