use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("refused: {0}")]
Refused(String),
#[error("queued: {0}")]
Queued(String),
#[error("nothing is driving run '{run}'")]
NothingDriving {
run: String,
},
#[error("invalid: {0}")]
Invalid(String),
#[error("no such run '{run}' under {root}")]
NoSuchRun {
run: String,
root: PathBuf,
},
#[error("run '{run}' belongs to {owner}, not to this session")]
NotOwned {
run: String,
owner: String,
},
#[error("run '{run}' is being written by pid {pid} on {host} ({verb})")]
Locked {
run: String,
pid: u32,
host: String,
verb: String,
},
#[error("{tool}: {message}")]
Sibling {
tool: &'static str,
message: String,
},
#[error("{path}: {source}")]
Ledger {
path: PathBuf,
#[source]
source: std::io::Error,
},
}
impl Error {
pub fn exit_code(&self) -> i32 {
match self {
Self::Queued(_) => EXIT_QUEUED,
Self::NothingDriving { .. } => EXIT_NOTHING_DRIVING,
_ => EXIT_REFUSED,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub const EXIT_SUCCESS: i32 = 0;
pub const EXIT_QUEUED: i32 = 1;
pub const EXIT_REFUSED: i32 = 2;
pub const EXIT_NOTHING_DRIVING: i32 = 3;
pub const EXIT_SURFACE_WAITING: i32 = 4;
pub const EXIT_WATCH_ELAPSED: i32 = 5;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_failure_carries_the_code_the_contract_assigns() {
assert_eq!(Error::Queued("edits".into()).exit_code(), EXIT_QUEUED);
assert_eq!(Error::Refused("bad op".into()).exit_code(), EXIT_REFUSED);
assert_eq!(Error::Invalid("bad plan".into()).exit_code(), EXIT_REFUSED);
assert_eq!(
Error::NothingDriving { run: "r".into() }.exit_code(),
EXIT_NOTHING_DRIVING
);
assert_eq!(
Error::Sibling {
tool: "onevcs",
message: "refused".into()
}
.exit_code(),
EXIT_REFUSED
);
}
#[test]
fn a_refusal_says_which_run_and_who_owns_it() {
let error = Error::NotOwned {
run: "demo".into(),
owner: "[claude-code:3f9a1c2e]".into(),
};
let rendered = error.to_string();
assert!(rendered.contains("demo"), "{rendered}");
assert!(rendered.contains("claude-code"), "{rendered}");
}
#[test]
fn a_locked_run_names_the_writer_that_holds_it() {
let rendered = Error::Locked {
run: "demo".into(),
pid: 4321,
host: "builder".into(),
verb: "drive".into(),
}
.to_string();
assert!(rendered.contains("4321"), "{rendered}");
assert!(rendered.contains("builder"), "{rendered}");
assert!(rendered.contains("drive"), "{rendered}");
}
}