1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum AcpCliError {
5 #[error("agent error: {0}")]
6 Agent(String),
7
8 #[error("usage error: {0}")]
9 Usage(String),
10
11 #[error("timeout after {0}s")]
12 Timeout(u64),
13
14 #[error("no session found for {agent} in {cwd}")]
15 NoSession { agent: String, cwd: String },
16
17 #[error("permission denied: {0}")]
18 PermissionDenied(String),
19
20 #[error("interrupted")]
21 Interrupted,
22
23 #[error(transparent)]
24 Io(#[from] std::io::Error),
25
26 #[error("acp connection failed: {0}")]
27 Connection(String),
28}
29
30impl AcpCliError {
31 pub fn exit_code(&self) -> i32 {
32 match self {
33 Self::Agent(_) | Self::Io(_) | Self::Connection(_) => 1,
34 Self::Usage(_) => 2,
35 Self::Timeout(_) => 3,
36 Self::NoSession { .. } => 4,
37 Self::PermissionDenied(_) => 5,
38 Self::Interrupted => 130,
39 }
40 }
41}
42
43pub fn is_transient(err: &AcpCliError) -> bool {
52 matches!(err, AcpCliError::Connection(_))
53}
54
55pub type Result<T> = std::result::Result<T, AcpCliError>;
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn connection_error_is_transient() {
63 assert!(is_transient(&AcpCliError::Connection("refused".into())));
64 }
65
66 #[test]
67 fn agent_error_is_not_transient() {
68 assert!(!is_transient(&AcpCliError::Agent(
69 "permission denied".into()
70 )));
71 }
72
73 #[test]
74 fn timeout_is_not_transient() {
75 assert!(!is_transient(&AcpCliError::Timeout(30)));
77 }
78
79 #[test]
80 fn interrupted_is_not_transient() {
81 assert!(!is_transient(&AcpCliError::Interrupted));
82 }
83
84 #[test]
85 fn io_error_is_not_transient() {
86 assert!(!is_transient(&AcpCliError::Io(std::io::Error::new(
87 std::io::ErrorKind::BrokenPipe,
88 "broken pipe"
89 ))));
90 }
91}