1use std::fmt;
2use std::path::Path;
3use std::str::FromStr;
4
5use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
6use rusqlite::{Connection, params};
7
8use crate::config::ActorKind;
9use crate::error::{Error, engine_err};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Act {
14 WorkspaceInit,
16 SourceAttach,
18 Snapshot,
20 PackageFailed,
24 FileTooLarge,
27 SessionOpen,
30 SessionAbandon,
32 LandRequest,
34 Approve,
37 Reject,
40 ApprovalsDismissed,
43 Land,
46 LandParked,
49 Sync,
52 Pull,
55 Undo,
59 SyncParked,
63}
64
65impl Act {
66 #[must_use]
68 pub fn as_str(self) -> &'static str {
69 match self {
70 Self::WorkspaceInit => "workspace_init",
71 Self::SourceAttach => "source_attach",
72 Self::Snapshot => "snapshot",
73 Self::PackageFailed => "package_failed",
74 Self::FileTooLarge => "file_too_large",
75 Self::SessionOpen => "session_open",
76 Self::SessionAbandon => "session_abandon",
77 Self::LandRequest => "land_request",
78 Self::Approve => "approve",
79 Self::Reject => "reject",
80 Self::ApprovalsDismissed => "approvals_dismissed",
81 Self::Land => "land",
82 Self::LandParked => "land_parked",
83 Self::Pull => "pull",
84 Self::Undo => "undo",
85 Self::Sync => "sync",
86 Self::SyncParked => "sync_parked",
87 }
88 }
89}
90
91impl fmt::Display for Act {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.write_str(self.as_str())
94 }
95}
96
97impl FromStr for Act {
98 type Err = Error;
99
100 fn from_str(text: &str) -> Result<Self, Self::Err> {
101 match text {
102 "workspace_init" => Ok(Self::WorkspaceInit),
103 "source_attach" => Ok(Self::SourceAttach),
104 "snapshot" => Ok(Self::Snapshot),
105 "package_failed" => Ok(Self::PackageFailed),
106 "file_too_large" => Ok(Self::FileTooLarge),
107 "session_open" => Ok(Self::SessionOpen),
108 "session_abandon" => Ok(Self::SessionAbandon),
109 "land_request" => Ok(Self::LandRequest),
110 "approve" => Ok(Self::Approve),
111 "reject" => Ok(Self::Reject),
112 "approvals_dismissed" => Ok(Self::ApprovalsDismissed),
113 "land" => Ok(Self::Land),
114 "land_parked" => Ok(Self::LandParked),
115 "pull" => Ok(Self::Pull),
116 "undo" => Ok(Self::Undo),
117 "sync" => Ok(Self::Sync),
118 "sync_parked" => Ok(Self::SyncParked),
119 other => Err(Error::Engine(format!("unknown journal act: {other}"))),
120 }
121 }
122}
123
124impl ToSql for Act {
125 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
126 Ok(ToSqlOutput::from(self.as_str()))
127 }
128}
129
130impl FromSql for Act {
131 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
132 let text = value.as_str()?;
133 Self::from_str(text).map_err(|error| FromSqlError::Other(error.to_string().into()))
134 }
135}
136
137impl ToSql for ActorKind {
138 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
139 Ok(ToSqlOutput::from(self.as_str()))
140 }
141}
142
143impl FromSql for ActorKind {
144 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
145 let text = value.as_str()?;
146 Self::from_str(text).map_err(|error| FromSqlError::Other(error.to_string().into()))
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct JournalEntry {
153 pub at_ms: i64,
155 pub actor_name: String,
157 pub actor_kind: ActorKind,
159 pub act: Act,
161 pub session: Option<String>,
163 pub instruction_summary: Option<String>,
165 pub instruction_run_ref: Option<String>,
167 pub instruction_verbatim: Option<String>,
169 pub reference: Option<String>,
172}
173
174pub struct Journal {
176 conn: Connection,
177}
178
179impl Journal {
180 pub fn open(path: &Path) -> Result<Self, Error> {
182 let conn = crate::store::open_connection(path)?;
183 Ok(Self { conn })
184 }
185
186 pub fn append(&self, entry: &JournalEntry) -> Result<(), Error> {
188 self.conn
189 .execute(
190 "INSERT INTO journal (
191 at_ms, actor_name, actor_kind, act, session,
192 instruction_summary, instruction_run_ref, instruction_verbatim, reference
193 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
194 params![
195 entry.at_ms,
196 entry.actor_name,
197 entry.actor_kind,
198 entry.act,
199 entry.session,
200 entry.instruction_summary,
201 entry.instruction_run_ref,
202 entry.instruction_verbatim,
203 entry.reference,
204 ],
205 )
206 .map_err(engine_err)?;
207 Ok(())
208 }
209
210 pub fn entries(&self, limit: usize) -> Result<Vec<JournalEntry>, Error> {
212 let mut stmt = self
213 .conn
214 .prepare(
215 "SELECT at_ms, actor_name, actor_kind, act, session,
216 instruction_summary, instruction_run_ref, instruction_verbatim, reference
217 FROM journal ORDER BY id DESC LIMIT ?1",
218 )
219 .map_err(engine_err)?;
220 let limit = i64::try_from(limit).map_err(engine_err)?;
221 let rows = stmt
222 .query_map(params![limit], |row| {
223 Ok(JournalEntry {
224 at_ms: row.get(0)?,
225 actor_name: row.get(1)?,
226 actor_kind: row.get(2)?,
227 act: row.get(3)?,
228 session: row.get(4)?,
229 instruction_summary: row.get(5)?,
230 instruction_run_ref: row.get(6)?,
231 instruction_verbatim: row.get(7)?,
232 reference: row.get(8)?,
233 })
234 })
235 .map_err(engine_err)?;
236 let mut entries = Vec::new();
237 for row in rows {
238 entries.push(row.map_err(engine_err)?);
239 }
240 Ok(entries)
241 }
242}