velesdb_core/collection/graph/
schema.rs1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use crate::error::{Error, Result};
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15#[non_exhaustive]
16pub enum ValueType {
17 String,
19 Integer,
21 Float,
23 Boolean,
25 Vector,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct NodeType {
32 name: String,
33 properties: HashMap<String, ValueType>,
34}
35
36impl NodeType {
37 #[must_use]
39 pub fn new(name: &str) -> Self {
40 Self {
41 name: name.to_string(),
42 properties: HashMap::new(),
43 }
44 }
45
46 #[must_use]
48 pub fn with_properties(mut self, properties: HashMap<String, ValueType>) -> Self {
49 self.properties = properties;
50 self
51 }
52
53 #[must_use]
55 pub fn name(&self) -> &str {
56 &self.name
57 }
58
59 #[must_use]
61 pub fn properties(&self) -> &HashMap<String, ValueType> {
62 &self.properties
63 }
64
65 #[must_use]
67 pub fn property_type(&self, name: &str) -> Option<&ValueType> {
68 self.properties.get(name)
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct EdgeType {
75 name: String,
76 from_type: String,
77 to_type: String,
78 properties: HashMap<String, ValueType>,
79}
80
81impl EdgeType {
82 #[must_use]
84 pub fn new(name: &str, from_type: &str, to_type: &str) -> Self {
85 Self {
86 name: name.to_string(),
87 from_type: from_type.to_string(),
88 to_type: to_type.to_string(),
89 properties: HashMap::new(),
90 }
91 }
92
93 #[must_use]
95 pub fn with_properties(mut self, properties: HashMap<String, ValueType>) -> Self {
96 self.properties = properties;
97 self
98 }
99
100 #[must_use]
102 pub fn name(&self) -> &str {
103 &self.name
104 }
105
106 #[must_use]
108 pub fn from_type(&self) -> &str {
109 &self.from_type
110 }
111
112 #[must_use]
114 pub fn to_type(&self) -> &str {
115 &self.to_type
116 }
117
118 #[must_use]
120 pub fn properties(&self) -> &HashMap<String, ValueType> {
121 &self.properties
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub struct GraphSchema {
132 schemaless: bool,
133 node_types: Vec<NodeType>,
134 edge_types: Vec<EdgeType>,
135}
136
137impl Default for GraphSchema {
138 fn default() -> Self {
139 Self::schemaless()
140 }
141}
142
143impl GraphSchema {
144 #[must_use]
148 pub fn new() -> Self {
149 Self {
150 schemaless: false,
151 node_types: Vec::new(),
152 edge_types: Vec::new(),
153 }
154 }
155
156 #[must_use]
158 pub fn schemaless() -> Self {
159 Self {
160 schemaless: true,
161 node_types: Vec::new(),
162 edge_types: Vec::new(),
163 }
164 }
165
166 #[must_use]
168 pub fn with_node_type(mut self, node_type: NodeType) -> Self {
169 self.node_types.push(node_type);
170 self
171 }
172
173 #[must_use]
175 pub fn with_edge_type(mut self, edge_type: EdgeType) -> Self {
176 self.edge_types.push(edge_type);
177 self
178 }
179
180 #[must_use]
182 pub fn is_schemaless(&self) -> bool {
183 self.schemaless
184 }
185
186 #[must_use]
188 pub fn node_types(&self) -> &[NodeType] {
189 &self.node_types
190 }
191
192 #[must_use]
194 pub fn edge_types(&self) -> &[EdgeType] {
195 &self.edge_types
196 }
197
198 #[must_use]
200 pub fn has_node_type(&self, name: &str) -> bool {
201 self.node_types.iter().any(|nt| nt.name == name)
202 }
203
204 #[must_use]
206 pub fn has_edge_type(&self, name: &str) -> bool {
207 self.edge_types.iter().any(|et| et.name == name)
208 }
209
210 pub fn validate_node_type(&self, type_name: &str) -> Result<()> {
218 if self.schemaless {
219 return Ok(());
220 }
221
222 if self.has_node_type(type_name) {
223 return Ok(());
224 }
225
226 let allowed: Vec<&str> = self.node_types.iter().map(|nt| nt.name.as_str()).collect();
227 Err(Error::SchemaValidation(format!(
228 "Node type '{type_name}' not allowed. Valid types: {allowed:?}",
229 )))
230 }
231
232 pub fn validate_edge_type(
241 &self,
242 edge_type: &str,
243 from_type: &str,
244 to_type: &str,
245 ) -> Result<()> {
246 if self.schemaless {
247 return Ok(());
248 }
249
250 let edge_def = self.edge_types.iter().find(|et| et.name == edge_type);
252
253 if let Some(def) = edge_def {
254 if def.from_type != from_type {
256 return Err(Error::SchemaValidation(format!(
257 "Edge '{edge_type}' expects source type '{}', got '{from_type}'",
258 def.from_type
259 )));
260 }
261 if def.to_type != to_type {
263 return Err(Error::SchemaValidation(format!(
264 "Edge '{edge_type}' expects target type '{}', got '{to_type}'",
265 def.to_type
266 )));
267 }
268 if !self.has_node_type(from_type) {
270 return Err(Error::SchemaValidation(format!(
271 "Edge '{edge_type}' references undeclared source node type '{from_type}'",
272 )));
273 }
274 if !self.has_node_type(to_type) {
275 return Err(Error::SchemaValidation(format!(
276 "Edge '{edge_type}' references undeclared target node type '{to_type}'",
277 )));
278 }
279 Ok(())
280 } else {
281 let allowed: Vec<&str> = self.edge_types.iter().map(|et| et.name.as_str()).collect();
282 Err(Error::SchemaValidation(format!(
283 "Edge type '{edge_type}' not allowed. Valid types: {allowed:?}",
284 )))
285 }
286 }
287
288 #[must_use]
290 pub fn get_node_type(&self, name: &str) -> Option<&NodeType> {
291 self.node_types.iter().find(|nt| nt.name == name)
292 }
293
294 #[must_use]
296 pub fn get_edge_type(&self, name: &str) -> Option<&EdgeType> {
297 self.edge_types.iter().find(|et| et.name == name)
298 }
299}