1use std::{
16 fmt::{Display, Formatter},
17 sync::Arc,
18};
19
20use crate::evaluation::variable_value::VariableValue;
21
22use super::{ElementPropertyMap, ElementValue};
23
24#[derive(Debug, Clone, Hash, PartialEq, Eq)]
25pub struct ElementReference {
26 pub source_id: Arc<str>,
27 pub element_id: Arc<str>,
28}
29
30impl ElementReference {
31 pub fn new(source_id: &str, element_id: &str) -> Self {
32 ElementReference {
33 source_id: Arc::from(source_id),
34 element_id: Arc::from(element_id),
35 }
36 }
37}
38
39impl Display for ElementReference {
40 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}:{}", self.source_id, self.element_id)
42 }
43}
44
45pub type ElementTimestamp = u64;
47
48pub const MAX_REASONABLE_MILLIS_TIMESTAMP: u64 = 10_000_000_000_000;
58
59pub fn validate_effective_from(value: ElementTimestamp) -> Result<(), String> {
81 if value == 0 {
82 return Ok(());
83 }
84 if value > MAX_REASONABLE_MILLIS_TIMESTAMP {
85 return Err(format!(
86 "effective_from value {} ({:.2e}) appears to be in nanoseconds or microseconds, \
87 not milliseconds. Expected a value < {} (~year 2286). \
88 Use timestamp_millis() instead of timestamp_nanos_opt().",
89 value, value as f64, MAX_REASONABLE_MILLIS_TIMESTAMP
90 ));
91 }
92 Ok(())
93}
94
95#[derive(Debug, Clone, Hash, PartialEq, Eq)]
96pub struct ElementMetadata {
97 pub reference: ElementReference,
98 pub labels: Arc<[Arc<str>]>,
99
100 pub effective_from: ElementTimestamp,
102}
103
104impl Display for ElementMetadata {
105 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
106 write!(
107 f,
108 "({}, [{}], {})",
109 self.reference,
110 self.labels.join(","),
111 self.effective_from
112 )
113 }
114}
115
116#[derive(Debug, Clone, Hash, Eq, PartialEq)]
117pub enum Element {
118 Node {
120 metadata: ElementMetadata,
121 properties: ElementPropertyMap,
122 },
123 Relation {
124 metadata: ElementMetadata,
125 in_node: ElementReference,
126 out_node: ElementReference,
127 properties: ElementPropertyMap,
128 },
129}
130
131impl Element {
132 pub fn get_reference(&self) -> &ElementReference {
133 match self {
134 Element::Node { metadata, .. } => &metadata.reference,
135 Element::Relation { metadata, .. } => &metadata.reference,
136 }
137 }
138
139 pub fn get_effective_from(&self) -> ElementTimestamp {
140 match self {
141 Element::Node { metadata, .. } => metadata.effective_from,
142 Element::Relation { metadata, .. } => metadata.effective_from,
143 }
144 }
145
146 pub fn get_metadata(&self) -> &ElementMetadata {
147 match self {
148 Element::Node { metadata, .. } => metadata,
149 Element::Relation { metadata, .. } => metadata,
150 }
151 }
152
153 pub fn get_property(&self, name: &str) -> &ElementValue {
154 let props = match self {
155 Element::Node { properties, .. } => properties,
156 Element::Relation { properties, .. } => properties,
157 };
158 &props[name]
159 }
160
161 pub fn get_properties(&self) -> &ElementPropertyMap {
162 match self {
163 Element::Node { properties, .. } => properties,
164 Element::Relation { properties, .. } => properties,
165 }
166 }
167
168 pub fn merge_missing_properties(&mut self, other: &Element) {
169 match (self, other) {
170 (
171 Element::Node {
172 properties,
173 metadata,
174 },
175 Element::Node {
176 properties: other_properties,
177 metadata: other_metadata,
178 },
179 ) => {
180 assert_eq!(metadata.reference, other_metadata.reference);
181 properties.merge(other_properties);
182 }
183 (
184 Element::Relation {
185 in_node: _,
186 out_node: _,
187 properties,
188 metadata,
189 },
190 Element::Relation {
191 in_node: _other_in_node,
192 out_node: _other_out_node,
193 properties: other_properties,
194 metadata: other_metadata,
195 },
196 ) => {
197 assert_eq!(metadata.reference, other_metadata.reference);
198 properties.merge(other_properties);
199 }
200 _ => panic!("Cannot merge different element types"),
201 }
202 }
203
204 pub fn to_expression_variable(&self) -> VariableValue {
205 VariableValue::Element(Arc::new(self.clone()))
206 }
207
208 pub fn update_effective_time(&mut self, timestamp: ElementTimestamp) {
209 match self {
210 Element::Node { metadata, .. } => metadata.effective_from = timestamp,
211 Element::Relation { metadata, .. } => metadata.effective_from = timestamp,
212 }
213 }
214}
215
216impl From<&Element> for serde_json::Value {
217 fn from(val: &Element) -> Self {
218 match val {
219 Element::Node {
220 metadata,
221 properties,
222 } => {
223 let mut properties: serde_json::Map<String, serde_json::Value> = properties.into();
224
225 properties.insert(
226 "$metadata".to_string(),
227 serde_json::Value::String(metadata.to_string()),
228 );
229
230 serde_json::Value::Object(properties)
231 }
232 Element::Relation {
233 metadata,
234 in_node,
235 out_node,
236 properties,
237 } => {
238 let mut properties: serde_json::Map<String, serde_json::Value> = properties.into();
239
240 properties.insert(
241 "$metadata".to_string(),
242 serde_json::Value::String(metadata.to_string()),
243 );
244
245 properties.insert(
246 "$in_node".to_string(),
247 serde_json::Value::String(in_node.to_string()),
248 );
249 properties.insert(
250 "$out_node".to_string(),
251 serde_json::Value::String(out_node.to_string()),
252 );
253
254 serde_json::Value::Object(properties)
255 }
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn validate_effective_from_accepts_zero() {
266 assert!(validate_effective_from(0).is_ok());
267 }
268
269 #[test]
270 fn validate_effective_from_accepts_valid_millis() {
271 assert!(validate_effective_from(1_771_000_000_000).is_ok());
273 assert!(validate_effective_from(946_684_800_000).is_ok());
275 assert!(validate_effective_from(4_102_444_800_000).is_ok());
277 }
278
279 #[test]
280 fn validate_effective_from_rejects_nanoseconds() {
281 let nanos = 1_771_000_000_000_000_000u64;
283 let result = validate_effective_from(nanos);
284 assert!(result.is_err());
285 assert!(result.unwrap_err().contains("nanoseconds"));
286 }
287
288 #[test]
289 fn validate_effective_from_rejects_microseconds() {
290 let micros = 1_771_000_000_000_000u64;
292 let result = validate_effective_from(micros);
293 assert!(result.is_err());
294 }
295
296 #[test]
297 fn validate_effective_from_accepts_boundary_value() {
298 assert!(validate_effective_from(MAX_REASONABLE_MILLIS_TIMESTAMP - 1).is_ok());
300 assert!(validate_effective_from(MAX_REASONABLE_MILLIS_TIMESTAMP).is_ok());
302 assert!(validate_effective_from(MAX_REASONABLE_MILLIS_TIMESTAMP + 1).is_err());
304 }
305}