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("{0}")]
65 Conflict(String),
66
67 #[error("provider misconfigured: {0}")]
70 ProviderMisconfigured(String),
71
72 #[error("invalid config: {0}")]
77 InvalidConfig(String),
78
79 #[error("fleet backend error: {0}")]
83 Fleet(String),
84
85 #[error("io error: {0}")]
87 Io(#[from] std::io::Error),
88
89 #[error("json error: {0}")]
91 Json(#[from] serde_json::Error),
92}
93
94impl From<SupervisorError> for CoreError {
95 fn from(e: SupervisorError) -> Self {
96 match e {
97 SupervisorError::NotFound { id } => CoreError::AgentNotFound(id),
98 SupervisorError::InvalidState { op, id, status } => CoreError::InvalidState {
99 op,
100 id,
101 status: format!("{status:?}"),
102 },
103 SupervisorError::Internal { message } => CoreError::Protocol(message),
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::model::AgentStatus;
112
113 #[test]
114 fn display_messages() {
115 let e = CoreError::CalibandUnreachable {
116 endpoint: "/tmp/x.sock".into(),
117 source: std::io::Error::new(std::io::ErrorKind::NotFound, "nope"),
118 };
119 assert!(
120 e.to_string()
121 .starts_with("caliband unreachable at /tmp/x.sock:")
122 );
123 assert_eq!(
124 CoreError::Protocol("bad".into()).to_string(),
125 "caliband protocol error: bad"
126 );
127 assert_eq!(
128 CoreError::AgentNotFound("a1".into()).to_string(),
129 "agent not found: a1"
130 );
131 assert_eq!(
132 CoreError::InvalidState {
133 op: "kill".into(),
134 id: "a1".into(),
135 status: "Done".into(),
136 }
137 .to_string(),
138 "invalid state for kill: agent a1 is Done"
139 );
140 assert_eq!(
141 CoreError::Discovery("d".into()).to_string(),
142 "discovery error: d"
143 );
144 assert_eq!(CoreError::Store("s".into()).to_string(), "store error: s");
145 assert_eq!(
146 CoreError::WorkspaceNotFound("r".into()).to_string(),
147 "workspace not registered: r"
148 );
149 assert_eq!(
150 CoreError::Fleet("timed out".into()).to_string(),
151 "fleet backend error: timed out"
152 );
153 }
154
155 #[test]
156 fn from_io_and_json() {
157 let io: CoreError = std::io::Error::other("boom").into();
158 assert!(matches!(io, CoreError::Io(_)));
159 assert!(io.to_string().starts_with("io error:"));
160 let json: CoreError = serde_json::from_str::<i32>("not json").unwrap_err().into();
161 assert!(matches!(json, CoreError::Json(_)));
162 assert!(json.to_string().starts_with("json error:"));
163 }
164
165 #[test]
166 fn from_supervisor_error_maps_all_arms() {
167 let nf: CoreError = SupervisorError::NotFound { id: "a1".into() }.into();
168 assert!(matches!(nf, CoreError::AgentNotFound(id) if id == "a1"));
169
170 let inv: CoreError = SupervisorError::InvalidState {
171 op: "respawn".into(),
172 id: "a2".into(),
173 status: AgentStatus::Done,
174 }
175 .into();
176 match inv {
177 CoreError::InvalidState { op, id, status } => {
178 assert_eq!(
179 (op.as_str(), id.as_str(), status.as_str()),
180 ("respawn", "a2", "Done")
181 );
182 }
183 other => panic!("expected InvalidState, got {other:?}"),
184 }
185
186 let internal: CoreError = SupervisorError::Internal {
187 message: "kaboom".into(),
188 }
189 .into();
190 assert!(matches!(internal, CoreError::Protocol(m) if m == "kaboom"));
191 }
192}