velesdb_core/collection/graph/node.rs
1//! Graph node and element types for knowledge graph storage.
2//!
3//! This module provides the core types for representing nodes in a knowledge graph:
4//! - `GraphNode`: A typed entity with properties and optional vector embedding
5//! - `Element`: An enum that unifies Points (vector data) and Nodes (graph entities)
6
7use crate::Point;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// A node in the knowledge graph.
13///
14/// Represents a typed entity with properties and an optional vector embedding.
15/// Nodes are distinct from Points in that they have a label (type) and structured
16/// properties, while Points are primarily vector data with metadata.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use velesdb_core::collection::graph::GraphNode;
22/// use serde_json::json;
23/// use std::collections::HashMap;
24///
25/// let mut props = HashMap::new();
26/// props.insert("name".to_string(), json!("Alice"));
27/// props.insert("age".to_string(), json!(30));
28///
29/// let node = GraphNode::new(1, "Person")
30/// .with_properties(props)
31/// .with_vector(vec![0.1, 0.2, 0.3]);
32/// ```
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct GraphNode {
35 id: u64,
36 label: String,
37 properties: HashMap<String, Value>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 vector: Option<Vec<f32>>,
40}
41
42impl GraphNode {
43 /// Creates a new graph node with the given ID and label.
44 #[must_use]
45 pub fn new(id: u64, label: &str) -> Self {
46 Self {
47 id,
48 label: label.to_string(),
49 properties: HashMap::new(),
50 vector: None,
51 }
52 }
53
54 /// Adds properties to this node (builder pattern).
55 #[must_use]
56 pub fn with_properties(mut self, properties: HashMap<String, Value>) -> Self {
57 self.properties = properties;
58 self
59 }
60
61 /// Adds a vector embedding to this node (builder pattern).
62 #[must_use]
63 pub fn with_vector(mut self, vector: Vec<f32>) -> Self {
64 self.vector = Some(vector);
65 self
66 }
67
68 /// Returns the node ID.
69 #[must_use]
70 pub fn id(&self) -> u64 {
71 self.id
72 }
73
74 /// Returns the node label (type).
75 #[must_use]
76 pub fn label(&self) -> &str {
77 &self.label
78 }
79
80 /// Returns all properties of this node.
81 #[must_use]
82 pub fn properties(&self) -> &HashMap<String, Value> {
83 &self.properties
84 }
85
86 /// Returns a specific property value, if it exists.
87 #[must_use]
88 pub fn property(&self, name: &str) -> Option<&Value> {
89 self.properties.get(name)
90 }
91
92 /// Returns the optional vector embedding.
93 #[must_use]
94 pub fn vector(&self) -> Option<&Vec<f32>> {
95 self.vector.as_ref()
96 }
97
98 /// Sets a property value.
99 pub fn set_property(&mut self, name: &str, value: Value) {
100 self.properties.insert(name.to_string(), value);
101 }
102}
103
104/// A unified element that can be either a Point or a Node.
105///
106/// This enum allows storing both vector data (Points) and graph entities (Nodes)
107/// in the same collection, enabling hybrid graph+vector storage.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(tag = "type", rename_all = "lowercase")]
110#[allow(dead_code)] // Scaffolded for hybrid graph+vector storage
111pub(crate) enum Element {
112 /// A vector point with optional metadata.
113 Point(Point),
114 /// A graph node with label, properties, and optional vector.
115 Node(GraphNode),
116}
117
118#[allow(dead_code)] // Scaffolded for hybrid graph+vector storage
119impl Element {
120 /// Returns the element ID.
121 #[must_use]
122 pub fn id(&self) -> u64 {
123 match self {
124 Self::Point(p) => p.id,
125 Self::Node(n) => n.id(),
126 }
127 }
128
129 /// Returns true if this is a Point.
130 #[must_use]
131 pub fn is_point(&self) -> bool {
132 matches!(self, Self::Point(_))
133 }
134
135 /// Returns true if this is a Node.
136 #[must_use]
137 pub fn is_node(&self) -> bool {
138 matches!(self, Self::Node(_))
139 }
140
141 /// Returns the inner Point if this is a Point.
142 #[must_use]
143 pub fn as_point(&self) -> Option<&Point> {
144 match self {
145 Self::Point(p) => Some(p),
146 Self::Node(_) => None,
147 }
148 }
149
150 /// Returns the inner Node if this is a Node.
151 #[must_use]
152 pub fn as_node(&self) -> Option<&GraphNode> {
153 match self {
154 Self::Point(_) => None,
155 Self::Node(n) => Some(n),
156 }
157 }
158
159 /// Returns true if this element has a vector embedding.
160 ///
161 /// Points always have vectors. Nodes may or may not have vectors.
162 #[must_use]
163 pub fn has_vector(&self) -> bool {
164 match self {
165 Self::Point(_) => true,
166 Self::Node(n) => n.vector().is_some(),
167 }
168 }
169
170 /// Returns the vector embedding if available.
171 #[must_use]
172 pub fn vector(&self) -> Option<&Vec<f32>> {
173 match self {
174 Self::Point(p) => Some(&p.vector),
175 Self::Node(n) => n.vector(),
176 }
177 }
178}