bonds-core 0.1.6

Core library for managing symlink-based bonds with SQLite persistence
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// Representation of a bond (source -> target)
/// The `Bond` struct encapsulates the properties of a bond, including its unique identifier, optional name, source and target paths, creation timestamp, and optional metadata. It provides methods for creating new bonds, accessing its fields, and serializing the creation timestamp for database storage. The struct is designed to be easily serializable to JSON for storage in a SQLite database and includes tests to ensure the uniqueness of IDs, correct serialization/deserialization, and proper handling of timestamps.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Bond {
    pub(crate) id: String,
    pub(crate) name: Option<String>,
    pub(crate) source: PathBuf,
    pub(crate) target: PathBuf,
    pub(crate) created_at: DateTime<Utc>,
    pub(crate) metadata: Option<HashMap<String, String>>,
}

impl Bond {
    /// Create a new Bond with a UUID and current timestamp.
    pub fn new(source: PathBuf, target: PathBuf, name: Option<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            name,
            source,
            target,
            created_at: Utc::now(),
            metadata: None,
        }
    }

    /// Helper to serialize `created_at` for DB storage.
    pub fn created_at_rfc3339(&self) -> String {
        self.created_at.to_rfc3339()
    }
    /// Returns the bond's unique identifier (UUID string).
    pub fn id(&self) -> &str {
        &self.id
    }
    /// Returns the optional human-friendly bond name.
    pub fn name(&self) -> Option<&str> {
        self.name.as_deref()
    }
    /// Returns the source path this bond links from.
    pub fn source(&self) -> &Path {
        &self.source
    }
    /// Returns the target symlink path this bond links to.
    pub fn target(&self) -> &Path {
        &self.target
    }
    /// Returns the timestamp when the bond was created.
    pub fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    /// Returns optional key/value metadata associated with this bond.
    pub fn metadata(&self) -> Option<&HashMap<String, String>> {
        self.metadata.as_ref()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_generates_unique_ids() {
        let a = Bond::new(
            PathBuf::from("/a"),
            PathBuf::from("/b"),
            Some("bond_a".to_string()),
        );
        let b = Bond::new(
            PathBuf::from("/a"),
            PathBuf::from("/b"),
            Some("bond_b".to_string()),
        );
        assert_ne!(a.id, b.id); // UUID v4 should never collide
    }

    #[test]
    fn created_at_rfc3339_roundtrips() {
        let bond = Bond::new(
            PathBuf::from("/a"),
            PathBuf::from("/b"),
            Some("bond".to_string()),
        );
        let rfc = bond.created_at_rfc3339();
        // Verify it parses back cleanly
        let parsed = DateTime::parse_from_rfc3339(&rfc).unwrap();
        assert_eq!(parsed.with_timezone(&Utc), bond.created_at);
    }

    #[test]
    fn serializes_to_json() {
        let bond = Bond::new(
            PathBuf::from("/src"),
            PathBuf::from("/tgt"),
            Some("bond".to_string()),
        );
        let json = serde_json::to_string(&bond).unwrap();
        let deserialized: Bond = serde_json::from_str(&json).unwrap();
        assert_eq!(bond, deserialized);
    }
}