1use crate::{
6 constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME},
7 rpc::ResponseError,
8};
9use std::fmt::Display;
10use thiserror::Error;
11
12#[derive(Debug, Clone)]
14pub struct WrappedError {
15 message: String,
16 original: String,
17}
18
19impl std::fmt::Display for WrappedError {
20 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
21 write!(f, "{}: {}", self.message, self.original)
22 }
23}
24
25impl std::error::Error for WrappedError {
26 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
27 None
28 }
29}
30
31impl WrappedError {
32 }
36
37impl From<reqwest::Error> for WrappedError {
38 fn from(e: reqwest::Error) -> WrappedError {
39 WrappedError {
40 message: format!(
41 "error requesting {}",
42 e.url().map_or("<unknown>", |u| u.as_str())
43 ),
44 original: format!("{}", e),
45 }
46 }
47}
48
49pub fn wrapdbg<T, S>(original: T, message: S) -> WrappedError
50where
51 T: std::fmt::Debug,
52 S: Into<String>,
53{
54 WrappedError {
55 message: message.into(),
56 original: format!("{:?}", original),
57 }
58}
59
60pub fn wrap<T, S>(original: T, message: S) -> WrappedError
61where
62 T: Display,
63 S: Into<String>,
64{
65 WrappedError {
66 message: message.into(),
67 original: format!("{}", original),
68 }
69}
70
71#[derive(Debug)]
73pub struct StatusError {
74 pub url: String,
75 pub status_code: u16,
76 pub body: String,
77}
78
79impl std::fmt::Display for StatusError {
80 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
81 write!(
82 f,
83 "error requesting {}: {} {}",
84 self.url, self.status_code, self.body
85 )
86 }
87}
88
89impl StatusError {
90 pub async fn from_res(res: reqwest::Response) -> Result<StatusError, AnyError> {
91 let status_code = res.status().as_u16();
92 let url = res.url().to_string();
93 let body = res.text().await.map_err(|e| {
94 wrap(
95 e,
96 format!(
97 "failed to read response body on {} code from {}",
98 status_code, url
99 ),
100 )
101 })?;
102
103 Ok(StatusError {
104 url,
105 status_code,
106 body,
107 })
108 }
109}
110
111#[derive(Debug)]
114pub struct MismatchConnectionToken(pub String);
115
116impl std::fmt::Display for MismatchConnectionToken {
117 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
118 write!(f, "{}", self.0)
119 }
120}
121
122#[derive(Debug)]
124pub struct InvalidServerExtensionError(pub String);
125
126impl std::fmt::Display for InvalidServerExtensionError {
127 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
128 write!(f, "invalid server extension '{}'", self.0)
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct DevTunnelError(pub String);
135
136impl std::fmt::Display for DevTunnelError {
137 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
138 write!(f, "could not open tunnel: {}", self.0)
139 }
140}
141
142impl std::error::Error for DevTunnelError {
143 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
144 None
145 }
146}
147
148#[derive(Debug)]
150pub struct MissingEntrypointError();
151
152impl std::fmt::Display for MissingEntrypointError {
153 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
154 write!(f, "Missing entrypoints in server download. Most likely this is a corrupted download. Please retry")
155 }
156}
157
158#[derive(Debug)]
159pub struct SetupError(pub String);
160
161impl std::fmt::Display for SetupError {
162 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163 write!(
164 f,
165 "{}\n\nMore info at {}/remote/linux",
166 DOCUMENTATION_URL.unwrap_or("<docs>"),
167 self.0
168 )
169 }
170}
171
172#[derive(Debug)]
173pub struct NoHomeForLauncherError();
174
175impl std::fmt::Display for NoHomeForLauncherError {
176 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
177 write!(
178 f,
179 "No $HOME variable was found in your environment. Either set it, or specify a `--data-dir` manually when invoking the launcher.",
180 )
181 }
182}
183
184#[derive(Debug)]
185pub struct InvalidTunnelName(pub String);
186
187impl std::fmt::Display for InvalidTunnelName {
188 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
189 write!(f, "{}", &self.0)
190 }
191}
192
193#[derive(Debug)]
194pub struct TunnelCreationFailed(pub String, pub String);
195
196impl std::fmt::Display for TunnelCreationFailed {
197 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
198 write!(
199 f,
200 "Could not create tunnel with name: {}\nReason: {}",
201 &self.0, &self.1
202 )
203 }
204}
205
206#[derive(Debug)]
207pub struct TunnelHostFailed(pub String);
208
209impl std::fmt::Display for TunnelHostFailed {
210 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
211 write!(f, "{}", &self.0)
212 }
213}
214
215#[derive(Debug)]
216pub struct ExtensionInstallFailed(pub String);
217
218impl std::fmt::Display for ExtensionInstallFailed {
219 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
220 write!(f, "Extension install failed: {}", &self.0)
221 }
222}
223
224#[derive(Debug)]
225pub struct MismatchedLaunchModeError();
226
227impl std::fmt::Display for MismatchedLaunchModeError {
228 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
229 write!(f, "A server is already running, but it was not launched in the same listening mode (port vs. socket) as this request")
230 }
231}
232
233#[derive(Debug)]
234pub struct NoAttachedServerError();
235
236impl std::fmt::Display for NoAttachedServerError {
237 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238 write!(f, "No server is running")
239 }
240}
241
242#[derive(Debug)]
243pub struct RefreshTokenNotAvailableError();
244
245impl std::fmt::Display for RefreshTokenNotAvailableError {
246 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
247 write!(f, "Refresh token not available, authentication is required")
248 }
249}
250
251#[derive(Debug)]
252pub struct NoInstallInUserProvidedPath(pub String);
253
254impl std::fmt::Display for NoInstallInUserProvidedPath {
255 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
256 write!(
257 f,
258 "No {} installation could be found in {}. You can run `{} --use-quality=stable` to switch to the latest stable version of {}.",
259 QUALITYLESS_PRODUCT_NAME,
260 self.0,
261 APPLICATION_NAME,
262 QUALITYLESS_PRODUCT_NAME
263 )
264 }
265}
266
267#[derive(Debug)]
268pub struct InvalidRequestedVersion();
269
270impl std::fmt::Display for InvalidRequestedVersion {
271 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
272 write!(
273 f,
274 "The reqested version is invalid, expected one of 'stable', 'insiders', version number (x.y.z), or absolute path.",
275 )
276 }
277}
278
279#[derive(Debug)]
280pub struct UserCancelledInstallation();
281
282impl std::fmt::Display for UserCancelledInstallation {
283 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
284 write!(f, "Installation aborted.")
285 }
286}
287
288#[derive(Debug)]
289pub struct CannotForwardControlPort();
290
291impl std::fmt::Display for CannotForwardControlPort {
292 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
293 write!(f, "Cannot forward or unforward port {}.", CONTROL_PORT)
294 }
295}
296
297#[derive(Debug)]
298pub struct ServerHasClosed();
299
300impl std::fmt::Display for ServerHasClosed {
301 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
302 write!(f, "Request cancelled because the server has closed")
303 }
304}
305
306#[derive(Debug)]
307pub struct ServiceAlreadyRegistered();
308
309impl std::fmt::Display for ServiceAlreadyRegistered {
310 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
311 write!(f, "Already registered the service. Run `{} tunnel service uninstall` to unregister it first", APPLICATION_NAME)
312 }
313}
314
315#[derive(Debug)]
316pub struct WindowsNeedsElevation(pub String);
317
318impl std::fmt::Display for WindowsNeedsElevation {
319 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
320 writeln!(f, "{}", self.0)?;
321 writeln!(f)?;
322 writeln!(f, "You may need to run this command as an administrator:")?;
323 writeln!(f, " 1. Open the start menu and search for Powershell")?;
324 writeln!(f, " 2. Right click and 'Run as administrator'")?;
325 if let Ok(exe) = std::env::current_exe() {
326 writeln!(
327 f,
328 " 3. Run &'{}' '{}'",
329 exe.display(),
330 std::env::args().skip(1).collect::<Vec<_>>().join("' '")
331 )
332 } else {
333 writeln!(f, " 3. Run the same command again",)
334 }
335 }
336}
337
338#[derive(Debug)]
339pub struct InvalidRpcDataError(pub String);
340
341impl std::fmt::Display for InvalidRpcDataError {
342 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
343 write!(f, "parse error: {}", self.0)
344 }
345}
346
347#[derive(Debug)]
348pub struct CorruptDownload(pub String);
349
350impl std::fmt::Display for CorruptDownload {
351 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
352 write!(
353 f,
354 "Error updating the {} CLI: {}",
355 QUALITYLESS_PRODUCT_NAME, self.0
356 )
357 }
358}
359
360#[derive(Debug)]
361pub struct MissingHomeDirectory();
362
363impl std::fmt::Display for MissingHomeDirectory {
364 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
365 write!(f, "Could not find your home directory. Please ensure this command is running in the context of an normal user.")
366 }
367}
368
369#[derive(Debug)]
370pub struct OAuthError {
371 pub error: String,
372 pub error_description: Option<String>,
373}
374
375impl std::fmt::Display for OAuthError {
376 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
377 write!(
378 f,
379 "Error getting authorization: {} {}",
380 self.error,
381 self.error_description.as_deref().unwrap_or("")
382 )
383 }
384}
385
386macro_rules! makeAnyError {
391 ($($e:ident),*) => {
392
393 #[derive(Debug)]
394 #[allow(clippy::enum_variant_names)]
395 pub enum AnyError {
396 $($e($e),)*
397 }
398
399 impl std::fmt::Display for AnyError {
400 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
401 match *self {
402 $(AnyError::$e(ref e) => e.fmt(f),)*
403 }
404 }
405 }
406
407 impl std::error::Error for AnyError {
408 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
409 None
410 }
411 }
412
413 $(impl From<$e> for AnyError {
414 fn from(e: $e) -> AnyError {
415 AnyError::$e(e)
416 }
417 })*
418 };
419}
420
421#[derive(Debug)]
422pub struct DbusConnectFailedError(pub String);
423
424impl Display for DbusConnectFailedError {
425 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
426 let mut str = String::new();
427 str.push_str("Error creating dbus session. This command uses systemd for managing services, you should check that systemd is installed and under your user.");
428
429 if std::env::var("WSL_DISTRO_NAME").is_ok() {
430 str.push_str("\n\nTo enable systemd on WSL, check out: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/.\n\n");
431 }
432
433 str.push_str("If running `systemctl status` works, systemd is ok, but your session dbus may not be. You might need to:\n\n- Install the `dbus-user-session` package, and reboot if it was not installed\n- Start the user dbus session with `systemctl --user enable dbus --now`.\n\nThe error encountered was: ");
434 str.push_str(&self.0);
435 str.push('\n');
436
437 write!(f, "{}", str)
438 }
439}
440
441#[derive(Error, Debug)]
444pub enum CodeError {
445 #[error("could not connect to socket/pipe: {0:?}")]
446 AsyncPipeFailed(std::io::Error),
447 #[error("could not listen on socket/pipe: {0:?}")]
448 AsyncPipeListenerFailed(std::io::Error),
449 #[error("could not create singleton lock file: {0:?}")]
450 SingletonLockfileOpenFailed(std::io::Error),
451 #[error("could not read singleton lock file: {0:?}")]
452 SingletonLockfileReadFailed(rmp_serde::decode::Error),
453 #[error("the process holding the singleton lock file (pid={0}) exited")]
454 SingletonLockedProcessExited(u32),
455 #[error("no tunnel process is currently running")]
456 NoRunningTunnel,
457 #[error("rpc call failed: {0:?}")]
458 TunnelRpcCallFailed(ResponseError),
459 #[cfg(windows)]
460 #[error("the windows app lock {0} already exists")]
461 AppAlreadyLocked(String),
462 #[cfg(windows)]
463 #[error("could not get windows app lock: {0:?}")]
464 AppLockFailed(std::io::Error),
465 #[error("failed to run command \"{command}\" (code {code}): {output}")]
466 CommandFailed {
467 command: String,
468 code: i32,
469 output: String,
470 },
471
472 #[error("platform not currently supported: {0}")]
473 UnsupportedPlatform(String),
474 #[error("This machine does not meet {name}'s prerequisites, expected either...: {bullets}")]
475 PrerequisitesFailed { name: &'static str, bullets: String },
476 #[error("failed to spawn process: {0:?}")]
477 ProcessSpawnFailed(std::io::Error),
478 #[error("failed to handshake spawned process: {0:?}")]
479 ProcessSpawnHandshakeFailed(std::io::Error),
480 #[error("download appears corrupted, please retry ({0})")]
481 CorruptDownload(&'static str),
482 #[error("port forwarding is not available in this context")]
483 PortForwardingNotAvailable,
484 #[error("'auth' call required")]
485 ServerAuthRequired,
486 #[error("challenge not yet issued")]
487 AuthChallengeNotIssued,
488 #[error("challenge token is invalid")]
489 AuthChallengeBadToken,
490 #[error("unauthorized client refused")]
491 AuthMismatch,
492 #[error("keyring communication timed out after 5s")]
493 KeyringTimeout,
494 #[error("no host is connected to the tunnel relay")]
495 NoTunnelEndpoint,
496 #[error("could not parse `host`: {0}")]
497 InvalidHostAddress(std::net::AddrParseError),
498 #[error("could not start server on the given host/port: {0}")]
499 CouldNotListenOnInterface(hyper::Error),
500 #[error(
501 "Run this command again with --accept-server-license-terms to indicate your agreement."
502 )]
503 NeedsInteractiveLegalConsent,
504 #[error("Sorry, you cannot use this CLI without accepting the terms.")]
505 DeniedLegalConset,
506 #[error("The server is not yet downloaded, try again shortly.")]
507 ServerNotYetDownloaded,
508 #[error("An error was encountered downloading the server, please retry: {0}")]
509 ServerDownloadError(String),
510 #[error("Updates are are not available: {0}")]
511 UpdatesNotConfigured(&'static str),
512 #[error("Could not check for update: {0}")]
514 UpdateCheckFailed(String),
515 #[error("Could not write connection token file: {0}")]
516 CouldNotCreateConnectionTokenFile(std::io::Error)
517}
518
519makeAnyError!(
520 MismatchConnectionToken,
521 DevTunnelError,
522 StatusError,
523 WrappedError,
524 InvalidServerExtensionError,
525 MissingEntrypointError,
526 SetupError,
527 NoHomeForLauncherError,
528 TunnelCreationFailed,
529 TunnelHostFailed,
530 InvalidTunnelName,
531 ExtensionInstallFailed,
532 MismatchedLaunchModeError,
533 NoAttachedServerError,
534 RefreshTokenNotAvailableError,
535 NoInstallInUserProvidedPath,
536 UserCancelledInstallation,
537 InvalidRequestedVersion,
538 CannotForwardControlPort,
539 ServerHasClosed,
540 ServiceAlreadyRegistered,
541 WindowsNeedsElevation,
542 CorruptDownload,
543 MissingHomeDirectory,
544 OAuthError,
545 InvalidRpcDataError,
546 CodeError,
547 DbusConnectFailedError
548);
549
550impl From<reqwest::Error> for AnyError {
551 fn from(e: reqwest::Error) -> AnyError {
552 AnyError::WrappedError(WrappedError::from(e))
553 }
554}