graph_sp/core/
data.rs

1//! Core data structures for the graph execution engine.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7/// A unique identifier for a port
8pub type PortId = String;
9
10/// A unique identifier for a node
11pub type NodeId = String;
12
13/// Represents data that can be passed between nodes through ports
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub enum PortData {
16    /// No data (unit type)
17    None,
18    /// Boolean value
19    Bool(bool),
20    /// Integer value
21    Int(i64),
22    /// Floating point value
23    Float(f64),
24    /// String value
25    String(String),
26    /// Binary data
27    Bytes(Vec<u8>),
28    /// JSON value
29    Json(serde_json::Value),
30    /// List of port data
31    List(Vec<PortData>),
32    /// Map of port data
33    Map(HashMap<String, PortData>),
34}
35
36impl fmt::Display for PortData {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            PortData::None => write!(f, "None"),
40            PortData::Bool(b) => write!(f, "{}", b),
41            PortData::Int(i) => write!(f, "{}", i),
42            PortData::Float(fl) => write!(f, "{}", fl),
43            PortData::String(s) => write!(f, "\"{}\"", s),
44            PortData::Bytes(b) => write!(f, "Bytes({})", b.len()),
45            PortData::Json(j) => write!(f, "{}", j),
46            PortData::List(l) => write!(f, "List({})", l.len()),
47            PortData::Map(m) => write!(f, "Map({})", m.len()),
48        }
49    }
50}
51
52/// Port configuration for a node
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Port {
55    /// Broadcast name (external name for connections between nodes)
56    pub broadcast_name: PortId,
57    /// Implementation name (internal name used within the node function)
58    pub impl_name: String,
59    /// Human-readable display name
60    pub display_name: String,
61    /// Port description
62    pub description: Option<String>,
63    /// Whether this port is required
64    pub required: bool,
65}
66
67impl Port {
68    /// Create a new required port with separate broadcast and implementation names
69    pub fn new(broadcast_name: impl Into<String>, impl_name: impl Into<String>) -> Self {
70        let broadcast = broadcast_name.into();
71        let impl_name = impl_name.into();
72        let display_name = broadcast.clone();
73        Self {
74            broadcast_name: broadcast,
75            impl_name,
76            display_name,
77            description: None,
78            required: true,
79        }
80    }
81
82    /// Create a port where broadcast and implementation names are the same
83    pub fn simple(name: impl Into<String>) -> Self {
84        let name = name.into();
85        Self::new(name.clone(), name)
86    }
87
88    /// Create a new optional port
89    pub fn optional(broadcast_name: impl Into<String>, impl_name: impl Into<String>) -> Self {
90        let mut port = Self::new(broadcast_name, impl_name);
91        port.required = false;
92        port
93    }
94
95    /// Set the display name for this port
96    pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
97        self.display_name = display_name.into();
98        self
99    }
100
101    /// Set the description for this port
102    pub fn with_description(mut self, description: impl Into<String>) -> Self {
103        self.description = Some(description.into());
104        self
105    }
106}
107
108/// Container for port data within a graph
109#[derive(Debug, Clone, Default, Serialize, Deserialize)]
110pub struct GraphData {
111    /// Map from port ID to port data
112    ports: HashMap<PortId, PortData>,
113}
114
115impl GraphData {
116    /// Create a new empty GraphData
117    pub fn new() -> Self {
118        Self {
119            ports: HashMap::new(),
120        }
121    }
122
123    /// Set data for a port
124    pub fn set(&mut self, port_id: impl Into<PortId>, data: PortData) {
125        self.ports.insert(port_id.into(), data);
126    }
127
128    /// Get data from a port
129    pub fn get(&self, port_id: &str) -> Option<&PortData> {
130        self.ports.get(port_id)
131    }
132
133    /// Remove data from a port
134    pub fn remove(&mut self, port_id: &str) -> Option<PortData> {
135        self.ports.remove(port_id)
136    }
137
138    /// Check if a port has data
139    pub fn has(&self, port_id: &str) -> bool {
140        self.ports.contains_key(port_id)
141    }
142
143    /// Get all port IDs
144    pub fn port_ids(&self) -> impl Iterator<Item = &PortId> {
145        self.ports.keys()
146    }
147
148    /// Clear all port data
149    pub fn clear(&mut self) {
150        self.ports.clear();
151    }
152
153    /// Get the number of ports with data
154    pub fn len(&self) -> usize {
155        self.ports.len()
156    }
157
158    /// Check if the GraphData is empty
159    pub fn is_empty(&self) -> bool {
160        self.ports.is_empty()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_port_creation() {
170        let port = Port::new("input1", "input1");
171        assert_eq!(port.broadcast_name, "input1");
172        assert_eq!(port.display_name, "input1");
173        assert!(port.required);
174        assert!(port.description.is_none());
175    }
176
177    #[test]
178    fn test_optional_port() {
179        let port = Port::optional("opt1", "Optional 1").with_description("An optional port");
180        assert!(!port.required);
181        assert_eq!(port.description.unwrap(), "An optional port");
182    }
183
184    #[test]
185    fn test_graph_data_operations() {
186        let mut data = GraphData::new();
187
188        // Test set and get
189        data.set("port1", PortData::Int(42));
190        assert!(data.has("port1"));
191        assert_eq!(data.len(), 1);
192
193        if let Some(PortData::Int(val)) = data.get("port1") {
194            assert_eq!(*val, 42);
195        } else {
196            panic!("Expected Int(42)");
197        }
198
199        // Test remove
200        let removed = data.remove("port1");
201        assert!(removed.is_some());
202        assert!(!data.has("port1"));
203        assert!(data.is_empty());
204    }
205
206    #[test]
207    fn test_port_data_types() {
208        let mut data = GraphData::new();
209
210        data.set("bool", PortData::Bool(true));
211        data.set("int", PortData::Int(123));
212        data.set("float", PortData::Float(3.14));
213        data.set("string", PortData::String("hello".to_string()));
214
215        assert_eq!(data.len(), 4);
216        assert!(data.has("bool"));
217        assert!(data.has("int"));
218        assert!(data.has("float"));
219        assert!(data.has("string"));
220    }
221}