datasynth_ocpm/models/
object_relationship.rs1use chrono::{DateTime, Utc};
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use uuid::Uuid;
12
13use super::ObjectAttributeValue;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ObjectRelationship {
18 pub relationship_id: Uuid,
20 pub relationship_type: String,
22 pub source_object_id: Uuid,
24 pub source_type_id: String,
26 pub target_object_id: Uuid,
28 pub target_type_id: String,
30 pub established_at: DateTime<Utc>,
32 pub quantity: Option<Decimal>,
34 pub attributes: HashMap<String, ObjectAttributeValue>,
36}
37
38impl ObjectRelationship {
39 pub fn new(
41 relationship_type: &str,
42 source_object_id: Uuid,
43 source_type_id: &str,
44 target_object_id: Uuid,
45 target_type_id: &str,
46 ) -> Self {
47 Self {
48 relationship_id: Uuid::new_v4(),
49 relationship_type: relationship_type.into(),
50 source_object_id,
51 source_type_id: source_type_id.into(),
52 target_object_id,
53 target_type_id: target_type_id.into(),
54 established_at: Utc::now(),
55 quantity: None,
56 attributes: HashMap::new(),
57 }
58 }
59
60 pub fn with_timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
62 self.established_at = timestamp;
63 self
64 }
65
66 pub fn with_quantity(mut self, quantity: Decimal) -> Self {
68 self.quantity = Some(quantity);
69 self
70 }
71
72 pub fn with_attribute(mut self, key: &str, value: ObjectAttributeValue) -> Self {
74 self.attributes.insert(key.into(), value);
75 self
76 }
77}
78
79#[derive(Debug, Clone, Default, Serialize, Deserialize)]
81pub struct RelationshipIndex {
82 relationships: Vec<ObjectRelationship>,
84 by_source: HashMap<Uuid, Vec<usize>>,
86 by_target: HashMap<Uuid, Vec<usize>>,
88 by_type: HashMap<String, Vec<usize>>,
90}
91
92impl RelationshipIndex {
93 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn add(&mut self, relationship: ObjectRelationship) {
100 let idx = self.relationships.len();
101
102 self.by_source
103 .entry(relationship.source_object_id)
104 .or_default()
105 .push(idx);
106
107 self.by_target
108 .entry(relationship.target_object_id)
109 .or_default()
110 .push(idx);
111
112 self.by_type
113 .entry(relationship.relationship_type.clone())
114 .or_default()
115 .push(idx);
116
117 self.relationships.push(relationship);
118 }
119
120 pub fn get_outgoing(&self, source_id: Uuid) -> Vec<&ObjectRelationship> {
122 self.by_source
123 .get(&source_id)
124 .map(|indices| {
125 indices
126 .iter()
127 .filter_map(|&i| self.relationships.get(i))
128 .collect()
129 })
130 .unwrap_or_default()
131 }
132
133 pub fn get_incoming(&self, target_id: Uuid) -> Vec<&ObjectRelationship> {
135 self.by_target
136 .get(&target_id)
137 .map(|indices| {
138 indices
139 .iter()
140 .filter_map(|&i| self.relationships.get(i))
141 .collect()
142 })
143 .unwrap_or_default()
144 }
145
146 pub fn get_by_type(&self, relationship_type: &str) -> Vec<&ObjectRelationship> {
148 self.by_type
149 .get(relationship_type)
150 .map(|indices| {
151 indices
152 .iter()
153 .filter_map(|&i| self.relationships.get(i))
154 .collect()
155 })
156 .unwrap_or_default()
157 }
158
159 pub fn all(&self) -> &[ObjectRelationship] {
161 &self.relationships
162 }
163
164 pub fn len(&self) -> usize {
166 self.relationships.len()
167 }
168
169 pub fn is_empty(&self) -> bool {
171 self.relationships.is_empty()
172 }
173
174 pub fn iter(&self) -> impl Iterator<Item = &ObjectRelationship> {
176 self.relationships.iter()
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_relationship_creation() {
186 let source_id = Uuid::new_v4();
187 let target_id = Uuid::new_v4();
188
189 let rel = ObjectRelationship::new(
190 "contains",
191 source_id,
192 "purchase_order",
193 target_id,
194 "order_line",
195 )
196 .with_quantity(Decimal::from(10));
197
198 assert_eq!(rel.relationship_type, "contains");
199 assert_eq!(rel.source_object_id, source_id);
200 assert_eq!(rel.target_object_id, target_id);
201 assert_eq!(rel.quantity, Some(Decimal::from(10)));
202 }
203
204 #[test]
205 fn test_relationship_index() {
206 let mut index = RelationshipIndex::new();
207
208 let po_id = Uuid::new_v4();
209 let line1_id = Uuid::new_v4();
210 let line2_id = Uuid::new_v4();
211
212 index.add(ObjectRelationship::new(
213 "contains",
214 po_id,
215 "purchase_order",
216 line1_id,
217 "order_line",
218 ));
219 index.add(ObjectRelationship::new(
220 "contains",
221 po_id,
222 "purchase_order",
223 line2_id,
224 "order_line",
225 ));
226
227 assert_eq!(index.len(), 2);
228 assert_eq!(index.get_outgoing(po_id).len(), 2);
229 assert_eq!(index.get_incoming(line1_id).len(), 1);
230 assert_eq!(index.get_by_type("contains").len(), 2);
231 }
232}