1use std::fmt;
2use std::path::PathBuf;
3
4#[derive(Debug)]
5pub enum Error {
6 OpenFile { path: PathBuf, source: std::io::Error },
7 NotAFile { path: PathBuf },
8 NoInput,
9 Runtime(String),
10 TagFileNotFound,
12 TagFileParse(String, std::path::PathBuf, usize),
14}
15
16impl Error {
17 pub fn exit_code(&self) -> i32 {
18 match self {
19 Error::OpenFile { .. }
20 | Error::NotAFile { .. }
21 | Error::NoInput
22 | Error::TagFileNotFound
23 | Error::TagFileParse(..) => 1,
24 Error::Runtime(_) => 2,
25 }
26 }
27}
28
29impl fmt::Display for Error {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Error::OpenFile { path, source } => {
33 write!(f, "tess: {}: {}", path.display(), source)
34 }
35 Error::NotAFile { path } => {
36 write!(f, "tess: {}: not a regular file", path.display())
37 }
38 Error::NoInput => write!(f, "tess: no input (specify a file or pipe stdin)"),
39 Error::Runtime(msg) => write!(f, "tess: {}", msg),
40 Error::TagFileNotFound => {
41 write!(f, "tags file not found")
42 }
43 Error::TagFileParse(reason, path, line) => {
44 write!(f, "tags file parse error: {reason} at {}:{line}", path.display())
45 }
46 }
47 }
48}
49
50impl std::error::Error for Error {}
51
52pub type Result<T> = std::result::Result<T, Error>;
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn display_wraps_io_error_with_context() {
60 let io = std::io::Error::new(std::io::ErrorKind::NotFound, "no such file");
61 let e = Error::OpenFile { path: "/nope".into(), source: io };
62 assert_eq!(format!("{}", e), "tess: /nope: no such file");
63 }
64
65 #[test]
66 fn exit_code_for_startup_is_one() {
67 let io = std::io::Error::new(std::io::ErrorKind::NotFound, "x");
68 let e = Error::OpenFile { path: "/nope".into(), source: io };
69 assert_eq!(e.exit_code(), 1);
70 }
71
72 #[test]
73 fn exit_code_for_runtime_is_two() {
74 let e = Error::Runtime("stdout closed".into());
75 assert_eq!(e.exit_code(), 2);
76 }
77}