1use std::path::PathBuf;
6
7pub mod exit {
10 pub const OK: i32 = 0;
11 pub const GENERIC: i32 = 1;
12 pub const USAGE: i32 = 2;
13 pub const UNSUPPORTED: i32 = 3;
14 pub const UNREADABLE: i32 = 4;
15 pub const PLUGIN: i32 = 5;
16 pub const INTERRUPTED: i32 = 130;
17}
18
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21 #[error("cannot read '{path}': {source}")]
22 Io {
23 path: PathBuf,
24 #[source]
25 source: std::io::Error,
26 },
27
28 #[error("{0}")]
29 Stdio(#[from] std::io::Error),
30
31 #[error("unsupported format: {0}")]
32 Unsupported(String),
33
34 #[error("corrupt document: {0}")]
35 Corrupt(String),
36
37 #[error("invalid encoding: {0}")]
38 Encoding(String),
39
40 #[error("{0}")]
41 Usage(String),
42
43 #[error("plugin '{name}' failed: {message}")]
44 Plugin { name: String, message: String },
45}
46
47impl Error {
48 pub fn exit_code(&self) -> i32 {
51 match self {
52 Error::Usage(_) => exit::USAGE,
53 Error::Unsupported(_) => exit::UNSUPPORTED,
54 Error::Io { .. } | Error::Corrupt(_) | Error::Encoding(_) => exit::UNREADABLE,
55 Error::Plugin { .. } => exit::PLUGIN,
56 Error::Stdio(e) if e.kind() == std::io::ErrorKind::BrokenPipe => exit::OK,
57 Error::Stdio(_) => exit::GENERIC,
58 }
59 }
60
61 pub fn is_broken_pipe(&self) -> bool {
65 matches!(self, Error::Stdio(e) if e.kind() == std::io::ErrorKind::BrokenPipe)
66 }
67}
68
69pub type Result<T> = std::result::Result<T, Error>;
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn broken_pipe_is_not_a_failure() {
77 let e = Error::Stdio(std::io::Error::from(std::io::ErrorKind::BrokenPipe));
78 assert!(e.is_broken_pipe());
79 assert_eq!(e.exit_code(), exit::OK);
80 }
81
82 #[test]
83 fn exit_codes_match_the_design() {
84 assert_eq!(Error::Usage("x".into()).exit_code(), 2);
85 assert_eq!(Error::Unsupported("x".into()).exit_code(), 3);
86 assert_eq!(Error::Corrupt("x".into()).exit_code(), 4);
87 assert_eq!(
88 Error::Plugin {
89 name: "x".into(),
90 message: "y".into()
91 }
92 .exit_code(),
93 5
94 );
95 }
96}