alopex_cli/
error.rs

1//! Error types for Alopex CLI
2//!
3//! This module defines CLI-specific error types using thiserror.
4
5use thiserror::Error;
6
7/// CLI-specific error type.
8///
9/// This enum represents all possible errors that can occur during CLI operations.
10#[derive(Error, Debug)]
11pub enum CliError {
12    /// An error from the underlying database layer.
13    #[error("Database error: {0}")]
14    Database(#[from] alopex_embedded::Error),
15
16    /// An I/O error occurred.
17    #[error("IO error: {0}")]
18    Io(#[from] std::io::Error),
19
20    /// An invalid argument was provided.
21    #[error("Invalid argument: {0}")]
22    InvalidArgument(String),
23
24    /// A specified profile was not found.
25    #[error("プロファイル '{0}' が見つかりません。alopex profile list で一覧を確認してください。")]
26    #[allow(dead_code)]
27    ProfileNotFound(String),
28
29    /// Conflicting CLI options were provided.
30    #[error(
31        "`--profile` と `--data-dir` は同時に指定できません。どちらか一方を使用してください。"
32    )]
33    #[allow(dead_code)]
34    ConflictingOptions,
35
36    /// A transaction ID is invalid.
37    #[error(
38        "トランザクション ID '{0}' は無効です。alopex kv txn begin で新しいトランザクションを開始してください。"
39    )]
40    #[allow(dead_code)]
41    InvalidTransactionId(String),
42
43    /// A transaction timed out and was rolled back.
44    #[error(
45        "トランザクション '{0}' がタイムアウトしました(60秒)。自動的にロールバックされました。"
46    )]
47    #[allow(dead_code)]
48    TransactionTimeout(String),
49
50    /// A query exceeded its deadline.
51    #[error("Query timeout: {0}. Suggestion: Use --deadline 120s to increase timeout.")]
52    #[allow(dead_code)]
53    Timeout(String),
54
55    /// A query was cancelled by the user.
56    #[error("Query cancelled by user.")]
57    #[allow(dead_code)]
58    Cancelled,
59
60    /// No SQL query was provided.
61    #[error("SQL クエリを指定してください。引数、-f オプション、または標準入力から入力できます。")]
62    #[allow(dead_code)]
63    NoQueryProvided,
64
65    /// An unknown index type was specified.
66    #[error("未知のインデックスタイプ: '{0}'。許可値: minmax, bloom")]
67    #[allow(dead_code)]
68    UnknownIndexType(String),
69
70    /// An unknown compression type was specified.
71    #[error("未知の圧縮形式: '{0}'。許可値: lz4, zstd, none")]
72    #[allow(dead_code)]
73    UnknownCompressionType(String),
74
75    /// File format is incompatible with the CLI version.
76    #[error("ファイルフォーマット v{file} は CLI v{cli} でサポートされていません。CLI をアップグレードしてください。")]
77    #[allow(dead_code)]
78    IncompatibleVersion { cli: String, file: String },
79
80    /// An S3-related error occurred.
81    #[error("S3 error: {0}")]
82    #[allow(dead_code)]
83    S3(String),
84
85    /// Credentials are missing or invalid.
86    #[error("Credentials error: {0}")]
87    Credentials(String),
88
89    /// A parsing error occurred.
90    #[error("Parse error: {0}")]
91    #[allow(dead_code)]
92    Parse(String),
93
94    /// A JSON serialization/deserialization error occurred.
95    #[error("JSON error: {0}")]
96    Json(#[from] serde_json::Error),
97
98    /// A server connection error occurred.
99    #[error("Server connection error: {0}")]
100    #[allow(dead_code)]
101    ServerConnection(String),
102
103    /// A server does not support the requested command.
104    #[error("Server does not support this command: {0}")]
105    #[allow(dead_code)]
106    ServerUnsupported(String),
107}
108
109/// Type alias for CLI results.
110pub type Result<T> = std::result::Result<T, CliError>;
111
112/// Handle an error by printing it and exiting.
113///
114/// If `verbose` is true, prints the debug format (with stack trace info).
115/// Otherwise, prints the display format (user-friendly message).
116pub fn handle_error(error: CliError, verbose: bool) {
117    if verbose {
118        eprintln!("Error: {:?}", error); // Debug format with stack trace
119    } else {
120        eprintln!("Error: {}", error); // Display format
121    }
122    std::process::exit(1);
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_invalid_argument_error() {
131        let err = CliError::InvalidArgument("test message".to_string());
132        assert_eq!(format!("{}", err), "Invalid argument: test message");
133    }
134
135    #[test]
136    fn test_s3_error() {
137        let err = CliError::S3("bucket not found".to_string());
138        assert_eq!(format!("{}", err), "S3 error: bucket not found");
139    }
140
141    #[test]
142    fn test_credentials_error() {
143        let err = CliError::Credentials("AWS_ACCESS_KEY_ID not set".to_string());
144        assert_eq!(
145            format!("{}", err),
146            "Credentials error: AWS_ACCESS_KEY_ID not set"
147        );
148    }
149
150    #[test]
151    fn test_parse_error() {
152        let err = CliError::Parse("invalid JSON".to_string());
153        assert_eq!(format!("{}", err), "Parse error: invalid JSON");
154    }
155
156    #[test]
157    fn test_io_error_from() {
158        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
159        let err: CliError = io_err.into();
160        assert!(matches!(err, CliError::Io(_)));
161    }
162}