Skip to main content

talon_core/
error.rs

1//! Error types for Talon core operations.
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6/// Result alias for Talon core operations.
7pub type TalonResult<T> = Result<T, TalonError>;
8
9/// Error codes used in the MCP and CLI output envelope.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum ErrorCode {
13    /// Invalid scope name in config.
14    InvalidScope,
15    /// Invalid `--where` filter expression.
16    InvalidWhere,
17    /// Invalid `--since` timestamp.
18    InvalidSince,
19    /// Database busy (sync lock held).
20    DbBusy,
21    /// Database corruption detected.
22    DbCorrupt,
23    /// No index exists for the configured vault.
24    NotIndexed,
25    /// Internal/server error.
26    Internal,
27}
28
29impl std::fmt::Display for ErrorCode {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::InvalidScope => write!(f, "invalid-scope"),
33            Self::InvalidWhere => write!(f, "invalid-where"),
34            Self::InvalidSince => write!(f, "invalid-since"),
35            Self::DbBusy => write!(f, "db-busy"),
36            Self::DbCorrupt => write!(f, "db-corrupt"),
37            Self::NotIndexed => write!(f, "not-indexed"),
38            Self::Internal => write!(f, "internal"),
39        }
40    }
41}
42
43/// Typed failures produced by Talon core operations.
44#[derive(Debug, Error)]
45#[non_exhaustive]
46pub enum TalonError {
47    /// A feature surface exists but is intentionally not implemented yet.
48    #[error("{feature} is not implemented yet")]
49    NotImplemented {
50        /// Feature name.
51        feature: &'static str,
52    },
53
54    /// User input failed boundary validation.
55    #[error("invalid input for {field}: {message}")]
56    InvalidInput {
57        /// Input field name.
58        field: &'static str,
59        /// Validation failure detail.
60        message: String,
61    },
62
63    /// Scope name not found in configuration.
64    #[error("scope '{name}' is not declared in config")]
65    InvalidScope {
66        /// Invalid scope name.
67        name: String,
68    },
69
70    /// `--where` filter expression is malformed.
71    #[error("invalid --where filter: {message}")]
72    InvalidWhere {
73        /// Filter expression detail.
74        message: String,
75    },
76
77    /// `--since` timestamp could not be parsed.
78    #[error("invalid --since timestamp: {message}")]
79    InvalidSince {
80        /// Timestamp detail.
81        message: String,
82    },
83
84    /// Database is locked by another sync operation.
85    #[error("database busy: another sync is in progress")]
86    DbBusy,
87
88    /// Database file is corrupt or unreadable.
89    #[error("database corrupt: {message}")]
90    DbCorrupt {
91        /// Corruption detail.
92        message: String,
93    },
94
95    /// No index exists for the configured vault path.
96    #[error("not indexed: vault '{path}' has no index")]
97    NotIndexed {
98        /// Vault path.
99        path: String,
100    },
101
102    /// Configuration is invalid or incomplete.
103    #[error("config error: {message}")]
104    Config {
105        /// Configuration detail.
106        message: String,
107    },
108
109    /// Internal/server error.
110    #[error("internal error: {message}")]
111    Internal {
112        /// Error detail.
113        message: String,
114    },
115
116    /// `SQLite` operation failed.
117    #[error("sqlite error in {context}: {source}")]
118    Sqlite {
119        /// Where the error occurred (e.g. "open database", "run migrations").
120        context: &'static str,
121        /// Underlying `rusqlite` error.
122        #[source]
123        source: rusqlite::Error,
124    },
125}
126
127impl TalonError {
128    /// Returns the error code for this error variant.
129    #[must_use]
130    pub const fn code(&self) -> ErrorCode {
131        match self {
132            Self::InvalidScope { .. } => ErrorCode::InvalidScope,
133            Self::InvalidWhere { .. } => ErrorCode::InvalidWhere,
134            Self::InvalidSince { .. } => ErrorCode::InvalidSince,
135            Self::DbBusy => ErrorCode::DbBusy,
136            Self::DbCorrupt { .. } => ErrorCode::DbCorrupt,
137            Self::NotIndexed { .. } => ErrorCode::NotIndexed,
138            Self::Internal { .. }
139            | Self::NotImplemented { .. }
140            | Self::InvalidInput { .. }
141            | Self::Config { .. }
142            | Self::Sqlite { .. } => ErrorCode::Internal,
143        }
144    }
145}