use serde::{Deserialize, Serialize};
use crate::types::MemoryId;
use crate::types::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EdgeType {
Caused,
Before,
Related,
Contradicts,
Supports,
Supersedes,
Derived,
PartOf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEdge {
pub source: MemoryId,
pub target: MemoryId,
pub edge_type: EdgeType,
pub weight: f32,
pub created_at: Timestamp,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub valid_from: Option<Timestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub valid_until: Option<Timestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
impl MemoryEdge {
pub fn is_valid_at(&self, at: Timestamp) -> bool {
let from = self.valid_from.unwrap_or(0);
match self.valid_until {
Some(until) => at >= from && at < until,
None => at >= from,
}
}
pub fn invalidate(&mut self, at: Timestamp) {
self.valid_until = Some(at);
}
pub fn is_invalidated(&self) -> bool {
self.valid_until.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_edge() -> MemoryEdge {
MemoryEdge {
source: MemoryId::new(),
target: MemoryId::new(),
edge_type: EdgeType::Related,
weight: 0.8,
created_at: 1000,
valid_from: None,
valid_until: None,
label: None,
}
}
#[test]
fn unbounded_edge_always_valid() {
let edge = make_edge();
assert!(edge.is_valid_at(0));
assert!(edge.is_valid_at(u64::MAX));
assert!(!edge.is_invalidated());
}
#[test]
fn edge_with_valid_from() {
let mut edge = make_edge();
edge.valid_from = Some(5000);
assert!(!edge.is_valid_at(4999));
assert!(edge.is_valid_at(5000));
assert!(edge.is_valid_at(99999));
}
#[test]
fn edge_with_valid_until() {
let mut edge = make_edge();
edge.valid_until = Some(8000);
assert!(edge.is_valid_at(0));
assert!(edge.is_valid_at(7999));
assert!(!edge.is_valid_at(8000));
assert!(edge.is_invalidated());
}
#[test]
fn edge_with_both_bounds() {
let mut edge = make_edge();
edge.valid_from = Some(1000);
edge.valid_until = Some(5000);
assert!(!edge.is_valid_at(999));
assert!(edge.is_valid_at(1000));
assert!(edge.is_valid_at(3000));
assert!(!edge.is_valid_at(5000));
}
#[test]
fn invalidate_sets_valid_until() {
let mut edge = make_edge();
assert!(!edge.is_invalidated());
edge.invalidate(9000);
assert!(edge.is_invalidated());
assert_eq!(edge.valid_until, Some(9000));
assert!(edge.is_valid_at(8999));
assert!(!edge.is_valid_at(9000));
}
#[test]
fn serde_roundtrip_with_no_bounds() {
let edge = make_edge();
let json = serde_json::to_string(&edge).unwrap();
assert!(!json.contains("valid_from"));
assert!(!json.contains("valid_until"));
let deserialized: MemoryEdge = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.valid_from, None);
assert_eq!(deserialized.valid_until, None);
}
#[test]
fn serde_roundtrip_with_bounds() {
let mut edge = make_edge();
edge.valid_from = Some(1000);
edge.valid_until = Some(5000);
let json = serde_json::to_string(&edge).unwrap();
assert!(json.contains("valid_from"));
assert!(json.contains("valid_until"));
let deserialized: MemoryEdge = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.valid_from, Some(1000));
assert_eq!(deserialized.valid_until, Some(5000));
}
#[test]
fn deserialize_old_format_without_temporal_fields() {
let json = r#"{"source":"00000000-0000-0000-0000-000000000001","target":"00000000-0000-0000-0000-000000000002","edge_type":"Related","weight":0.8,"created_at":1000}"#;
let edge: MemoryEdge = serde_json::from_str(json).unwrap();
assert_eq!(edge.valid_from, None);
assert_eq!(edge.valid_until, None);
assert_eq!(edge.label, None);
assert!(edge.is_valid_at(5000));
}
#[test]
fn edge_with_label() {
let mut edge = make_edge();
edge.label = Some("owns".to_string());
let json = serde_json::to_string(&edge).unwrap();
assert!(json.contains("\"label\":\"owns\""));
let deserialized: MemoryEdge = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.label, Some("owns".to_string()));
}
}