rusty-beads 0.1.0

Git-backed graph issue tracker for AI coding agents - a Rust implementation with context store, dependency tracking, and semantic compaction
Documentation
//! Event type definitions for audit trail.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

/// An audit event recording a change to an issue.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
    /// Unique event ID.
    pub id: i64,
    /// Associated issue ID.
    pub issue_id: String,
    /// Type of change.
    pub event_type: EventType,
    /// Who made the change.
    pub actor: String,
    /// Previous value (if applicable).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub old_value: Option<String>,
    /// New value (if applicable).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub new_value: Option<String>,
    /// Explanatory note.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
    /// When the event occurred.
    pub created_at: DateTime<Utc>,
}

impl Event {
    /// Create a new event.
    pub fn new(
        id: i64,
        issue_id: impl Into<String>,
        event_type: EventType,
        actor: impl Into<String>,
    ) -> Self {
        Self {
            id,
            issue_id: issue_id.into(),
            event_type,
            actor: actor.into(),
            old_value: None,
            new_value: None,
            comment: None,
            created_at: Utc::now(),
        }
    }

    /// Set old and new values.
    pub fn with_change(
        mut self,
        old_value: Option<String>,
        new_value: Option<String>,
    ) -> Self {
        self.old_value = old_value;
        self.new_value = new_value;
        self
    }

    /// Set a comment.
    pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
        self.comment = Some(comment.into());
        self
    }
}

/// The type of audit event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
    /// Issue created.
    Created,
    /// General update.
    Updated,
    /// Status transition.
    StatusChanged,
    /// Comment added.
    Commented,
    /// Issue closed.
    Closed,
    /// Issue reopened.
    Reopened,
    /// Dependency created.
    DependencyAdded,
    /// Dependency removed.
    DependencyRemoved,
    /// Label added.
    LabelAdded,
    /// Label removed.
    LabelRemoved,
    /// Issue compacted.
    Compacted,
}

impl EventType {
    /// Returns the string representation for database storage.
    pub fn as_str(&self) -> &'static str {
        match self {
            EventType::Created => "created",
            EventType::Updated => "updated",
            EventType::StatusChanged => "status_changed",
            EventType::Commented => "commented",
            EventType::Closed => "closed",
            EventType::Reopened => "reopened",
            EventType::DependencyAdded => "dependency_added",
            EventType::DependencyRemoved => "dependency_removed",
            EventType::LabelAdded => "label_added",
            EventType::LabelRemoved => "label_removed",
            EventType::Compacted => "compacted",
        }
    }
}

impl fmt::Display for EventType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl FromStr for EventType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "created" => Ok(EventType::Created),
            "updated" => Ok(EventType::Updated),
            "status_changed" => Ok(EventType::StatusChanged),
            "commented" => Ok(EventType::Commented),
            "closed" => Ok(EventType::Closed),
            "reopened" => Ok(EventType::Reopened),
            "dependency_added" => Ok(EventType::DependencyAdded),
            "dependency_removed" => Ok(EventType::DependencyRemoved),
            "label_added" => Ok(EventType::LabelAdded),
            "label_removed" => Ok(EventType::LabelRemoved),
            "compacted" => Ok(EventType::Compacted),
            _ => Err(format!("unknown event type: {}", s)),
        }
    }
}