Skip to main content

shuttle_rs/
core.rs

1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use thiserror::Error;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum EventType {
11    Message,
12    Memory,
13    Decision,
14    Task,
15    Handoff,
16    Observation,
17    Pattern,
18    Fact,
19    Bug,
20    Artifact,
21}
22
23impl EventType {
24    pub fn as_str(self) -> &'static str {
25        match self {
26            Self::Message => "message",
27            Self::Memory => "memory",
28            Self::Decision => "decision",
29            Self::Task => "task",
30            Self::Handoff => "handoff",
31            Self::Observation => "observation",
32            Self::Pattern => "pattern",
33            Self::Fact => "fact",
34            Self::Bug => "bug",
35            Self::Artifact => "artifact",
36        }
37    }
38}
39
40impl TryFrom<&str> for EventType {
41    type Error = ShuttleError;
42
43    fn try_from(value: &str) -> Result<Self> {
44        match value {
45            "message" => Ok(Self::Message),
46            "memory" => Ok(Self::Memory),
47            "decision" => Ok(Self::Decision),
48            "task" => Ok(Self::Task),
49            "handoff" => Ok(Self::Handoff),
50            "observation" => Ok(Self::Observation),
51            "pattern" => Ok(Self::Pattern),
52            "fact" => Ok(Self::Fact),
53            "bug" => Ok(Self::Bug),
54            "artifact" => Ok(Self::Artifact),
55            other => Err(ShuttleError::InvalidEventType(other.to_owned())),
56        }
57    }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct Event {
62    pub id: Uuid,
63    pub event_type: EventType,
64    pub workspace_id: String,
65    pub repo_id: Option<String>,
66    pub repo_path: Option<String>,
67    pub git_remote: Option<String>,
68    pub bit_repo_id: Option<String>,
69    pub branch: Option<String>,
70    pub commit: Option<String>,
71    pub repo_dirty: Option<bool>,
72    pub agent: String,
73    pub session_id: String,
74    pub title: Option<String>,
75    pub content: String,
76    pub tags: Vec<String>,
77    pub metadata_json: Value,
78    pub created_at: DateTime<Utc>,
79}
80
81impl Event {
82    pub fn new(input: NewEvent) -> Self {
83        Self {
84            id: Uuid::new_v4(),
85            event_type: input.event_type,
86            workspace_id: input.workspace_id,
87            repo_id: input.repo_id,
88            repo_path: input.repo_path,
89            git_remote: input.git_remote,
90            bit_repo_id: input.bit_repo_id,
91            branch: input.branch,
92            commit: input.commit,
93            repo_dirty: input.repo_dirty,
94            agent: input.agent,
95            session_id: input.session_id,
96            title: input.title,
97            content: input.content,
98            tags: input.tags,
99            metadata_json: input.metadata_json,
100            created_at: Utc::now(),
101        }
102    }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct NewEvent {
107    pub event_type: EventType,
108    pub workspace_id: String,
109    pub repo_id: Option<String>,
110    pub repo_path: Option<String>,
111    pub git_remote: Option<String>,
112    pub bit_repo_id: Option<String>,
113    pub branch: Option<String>,
114    pub commit: Option<String>,
115    pub repo_dirty: Option<bool>,
116    pub agent: String,
117    pub session_id: String,
118    pub title: Option<String>,
119    pub content: String,
120    pub tags: Vec<String>,
121    pub metadata_json: Value,
122}
123
124#[derive(Debug, Clone, Default, PartialEq)]
125pub struct EventFilter {
126    pub id: Option<Uuid>,
127    pub event_type: Option<EventType>,
128    pub workspace_id: Option<String>,
129    pub agent: Option<String>,
130    pub recipient: Option<String>,
131    pub tag: Option<String>,
132    pub tags: Vec<String>,
133    pub query: Option<String>,
134    pub limit: Option<u32>,
135}
136
137#[derive(Debug, Error)]
138pub enum ShuttleError {
139    #[error("invalid event type: {0}")]
140    InvalidEventType(String),
141    #[error("store error: {0}")]
142    Store(String),
143    #[error("serialization error: {0}")]
144    Serialization(String),
145}
146
147pub type Result<T> = std::result::Result<T, ShuttleError>;
148
149#[async_trait]
150pub trait EventStore: Send + Sync {
151    async fn append(&self, event: Event) -> Result<Event>;
152    async fn list(&self, filter: EventFilter) -> Result<Vec<Event>>;
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn event_type_round_trips_through_string_values() {
161        assert_eq!(EventType::try_from("memory").unwrap(), EventType::Memory);
162        assert_eq!(EventType::try_from("pattern").unwrap(), EventType::Pattern);
163        assert_eq!(EventType::Fact.as_str(), "fact");
164        assert_eq!(EventType::Bug.as_str(), "bug");
165        assert_eq!(EventType::Message.as_str(), "message");
166        assert!(EventType::try_from("unknown").is_err());
167    }
168}