git_brws/
error.rs

1use std::ffi::OsString;
2use std::fmt;
3use std::io;
4use std::path::PathBuf;
5
6#[derive(Debug)]
7pub enum ExpectedNumberOfArgs {
8    Single(usize),
9    Range(usize, usize),
10}
11
12impl fmt::Display for ExpectedNumberOfArgs {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        match self {
15            ExpectedNumberOfArgs::Single(num) => write!(f, "{}", num),
16            ExpectedNumberOfArgs::Range(min, max) => write!(f, "{}..{}", min, max),
17        }
18    }
19}
20
21// TODO: Add backtrace when std::backtrace is stabilized
22#[derive(Debug)]
23pub struct Error {
24    kind: ErrorKind,
25}
26
27impl Error {
28    pub fn new(kind: ErrorKind) -> Box<Error> {
29        // TODO: Capture backtrace when std::backtrace is stabilized
30        Box::new(Error { kind })
31    }
32
33    pub fn err<T>(kind: ErrorKind) -> Result<T> {
34        Err(Error::new(kind))
35    }
36
37    pub fn kind(&self) -> &ErrorKind {
38        &self.kind
39    }
40
41    pub fn eprintln(&self) {
42        use std::error::Error;
43        fn eprint_cause(e: &dyn std::error::Error) {
44            eprint!(": {}", e);
45            if let Some(s) = e.source() {
46                eprint_cause(s);
47            }
48        }
49
50        eprint!("Error: {}", self);
51        if let Some(s) = self.source() {
52            eprint_cause(s);
53        }
54        eprintln!();
55    }
56}
57
58impl fmt::Display for Error {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        self.kind.fmt(f)
61    }
62}
63
64impl std::error::Error for Error {
65    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
66        use ErrorKind::*;
67        match self.kind() {
68            CliParseFail(s) => Some(s),
69            IoError(s) => Some(s),
70            HttpClientError(s) => Some(s),
71            EnvLoadError(s) => Some(s),
72            _ => None,
73        }
74    }
75}
76
77#[derive(Debug)]
78pub enum ErrorKind {
79    BrokenRepoFormat {
80        input: String,
81    },
82    CliParseFail(getopts::Fail),
83    OpenUrlFailure {
84        url: String,
85        msg: String,
86    },
87    GitLabDiffNotSupported,
88    BitbucketDiffNotSupported,
89    AzureDevOpsNotSupported,
90    NoUserInPath {
91        path: String,
92    },
93    NoRepoInPath {
94        path: String,
95    },
96    UnknownHostingService {
97        url: String,
98    },
99    BrokenUrl {
100        url: String,
101        msg: String,
102    },
103    PullReqNotSupported {
104        service: String,
105    },
106    GitHubStatusFailure {
107        status: reqwest::StatusCode,
108        msg: String,
109    },
110    HttpClientError(reqwest::Error),
111    IoError(io::Error),
112    GitCommandError {
113        stderr: String,
114        args: Vec<OsString>,
115    },
116    UnexpectedRemoteName(String),
117    GitObjectNotFound {
118        kind: &'static str,
119        object: String,
120        msg: String,
121    },
122    GitRootDirNotFound {
123        cwd: PathBuf,
124        stderr: String,
125    },
126    WrongNumberOfArgs {
127        expected: ExpectedNumberOfArgs,
128        actual: usize,
129        kind: String,
130    },
131    DiffDotsNotFound,
132    DiffHandIsEmpty {
133        input: String,
134    },
135    FileDirNotInRepo {
136        repo_root: PathBuf,
137        path: PathBuf,
138    },
139    PageParseError {
140        args: Vec<String>,
141        attempts: Vec<(&'static str, Error)>,
142    },
143    InvalidIssueNumberFormat,
144    LineSpecifiedForDir(PathBuf),
145    EnvLoadError(envy::Error),
146    NoLocalRepoFound {
147        operation: String,
148    },
149    NoSearchResult {
150        query: String,
151    },
152    ArgsNotAllowed {
153        flag: &'static str,
154        args: Vec<String>,
155    },
156    GheTokenRequired,
157    BlameWithoutFilePath,
158    CannotBlameDirectory {
159        dir: String,
160    },
161    UserBrowseCommandFailed {
162        cmd: String,
163        url: String,
164        msg: String,
165    },
166    SpecifiedDirNotExist {
167        dir: String,
168    },
169    BranchNameEmpty,
170    InvalidUser {
171        name: String,
172    },
173}
174
175impl fmt::Display for ErrorKind {
176    #[cfg(not(tarpaulin_include))]
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        use ErrorKind::*;
179        match self {
180            BrokenRepoFormat {input} => write!(f, "Invalid repository format '{}' or unknown remote. Note: Format must be one of 'repo', 'user/repo', 'host/user/repo', Git URL", input),
181            CliParseFail(_) => write!(f, "Can't parse command line arguments"),
182            OpenUrlFailure {url, msg} => write!(f, "{}: Cannot open URL {}", msg, url),
183            GitLabDiffNotSupported => write!(f, "GitLab does not support '..' for comparing diff between commits. Please use '...'"),
184            BitbucketDiffNotSupported => write!(f, "BitBucket does not support diff between commits (see https://bitbucket.org/site/master/issues/4779/ability-to-diff-between-any-two-commits)"),
185            AzureDevOpsNotSupported => write!(f, "Azure Devops does not currently support this operation"),
186            NoUserInPath{path} => write!(f, "Can't detect user name from path {}", path),
187            NoRepoInPath{path} => write!(f, "Can't detect repository name from path {}", path),
188            UnknownHostingService {url} => write!(f, "Unknown hosting service for URL {}. If you want to use custom URL for GitHub Enterprise, please set $GIT_BRWS_GHE_URL_HOST", url),
189            BrokenUrl {url, msg} => write!(f, "Broken URL '{}': {}", url, msg),
190            PullReqNotSupported {service} => write!(f, "--pr or -p does not support the service {}", service),
191            GitHubStatusFailure {status, msg} => write!(f, "GitHub API failure with response status {}: {}", status, msg),
192            HttpClientError(_) => write!(f, "Network request failure"),
193            IoError(_) => write!(f, "I/O error happened. Git command or current directory or file path may not exist"),
194            GitCommandError{stderr, args} => {
195                if stderr.is_empty() {
196                    write!(f, "`git")?;
197                } else {
198                    write!(f, "{}: `git", stderr)?;
199                }
200                for arg in args.iter() {
201                    write!(f, " '{}'", arg.to_string_lossy())?;
202                }
203                write!(f, "` exited with non-zero status")
204            }
205            GitObjectNotFound{kind, object, msg} if msg.is_empty() => write!(f, "Git could not find {} '{}'", kind, object),
206            GitObjectNotFound{kind, object, msg} => write!(f, "Git could not find {} '{}': {}", kind, object, msg),
207            GitRootDirNotFound{cwd, stderr} => write!(f, "Cannot locate root directory at {:?}: {}", cwd, stderr),
208            UnexpectedRemoteName(name) => write!(f, "Tracking name must be remote-url/branch-name: {}", name),
209            WrongNumberOfArgs{expected, actual, kind} => write!(f, "Invalid number of arguments for {}. {} is expected but {} given", kind, expected, actual),
210            DiffDotsNotFound => write!(f, "'..' or '...' must be contained for diff"),
211            DiffHandIsEmpty{input} => write!(f, "Not a diff format since LHS and/or RHS is empty {}", input),
212            FileDirNotInRepo{repo_root, path} => write!(f, "Given path '{:?}' is not in repository '{:?}'", path, repo_root),
213            PageParseError{args, attempts} => {
214                write!(f, "Cannot parse command line arguments {:?}\nAttempts:", args)?;
215                for (what, err) in attempts.iter() {
216                    write!(f, "\n  - {}: {}", what, err.kind())?;
217                }
218                Ok(())
219            }
220            InvalidIssueNumberFormat => write!(f, "Issue number must start with '#' followed by numbers like #123"),
221            LineSpecifiedForDir(path) => write!(f, "Directory cannot have line number: {:?}", path),
222            EnvLoadError(_) => write!(f, "Cannot load environment variable"),
223            NoLocalRepoFound{operation} => write!(f, ".git directory was not found. For {}, local repository must be known", operation),
224            NoSearchResult{query} => write!(f, "No repository was hit for query '{}'", query),
225            ArgsNotAllowed{flag, args} => write!(f, "{} option does not allow any command line argument. It opens page based on {{repo}}, but argument(s) {:?} retrives information from local directory.", flag, args),
226            GheTokenRequired => write!(f, "GitHub Enterprise requires API token. Please set $GIT_BRWS_GHE_TOKEN"),
227            BlameWithoutFilePath => write!(f, "File path is not given to blame"),
228            CannotBlameDirectory{dir} => write!(f, "Cannot blame directory '{}'. Please specify file path", dir),
229            UserBrowseCommandFailed{cmd, url, msg} => write!(f, "Command '{}' failed to open URL {}. Please check $GIT_BRWS_BROWSE_COMMAND. stderr: {}", cmd, url, msg),
230            SpecifiedDirNotExist{dir} => write!(f, "Specified directory '{}' with -d option does not exist", dir),
231            BranchNameEmpty => write!(f, "Branch name cannot be empty"),
232            InvalidUser{name} => write!(f, "Invalid user or organization name '{}'", name),
233        }
234    }
235}
236
237macro_rules! error_from {
238    ($cause:ty, $kind:ident) => {
239        impl From<$cause> for Box<Error> {
240            fn from(err: $cause) -> Box<Error> {
241                Error::new(ErrorKind::$kind(err))
242            }
243        }
244    };
245}
246
247error_from!(io::Error, IoError);
248error_from!(reqwest::Error, HttpClientError);
249error_from!(getopts::Fail, CliParseFail);
250error_from!(envy::Error, EnvLoadError);
251
252// Note: Use Box<Error> instead of Error to reduce size of Result<T>
253pub type Result<T> = ::std::result::Result<T, Box<Error>>;