use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
pub trait GraphModel:
Serialize + DeserializeOwned + JsonSchema + Clone + Send + Sync + 'static
{
fn is_default_knowledge_graph() -> bool {
false
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Node {
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub node_type: String,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Edge {
pub source_node_id: String,
pub target_node_id: String,
pub relationship_name: String,
#[serde(default)]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct KnowledgeGraph {
#[serde(default)]
pub nodes: Vec<Node>,
#[serde(default)]
pub edges: Vec<Edge>,
}
impl KnowledgeGraph {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty() && self.edges.is_empty()
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
}
impl Default for KnowledgeGraph {
fn default() -> Self {
Self::new()
}
}
impl GraphModel for KnowledgeGraph {
fn is_default_knowledge_graph() -> bool {
true
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable failures"
)]
mod tests {
use super::*;
#[test]
fn test_node_serialization() {
let node = Node {
id: "alice_johnson".to_string(),
name: "Alice Johnson".to_string(),
node_type: "PERSON".to_string(),
description: "Software engineer at TechCorp".to_string(),
};
let json = serde_json::to_string(&node).unwrap();
assert!(json.contains("\"type\":\"PERSON\""));
let deserialized: Node = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.node_type, "PERSON");
}
#[test]
fn test_edge_creation() {
let edge = Edge {
source_node_id: "alice_johnson".to_string(),
target_node_id: "techcorp".to_string(),
relationship_name: "works_at".to_string(),
description: None,
};
assert_eq!(edge.relationship_name, "works_at");
}
#[test]
fn test_edge_serializes_description() {
let edge = Edge {
source_node_id: "alice".to_string(),
target_node_id: "acme".to_string(),
relationship_name: "founded".to_string(),
description: Some("Alice founded Acme".to_string()),
};
let json = serde_json::to_string(&edge).unwrap();
assert!(json.contains("\"description\":\"Alice founded Acme\""));
}
#[test]
fn test_edge_deserializes_without_description() {
let json = r#"{
"source_node_id": "alice",
"target_node_id": "acme",
"relationship_name": "founded"
}"#;
let edge: Edge = serde_json::from_str(json).unwrap();
assert_eq!(edge.relationship_name, "founded");
assert_eq!(edge.description, None);
}
#[test]
fn test_edge_deserializes_with_description() {
let json = r#"{
"source_node_id": "alice",
"target_node_id": "acme",
"relationship_name": "founded",
"description": "Alice founded Acme"
}"#;
let edge: Edge = serde_json::from_str(json).unwrap();
assert_eq!(edge.description.as_deref(), Some("Alice founded Acme"));
}
#[test]
fn test_knowledge_graph() {
let mut graph = KnowledgeGraph::new();
assert!(graph.is_empty());
assert_eq!(graph.node_count(), 0);
assert_eq!(graph.edge_count(), 0);
graph.nodes.push(Node {
id: "alice".to_string(),
name: "Alice".to_string(),
node_type: "PERSON".to_string(),
description: "A person".to_string(),
});
graph.edges.push(Edge {
source_node_id: "alice".to_string(),
target_node_id: "techcorp".to_string(),
relationship_name: "works_at".to_string(),
description: None,
});
assert!(!graph.is_empty());
assert_eq!(graph.node_count(), 1);
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_knowledge_graph_is_default() {
assert!(KnowledgeGraph::is_default_knowledge_graph());
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct CustomModel {
items: Vec<String>,
}
impl GraphModel for CustomModel {}
#[test]
fn test_custom_model_is_not_default() {
assert!(!CustomModel::is_default_knowledge_graph());
}
#[test]
fn test_custom_model_roundtrip() {
let model = CustomModel {
items: vec!["a".to_string(), "b".to_string()],
};
let json = serde_json::to_string(&model).unwrap();
let deserialized: CustomModel = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.items, vec!["a", "b"]);
}
}