agent_harness_adapter_core/
error.rs1use std::{fmt, io, path::PathBuf};
5
6use crate::harness::Scope;
7
8#[derive(Debug)]
13#[non_exhaustive]
14pub enum Error {
15 UnknownScope {
17 name: String,
19 },
20 UnknownEvent {
22 name: String,
24 },
25 UnknownHarness {
27 harness: String,
29 },
30 UnsupportedScope {
32 harness: String,
34 scope: Scope,
36 },
37 UnknownPart {
39 harness: String,
41 part: String,
43 },
44 Unsupported {
47 harness: String,
49 what: String,
51 },
52 File {
54 file: String,
56 message: String,
58 },
59 Refused(String),
64 Io {
66 path: PathBuf,
68 source: io::Error,
70 },
71 Internal(String),
73}
74
75impl Error {
76 pub fn io(path: impl Into<PathBuf>, source: io::Error) -> Self {
78 Error::Io {
79 path: path.into(),
80 source,
81 }
82 }
83
84 pub fn file(file: impl Into<String>, message: impl Into<String>) -> Self {
86 Error::File {
87 file: file.into(),
88 message: message.into(),
89 }
90 }
91}
92
93impl fmt::Display for Error {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Error::UnknownScope { name } => write!(f, "no scope named `{name}`"),
97 Error::UnknownEvent { name } => write!(f, "no hook event named `{name}`"),
98 Error::UnknownHarness { harness } => write!(f, "no harness named `{harness}`"),
99 Error::UnsupportedScope { harness, scope } => {
100 write!(f, "harness `{harness}` has no {scope} scope")
101 }
102 Error::UnknownPart { harness, part } => {
103 write!(f, "harness `{harness}` has no part named `{part}`")
104 }
105 Error::Unsupported { harness, what } => {
106 write!(f, "harness `{harness}` cannot {what}")
107 }
108 Error::File { file, message } => write!(f, "{file}: {message}"),
109 Error::Refused(message) => f.write_str(message),
110 Error::Io { path, source } => write!(f, "{}: {source}", path.display()),
111 Error::Internal(message) => write!(f, "internal: {message}"),
112 }
113 }
114}
115
116impl std::error::Error for Error {
117 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118 match self {
119 Error::Io { source, .. } => Some(source),
120 _ => None,
121 }
122 }
123}
124
125pub type Result<T> = std::result::Result<T, Error>;
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
134 fn each_kind_displays_one_line() {
135 let e = Error::io(
136 "/tmp/x",
137 io::Error::new(io::ErrorKind::PermissionDenied, "denied"),
138 );
139 let msg = e.to_string();
140 assert!(msg.contains("/tmp/x") && msg.contains("denied"), "{msg}");
141 assert!(std::error::Error::source(&e).is_some());
142 let cases = [
143 (
144 Error::UnknownScope { name: "g".into() },
145 "no scope named `g`",
146 ),
147 (
148 Error::UnknownEvent { name: "e".into() },
149 "no hook event named `e`",
150 ),
151 (
152 Error::UnknownHarness {
153 harness: "x".into(),
154 },
155 "no harness named `x`",
156 ),
157 (
158 Error::UnsupportedScope {
159 harness: "cursor".into(),
160 scope: Scope::Local,
161 },
162 "harness `cursor` has no local scope",
163 ),
164 (
165 Error::UnknownPart {
166 harness: "claude".into(),
167 part: "p".into(),
168 },
169 "harness `claude` has no part named `p`",
170 ),
171 (
172 Error::Unsupported {
173 harness: "copilot".into(),
174 what: "answer context at stop".into(),
175 },
176 "harness `copilot` cannot answer context at stop",
177 ),
178 (
179 Error::file("a.json", "is not a JSON object"),
180 "a.json: is not a JSON object",
181 ),
182 (Error::Refused("could not run x".into()), "could not run x"),
183 (Error::Internal("bug".into()), "internal: bug"),
184 ];
185 for (e, text) in cases {
186 assert_eq!(e.to_string(), text);
187 assert!(std::error::Error::source(&e).is_none());
188 }
189 }
190}