bonds_core/manager/
storage.rs1use super::*;
2use std::fs;
3
4impl BondManager {
6 pub fn new(db_path: Option<PathBuf>) -> Result<Self, BondError> {
8 let db_path = db_path.unwrap_or_else(|| {
9 std::env::var("HOME")
10 .map(PathBuf::from)
11 .unwrap_or_else(|_| PathBuf::from("."))
12 .join(".bonds")
13 .join("bonds.db")
14 });
15
16 if let Some(parent) = db_path.parent() {
17 fs::create_dir_all(parent)?;
18 }
19
20 let conn = Connection::open(db_path)?;
21 Self::from_connection(conn)
22 }
23
24 pub fn list_bonds(&self) -> Result<Vec<Bond>, BondError> {
26 let mut stmt = self.conn.prepare(
27 "SELECT id, name, source, target, created_at, metadata FROM bonds ORDER BY created_at DESC",
28 )?;
29 let mut rows = stmt.query([])?;
30
31 let mut out = Vec::new();
32 while let Some(row) = rows.next()? {
33 out.push(self.bond_from_row(row)?);
34 }
35 Ok(out)
36 }
37
38 pub fn get_bond(&self, identifier: &str) -> Result<Bond, BondError> {
40 let mut stmt = self.conn.prepare(
42 "SELECT id, name, source, target, created_at, metadata FROM bonds WHERE name = ?1",
43 )?;
44 let mut rows = stmt.query(params![identifier])?;
45 if let Some(row) = rows.next()? {
46 return self.bond_from_row(row);
47 }
48 drop(rows);
49 drop(stmt);
50
51 let mut stmt = self.conn.prepare(
53 "SELECT id, name, source, target, created_at, metadata FROM bonds WHERE id LIKE ?1 || '%'",
54 )?;
55 let mut rows = stmt.query(params![identifier])?;
56
57 let first = match rows.next()? {
58 Some(row) => self.bond_from_row(row)?,
59 None => return Err(BondError::NotFound(identifier.to_string())),
60 };
61
62 if rows.next()?.is_some() {
63 return Err(BondError::AmbiguousId(identifier.to_string()));
64 }
65
66 Ok(first)
67 }
68
69 fn bond_from_row(&self, row: &rusqlite::Row) -> Result<Bond, BondError> {
71 let id: String = row.get(0)?;
72 let name: Option<String> = row.get(1)?;
73 let source: String = row.get(2)?;
74 let target: String = row.get(3)?;
75 let created_at_str: String = row.get(4)?;
76 let metadata_json: Option<String> = row.get(5)?;
77
78 let created_at = DateTime::parse_from_rfc3339(&created_at_str)
79 .map(|dt| dt.with_timezone(&Utc))
80 .map_err(|e| BondError::InvalidTimestamp(e.to_string()))?;
81
82 let metadata = match metadata_json {
83 Some(s) => Some(serde_json::from_str(&s)?),
84 None => None,
85 };
86
87 Ok(Bond {
88 id,
89 name,
90 source: PathBuf::from(source),
91 target: PathBuf::from(target),
92 created_at,
93 metadata,
94 })
95 }
96
97 pub(crate) fn from_connection(conn: Connection) -> Result<Self, BondError> {
99 conn.execute_batch(
100 "CREATE TABLE IF NOT EXISTS bonds (
101 id TEXT PRIMARY KEY,
102 name TEXT,
103 source TEXT NOT NULL,
104 target TEXT NOT NULL,
105 created_at TEXT NOT NULL,
106 metadata TEXT
107 );",
108 )?;
109
110 let _ = conn.execute_batch("ALTER TABLE bonds ADD COLUMN name TEXT;");
112 let _ = conn.execute_batch("ALTER TABLE bonds ADD COLUMN metadata TEXT;");
113
114 Ok(Self {
115 conn,
116 hooks: RwLock::new(Vec::new()),
117 })
118 }
119}