use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EdgeBase<N = Uuid> {
pub source: N,
pub target: N,
pub created_at: DateTime<Utc>,
pub archived_at: Option<DateTime<Utc>>,
}
impl<N> EdgeBase<N> {
pub fn new(source: N, target: N) -> Self {
Self {
source,
target,
created_at: Utc::now(),
archived_at: None,
}
}
}
pub trait Edge {
type NodeId: Copy + Eq + std::hash::Hash;
fn source(&self) -> Self::NodeId;
fn target(&self) -> Self::NodeId;
fn created_at(&self) -> DateTime<Utc>;
fn archived_at(&self) -> Option<DateTime<Utc>>;
fn is_active(&self) -> bool {
self.archived_at().is_none()
}
fn is_archived(&self) -> bool {
self.archived_at().is_some()
}
fn involves(&self, node: Self::NodeId) -> bool {
self.source() == node || self.target() == node
}
fn archive(&mut self);
fn unarchive(&mut self);
fn from_endpoints(source: Self::NodeId, target: Self::NodeId) -> Self
where
Self: Sized;
}
impl<N> Edge for EdgeBase<N>
where
N: Copy + Eq + std::hash::Hash,
{
type NodeId = N;
fn source(&self) -> N {
self.source
}
fn target(&self) -> N {
self.target
}
fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
fn archived_at(&self) -> Option<DateTime<Utc>> {
self.archived_at
}
fn archive(&mut self) {
if self.archived_at.is_none() {
self.archived_at = Some(Utc::now());
}
}
fn unarchive(&mut self) {
self.archived_at = None;
}
fn from_endpoints(source: N, target: N) -> Self {
EdgeBase::new(source, target)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edge_base_new_sets_endpoints_and_active_state() {
let s = Uuid::new_v4();
let t = Uuid::new_v4();
let base = EdgeBase::new(s, t);
assert_eq!(<EdgeBase as Edge>::source(&base), s);
assert_eq!(<EdgeBase as Edge>::target(&base), t);
assert!(base.is_active());
assert!(!base.is_archived());
}
#[test]
fn test_edge_trait_archive_then_unarchive_round_trips_state() {
let mut base = EdgeBase::new(Uuid::new_v4(), Uuid::new_v4());
base.archive();
assert!(base.is_archived());
base.unarchive();
assert!(base.is_active());
}
#[test]
fn test_edge_trait_involves_returns_true_for_both_endpoints_only() {
let s = Uuid::new_v4();
let t = Uuid::new_v4();
let other = Uuid::new_v4();
let base = EdgeBase::new(s, t);
assert!(base.involves(s));
assert!(base.involves(t));
assert!(!base.involves(other));
}
#[test]
fn test_edge_trait_is_object_safe() {
fn _accepts_dyn(_e: &dyn Edge<NodeId = Uuid>) {}
fn _accepts_dyn_mut(_e: &mut dyn Edge<NodeId = Uuid>) {}
}
}