fix(error): migrate session submodules + btmsg/bttask backends to AppError

- session/*.rs (sessions, layout, settings, ssh, agents, metrics, anchors)
  now return Result<T, AppError> internally, not just at command boundary
- btmsg.rs and bttask.rs backends migrated to AppError::Database
- 116 cargo tests passing
This commit is contained in:
Hibryda 2026-03-18 01:32:07 +01:00
parent eb04e7e5b5
commit f19b69f018
11 changed files with 264 additions and 255 deletions

View file

@ -4,6 +4,7 @@
use rusqlite::{params, Connection, OpenFlags};
use serde::{Deserialize, Serialize};
use crate::error::AppError;
use std::path::PathBuf;
use std::sync::OnceLock;
@ -24,17 +25,17 @@ fn db_path() -> PathBuf {
})
}
fn open_db() -> Result<Connection, String> {
fn open_db() -> Result<Connection, AppError> {
let path = db_path();
if !path.exists() {
return Err("btmsg database not found".into());
return Err(AppError::database("btmsg database not found"));
}
let conn = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE)
.map_err(|e| format!("Failed to open btmsg.db: {e}"))?;
.map_err(|e| AppError::database(format!("Failed to open btmsg.db: {e}")))?;
conn.query_row("PRAGMA journal_mode=WAL", [], |_| Ok(()))
.map_err(|e| format!("Failed to set WAL mode: {e}"))?;
.map_err(|e| AppError::database(format!("Failed to set WAL mode: {e}")))?;
conn.query_row("PRAGMA busy_timeout = 5000", [], |_| Ok(()))
.map_err(|e| format!("Failed to set busy_timeout: {e}"))?;
.map_err(|e| AppError::database(format!("Failed to set busy_timeout: {e}")))?;
// Migration: add version column if missing
let has_version: i64 = conn
@ -81,7 +82,7 @@ pub struct TaskComment {
}
/// Get all tasks for a group
pub fn list_tasks(group_id: &str) -> Result<Vec<Task>, String> {
pub fn list_tasks(group_id: &str) -> Result<Vec<Task>, AppError> {
let db = open_db()?;
let mut stmt = db
.prepare(
@ -91,7 +92,7 @@ pub fn list_tasks(group_id: &str) -> Result<Vec<Task>, String> {
FROM tasks WHERE group_id = ?1
ORDER BY sort_order ASC, created_at DESC",
)
.map_err(|e| format!("Query error: {e}"))?;
.map_err(|e| AppError::database(format!("Query error: {e}")))?;
let rows = stmt
.query_map(params![group_id], |row| {
@ -111,14 +112,14 @@ pub fn list_tasks(group_id: &str) -> Result<Vec<Task>, String> {
version: row.get::<_, i64>("version").unwrap_or(1),
})
})
.map_err(|e| format!("Query error: {e}"))?;
.map_err(|e| AppError::database(format!("Query error: {e}")))?;
rows.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("Row error: {e}"))
.map_err(|e| AppError::database(format!("Row error: {e}")))
}
/// Get comments for a task
pub fn task_comments(task_id: &str) -> Result<Vec<TaskComment>, String> {
pub fn task_comments(task_id: &str) -> Result<Vec<TaskComment>, AppError> {
let db = open_db()?;
let mut stmt = db
.prepare(
@ -126,7 +127,7 @@ pub fn task_comments(task_id: &str) -> Result<Vec<TaskComment>, String> {
FROM task_comments WHERE task_id = ?1
ORDER BY created_at ASC",
)
.map_err(|e| format!("Query error: {e}"))?;
.map_err(|e| AppError::database(format!("Query error: {e}")))?;
let rows = stmt
.query_map(params![task_id], |row| {
@ -138,20 +139,20 @@ pub fn task_comments(task_id: &str) -> Result<Vec<TaskComment>, String> {
created_at: row.get::<_, String>("created_at").unwrap_or_default(),
})
})
.map_err(|e| format!("Query error: {e}"))?;
.map_err(|e| AppError::database(format!("Query error: {e}")))?;
rows.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("Row error: {e}"))
.map_err(|e| AppError::database(format!("Row error: {e}")))
}
/// Update task status with optimistic locking.
/// `expected_version` must match the current version in the database.
/// Returns the new version on success.
/// When transitioning to 'review', auto-posts to #review-queue channel if it exists.
pub fn update_task_status(task_id: &str, status: &str, expected_version: i64) -> Result<i64, String> {
pub fn update_task_status(task_id: &str, status: &str, expected_version: i64) -> Result<i64, AppError> {
let valid = ["todo", "progress", "review", "done", "blocked"];
if !valid.contains(&status) {
return Err(format!("Invalid status '{}'. Valid: {:?}", status, valid));
return Err(AppError::database(format!("Invalid status '{}'. Valid: {:?}", status, valid)));
}
let db = open_db()?;
@ -171,10 +172,10 @@ pub fn update_task_status(task_id: &str, status: &str, expected_version: i64) ->
WHERE id = ?2 AND version = ?3",
params![status, task_id, expected_version],
)
.map_err(|e| format!("Update error: {e}"))?;
.map_err(|e| AppError::database(format!("Update error: {e}")))?;
if rows_affected == 0 {
return Err("Task was modified by another agent (version conflict)".into());
return Err(AppError::database("Task was modified by another agent (version conflict)"));
}
let new_version = expected_version + 1;
@ -248,25 +249,25 @@ fn ensure_review_channels(db: &Connection, group_id: &str) -> Option<String> {
}
/// Count tasks in 'review' status for a group
pub fn review_queue_count(group_id: &str) -> Result<i64, String> {
pub fn review_queue_count(group_id: &str) -> Result<i64, AppError> {
let db = open_db()?;
db.query_row(
"SELECT COUNT(*) FROM tasks WHERE group_id = ?1 AND status = 'review'",
params![group_id],
|row| row.get(0),
)
.map_err(|e| format!("Query error: {e}"))
.map_err(|e| AppError::database(format!("Query error: {e}")))
}
/// Add a comment to a task
pub fn add_comment(task_id: &str, agent_id: &str, content: &str) -> Result<String, String> {
pub fn add_comment(task_id: &str, agent_id: &str, content: &str) -> Result<String, AppError> {
let db = open_db()?;
let id = uuid::Uuid::new_v4().to_string();
db.execute(
"INSERT INTO task_comments (id, task_id, agent_id, content) VALUES (?1, ?2, ?3, ?4)",
params![id, task_id, agent_id, content],
)
.map_err(|e| format!("Insert error: {e}"))?;
.map_err(|e| AppError::database(format!("Insert error: {e}")))?;
Ok(id)
}
@ -278,7 +279,7 @@ pub fn create_task(
group_id: &str,
created_by: &str,
assigned_to: Option<&str>,
) -> Result<String, String> {
) -> Result<String, AppError> {
let db = open_db()?;
let id = uuid::Uuid::new_v4().to_string();
db.execute(
@ -286,17 +287,17 @@ pub fn create_task(
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
params![id, title, description, priority, group_id, created_by, assigned_to],
)
.map_err(|e| format!("Insert error: {e}"))?;
.map_err(|e| AppError::database(format!("Insert error: {e}")))?;
Ok(id)
}
/// Delete a task
pub fn delete_task(task_id: &str) -> Result<(), String> {
pub fn delete_task(task_id: &str) -> Result<(), AppError> {
let db = open_db()?;
db.execute("DELETE FROM task_comments WHERE task_id = ?1", params![task_id])
.map_err(|e| format!("Delete comments error: {e}"))?;
.map_err(|e| AppError::database(format!("Delete comments error: {e}")))?;
db.execute("DELETE FROM tasks WHERE id = ?1", params![task_id])
.map_err(|e| format!("Delete task error: {e}"))?;
.map_err(|e| AppError::database(format!("Delete task error: {e}")))?;
Ok(())
}