Skip to main content

ai_crew_sync/
error.rs

1use rmcp::ErrorData;
2
3/// Errors produced by the store layer.
4///
5/// These map onto MCP tool errors so that the calling agent gets a useful
6/// message instead of an opaque "internal error".
7#[derive(Debug, thiserror::Error)]
8pub enum BusError {
9    #[error("not found: {0}")]
10    NotFound(String),
11
12    #[error("invalid input: {0}")]
13    Invalid(String),
14
15    #[error("conflict: {0}")]
16    Conflict(String),
17
18    #[error("unauthenticated: {0}")]
19    Unauthenticated(String),
20
21    #[error("database error: {0}")]
22    Db(#[from] sqlx::Error),
23}
24
25impl BusError {
26    pub fn not_found(msg: impl Into<String>) -> Self {
27        Self::NotFound(msg.into())
28    }
29    pub fn invalid(msg: impl Into<String>) -> Self {
30        Self::Invalid(msg.into())
31    }
32    pub fn conflict(msg: impl Into<String>) -> Self {
33        Self::Conflict(msg.into())
34    }
35}
36
37impl From<BusError> for ErrorData {
38    fn from(err: BusError) -> Self {
39        match err {
40            BusError::NotFound(m) => ErrorData::invalid_params(format!("not found: {m}"), None),
41            BusError::Invalid(m) => ErrorData::invalid_params(m, None),
42            BusError::Conflict(m) => ErrorData::invalid_params(format!("conflict: {m}"), None),
43            BusError::Unauthenticated(m) => {
44                ErrorData::invalid_request(format!("unauthenticated: {m}"), None)
45            }
46            BusError::Db(e) => {
47                // Never leak SQL/connection detail to the model; log it instead.
48                tracing::error!(error = %e, "database error");
49                ErrorData::internal_error("database error", None)
50            }
51        }
52    }
53}
54
55pub type BusResult<T> = Result<T, BusError>;