1use crate::caliband::wire::SupervisorError;
7
8pub type Result<T> = std::result::Result<T, CoreError>;
10
11#[derive(thiserror::Error, Debug)]
13pub enum CoreError {
14 #[error("caliband unreachable at {endpoint}: {source}")]
16 CalibandUnreachable {
17 endpoint: String,
19 source: std::io::Error,
21 },
22
23 #[error("caliband protocol error: {0}")]
25 Protocol(String),
26
27 #[error("agent not found: {0}")]
29 AgentNotFound(String),
30
31 #[error("invalid state for {op}: agent {id} is {status}")]
33 InvalidState {
34 op: String,
36 id: String,
38 status: String,
40 },
41
42 #[error("discovery error: {0}")]
44 Discovery(String),
45
46 #[error("store error: {0}")]
48 Store(String),
49
50 #[error("event seq conflict")]
54 SeqConflict,
55
56 #[error("workspace not registered: {0}")]
58 WorkspaceNotFound(String),
59
60 #[error("automation not found: {0}")]
62 AutomationNotFound(String),
63
64 #[error("unauthorized: {0}")]
69 Unauthorized(String),
70
71 #[error("{0}")]
76 Conflict(String),
77
78 #[error("provider misconfigured: {0}")]
81 ProviderMisconfigured(String),
82
83 #[error("invalid config: {0}")]
88 InvalidConfig(String),
89
90 #[error("fleet backend error: {0}")]
94 Fleet(String),
95
96 #[error("io error: {0}")]
98 Io(#[from] std::io::Error),
99
100 #[error("json error: {0}")]
102 Json(#[from] serde_json::Error),
103}
104
105impl From<SupervisorError> for CoreError {
106 fn from(e: SupervisorError) -> Self {
107 match e {
108 SupervisorError::NotFound { id } => CoreError::AgentNotFound(id),
109 SupervisorError::InvalidState { op, id, status } => CoreError::InvalidState {
110 op,
111 id,
112 status: format!("{status:?}"),
113 },
114 SupervisorError::Internal { message } => CoreError::Protocol(message),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn display_messages() {
125 let e = CoreError::CalibandUnreachable {
126 endpoint: "/tmp/x.sock".into(),
127 source: std::io::Error::new(std::io::ErrorKind::NotFound, "nope"),
128 };
129 assert!(
130 e.to_string()
131 .starts_with("caliband unreachable at /tmp/x.sock:")
132 );
133 assert_eq!(
134 CoreError::Protocol("bad".into()).to_string(),
135 "caliband protocol error: bad"
136 );
137 assert_eq!(
138 CoreError::AgentNotFound("a1".into()).to_string(),
139 "agent not found: a1"
140 );
141 assert_eq!(
142 CoreError::InvalidState {
143 op: "kill".into(),
144 id: "a1".into(),
145 status: "Done".into(),
146 }
147 .to_string(),
148 "invalid state for kill: agent a1 is Done"
149 );
150 assert_eq!(
151 CoreError::Discovery("d".into()).to_string(),
152 "discovery error: d"
153 );
154 assert_eq!(CoreError::Store("s".into()).to_string(), "store error: s");
155 assert_eq!(
156 CoreError::WorkspaceNotFound("r".into()).to_string(),
157 "workspace not registered: r"
158 );
159 assert_eq!(
160 CoreError::Fleet("timed out".into()).to_string(),
161 "fleet backend error: timed out"
162 );
163 }
164
165 #[test]
166 fn from_io_and_json() {
167 let io: CoreError = std::io::Error::other("boom").into();
168 assert!(matches!(io, CoreError::Io(_)));
169 assert!(io.to_string().starts_with("io error:"));
170 let json: CoreError = serde_json::from_str::<i32>("not json").unwrap_err().into();
171 assert!(matches!(json, CoreError::Json(_)));
172 assert!(json.to_string().starts_with("json error:"));
173 }
174
175 #[test]
176 fn from_supervisor_error_maps_all_arms() {
177 let nf: CoreError = SupervisorError::NotFound { id: "a1".into() }.into();
178 assert!(matches!(nf, CoreError::AgentNotFound(id) if id == "a1"));
179
180 let inv: CoreError = SupervisorError::InvalidState {
181 op: "respawn".into(),
182 id: "a2".into(),
183 status: crate::caliband::wire::WireAgentStatus::Done,
185 }
186 .into();
187 match inv {
188 CoreError::InvalidState { op, id, status } => {
189 assert_eq!(
190 (op.as_str(), id.as_str(), status.as_str()),
191 ("respawn", "a2", "Done")
192 );
193 }
194 other => panic!("expected InvalidState, got {other:?}"),
195 }
196
197 let internal: CoreError = SupervisorError::Internal {
198 message: "kaboom".into(),
199 }
200 .into();
201 assert!(matches!(internal, CoreError::Protocol(m) if m == "kaboom"));
202 }
203}