use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
pub type PortId = String;
pub type NodeId = String;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PortData {
None,
Bool(bool),
Int(i64),
Float(f64),
String(String),
Bytes(Vec<u8>),
Json(serde_json::Value),
List(Vec<PortData>),
Map(HashMap<String, PortData>),
}
impl fmt::Display for PortData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PortData::None => write!(f, "None"),
PortData::Bool(b) => write!(f, "{}", b),
PortData::Int(i) => write!(f, "{}", i),
PortData::Float(fl) => write!(f, "{}", fl),
PortData::String(s) => write!(f, "\"{}\"", s),
PortData::Bytes(b) => write!(f, "Bytes({})", b.len()),
PortData::Json(j) => write!(f, "{}", j),
PortData::List(l) => write!(f, "List({})", l.len()),
PortData::Map(m) => write!(f, "Map({})", m.len()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Port {
pub broadcast_name: PortId,
pub impl_name: String,
pub display_name: String,
pub description: Option<String>,
pub required: bool,
}
impl Port {
pub fn new(broadcast_name: impl Into<String>, impl_name: impl Into<String>) -> Self {
let broadcast = broadcast_name.into();
let impl_name = impl_name.into();
let display_name = broadcast.clone();
Self {
broadcast_name: broadcast,
impl_name,
display_name,
description: None,
required: true,
}
}
pub fn simple(name: impl Into<String>) -> Self {
let name = name.into();
Self::new(name.clone(), name)
}
pub fn optional(broadcast_name: impl Into<String>, impl_name: impl Into<String>) -> Self {
let mut port = Self::new(broadcast_name, impl_name);
port.required = false;
port
}
pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
self.display_name = display_name.into();
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GraphData {
ports: HashMap<PortId, PortData>,
}
impl GraphData {
pub fn new() -> Self {
Self {
ports: HashMap::new(),
}
}
pub fn set(&mut self, port_id: impl Into<PortId>, data: PortData) {
self.ports.insert(port_id.into(), data);
}
pub fn get(&self, port_id: &str) -> Option<&PortData> {
self.ports.get(port_id)
}
pub fn remove(&mut self, port_id: &str) -> Option<PortData> {
self.ports.remove(port_id)
}
pub fn has(&self, port_id: &str) -> bool {
self.ports.contains_key(port_id)
}
pub fn port_ids(&self) -> impl Iterator<Item = &PortId> {
self.ports.keys()
}
pub fn clear(&mut self) {
self.ports.clear();
}
pub fn len(&self) -> usize {
self.ports.len()
}
pub fn is_empty(&self) -> bool {
self.ports.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_port_creation() {
let port = Port::new("input1", "input1");
assert_eq!(port.broadcast_name, "input1");
assert_eq!(port.display_name, "input1");
assert!(port.required);
assert!(port.description.is_none());
}
#[test]
fn test_optional_port() {
let port = Port::optional("opt1", "Optional 1").with_description("An optional port");
assert!(!port.required);
assert_eq!(port.description.unwrap(), "An optional port");
}
#[test]
fn test_graph_data_operations() {
let mut data = GraphData::new();
data.set("port1", PortData::Int(42));
assert!(data.has("port1"));
assert_eq!(data.len(), 1);
if let Some(PortData::Int(val)) = data.get("port1") {
assert_eq!(*val, 42);
} else {
panic!("Expected Int(42)");
}
let removed = data.remove("port1");
assert!(removed.is_some());
assert!(!data.has("port1"));
assert!(data.is_empty());
}
#[test]
fn test_port_data_types() {
let mut data = GraphData::new();
data.set("bool", PortData::Bool(true));
data.set("int", PortData::Int(123));
data.set("float", PortData::Float(3.14));
data.set("string", PortData::String("hello".to_string()));
assert_eq!(data.len(), 4);
assert!(data.has("bool"));
assert!(data.has("int"));
assert!(data.has("float"));
assert!(data.has("string"));
}
}