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#[derive(Debug)]
23pub struct Error {
24 kind: ErrorKind,
25}
26
27impl Error {
28 pub fn new(kind: ErrorKind) -> Box<Error> {
29 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
252pub type Result<T> = ::std::result::Result<T, Box<Error>>;