use crate::{
constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME},
rpc::ResponseError,
};
use std::fmt::Display;
use thiserror::Error;
#[derive(Debug, Clone)]
pub struct WrappedError {
message: String,
original: String,
}
impl std::fmt::Display for WrappedError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}: {}", self.message, self.original)
}
}
impl std::error::Error for WrappedError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl WrappedError {
}
impl From<reqwest::Error> for WrappedError {
fn from(e: reqwest::Error) -> WrappedError {
WrappedError {
message: format!(
"error requesting {}",
e.url().map_or("<unknown>", |u| u.as_str())
),
original: format!("{e}"),
}
}
}
pub fn wrapdbg<T, S>(original: T, message: S) -> WrappedError
where
T: std::fmt::Debug,
S: Into<String>,
{
WrappedError {
message: message.into(),
original: format!("{original:?}"),
}
}
pub fn wrap<T, S>(original: T, message: S) -> WrappedError
where
T: Display,
S: Into<String>,
{
WrappedError {
message: message.into(),
original: format!("{original}"),
}
}
#[derive(Debug)]
pub struct StatusError {
pub url: String,
pub status_code: u16,
pub body: String,
}
impl std::fmt::Display for StatusError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"error requesting {}: {} {}",
self.url, self.status_code, self.body
)
}
}
impl StatusError {
pub async fn from_res(res: reqwest::Response) -> Result<StatusError, AnyError> {
let status_code = res.status().as_u16();
let url = res.url().to_string();
let body = res.text().await.map_err(|e| {
wrap(
e,
format!("failed to read response body on {status_code} code from {url}"),
)
})?;
Ok(StatusError {
url,
status_code,
body,
})
}
}
#[derive(Debug)]
pub struct MismatchConnectionToken(pub String);
impl std::fmt::Display for MismatchConnectionToken {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug)]
pub struct InvalidServerExtensionError(pub String);
impl std::fmt::Display for InvalidServerExtensionError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "invalid server extension '{}'", self.0)
}
}
#[derive(Debug, Clone)]
pub struct DevTunnelError(pub String);
impl std::fmt::Display for DevTunnelError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "could not open tunnel: {}", self.0)
}
}
impl std::error::Error for DevTunnelError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[derive(Debug)]
pub struct MissingEntrypointError();
impl std::fmt::Display for MissingEntrypointError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Missing entrypoints in server download. Most likely this is a corrupted download. Please retry")
}
}
#[derive(Debug)]
pub struct SetupError(pub String);
impl std::fmt::Display for SetupError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}\n\nMore info at {}/remote/linux",
DOCUMENTATION_URL.unwrap_or("<docs>"),
self.0
)
}
}
#[derive(Debug)]
pub struct NoHomeForLauncherError();
impl std::fmt::Display for NoHomeForLauncherError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"No $HOME variable was found in your environment. Either set it, or specify a `--data-dir` manually when invoking the launcher.",
)
}
}
#[derive(Debug)]
pub struct InvalidTunnelName(pub String);
impl std::fmt::Display for InvalidTunnelName {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(Debug)]
pub struct TunnelCreationFailed(pub String, pub String);
impl std::fmt::Display for TunnelCreationFailed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Could not create tunnel with name: {}\nReason: {}",
&self.0, &self.1
)
}
}
#[derive(Debug)]
pub struct TunnelHostFailed(pub String);
impl std::fmt::Display for TunnelHostFailed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(Debug)]
pub struct ExtensionInstallFailed(pub String);
impl std::fmt::Display for ExtensionInstallFailed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Extension install failed: {}", &self.0)
}
}
#[derive(Debug)]
pub struct MismatchedLaunchModeError();
impl std::fmt::Display for MismatchedLaunchModeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "A server is already running, but it was not launched in the same listening mode (port vs. socket) as this request")
}
}
#[derive(Debug)]
pub struct NoAttachedServerError();
impl std::fmt::Display for NoAttachedServerError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "No server is running")
}
}
#[derive(Debug)]
pub struct RefreshTokenNotAvailableError();
impl std::fmt::Display for RefreshTokenNotAvailableError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Refresh token not available, authentication is required")
}
}
#[derive(Debug)]
pub struct NoInstallInUserProvidedPath(pub String);
impl std::fmt::Display for NoInstallInUserProvidedPath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"No {} installation could be found in {}. You can run `{} --use-quality=stable` to switch to the latest stable version of {}.",
QUALITYLESS_PRODUCT_NAME,
self.0,
APPLICATION_NAME,
QUALITYLESS_PRODUCT_NAME
)
}
}
#[derive(Debug)]
pub struct InvalidRequestedVersion();
impl std::fmt::Display for InvalidRequestedVersion {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"The reqested version is invalid, expected one of 'stable', 'insiders', version number (x.y.z), or absolute path.",
)
}
}
#[derive(Debug)]
pub struct UserCancelledInstallation();
impl std::fmt::Display for UserCancelledInstallation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Installation aborted.")
}
}
#[derive(Debug)]
pub struct CannotForwardControlPort();
impl std::fmt::Display for CannotForwardControlPort {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Cannot forward or unforward port {CONTROL_PORT}.")
}
}
#[derive(Debug)]
pub struct ServerHasClosed();
impl std::fmt::Display for ServerHasClosed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Request cancelled because the server has closed")
}
}
#[derive(Debug)]
pub struct ServiceAlreadyRegistered();
impl std::fmt::Display for ServiceAlreadyRegistered {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Already registered the service. Run `{APPLICATION_NAME} tunnel service uninstall` to unregister it first")
}
}
#[derive(Debug)]
pub struct WindowsNeedsElevation(pub String);
impl std::fmt::Display for WindowsNeedsElevation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "{}", self.0)?;
writeln!(f)?;
writeln!(f, "You may need to run this command as an administrator:")?;
writeln!(f, " 1. Open the start menu and search for Powershell")?;
writeln!(f, " 2. Right click and 'Run as administrator'")?;
if let Ok(exe) = std::env::current_exe() {
writeln!(
f,
" 3. Run &'{}' '{}'",
exe.display(),
std::env::args().skip(1).collect::<Vec<_>>().join("' '")
)
} else {
writeln!(f, " 3. Run the same command again",)
}
}
}
#[derive(Debug)]
pub struct InvalidRpcDataError(pub String);
impl std::fmt::Display for InvalidRpcDataError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "parse error: {}", self.0)
}
}
#[derive(Debug)]
pub struct CorruptDownload(pub String);
impl std::fmt::Display for CorruptDownload {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Error updating the {} CLI: {}",
QUALITYLESS_PRODUCT_NAME, self.0
)
}
}
#[derive(Debug)]
pub struct MissingHomeDirectory();
impl std::fmt::Display for MissingHomeDirectory {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Could not find your home directory. Please ensure this command is running in the context of an normal user.")
}
}
#[derive(Debug)]
pub struct OAuthError {
pub error: String,
pub error_description: Option<String>,
}
impl std::fmt::Display for OAuthError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Error getting authorization: {} {}",
self.error,
self.error_description.as_deref().unwrap_or("")
)
}
}
macro_rules! makeAnyError {
($($e:ident),*) => {
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
pub enum AnyError {
$($e($e),)*
}
impl std::fmt::Display for AnyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
$(AnyError::$e(ref e) => e.fmt(f),)*
}
}
}
impl std::error::Error for AnyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
$(impl From<$e> for AnyError {
fn from(e: $e) -> AnyError {
AnyError::$e(e)
}
})*
};
}
#[derive(Debug)]
pub struct DbusConnectFailedError(pub String);
impl Display for DbusConnectFailedError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut str = String::new();
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.");
if std::env::var("WSL_DISTRO_NAME").is_ok() {
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");
}
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: ");
str.push_str(&self.0);
str.push('\n');
write!(f, "{str}")
}
}
#[derive(Error, Debug)]
pub enum CodeError {
#[error("could not connect to socket/pipe: {0:?}")]
AsyncPipeFailed(std::io::Error),
#[error("could not listen on socket/pipe: {0:?}")]
AsyncPipeListenerFailed(std::io::Error),
#[error("could not create singleton lock file: {0:?}")]
SingletonLockfileOpenFailed(std::io::Error),
#[error("could not read singleton lock file: {0:?}")]
SingletonLockfileReadFailed(rmp_serde::decode::Error),
#[error("the process holding the singleton lock file (pid={0}) exited")]
SingletonLockedProcessExited(u32),
#[error("no tunnel process is currently running")]
NoRunningTunnel,
#[error("rpc call failed: {0:?}")]
TunnelRpcCallFailed(ResponseError),
#[cfg(windows)]
#[error("the windows app lock {0} already exists")]
AppAlreadyLocked(String),
#[cfg(windows)]
#[error("could not get windows app lock: {0:?}")]
AppLockFailed(std::io::Error),
#[error("failed to run command \"{command}\" (code {code}): {output}")]
CommandFailed {
command: String,
code: i32,
output: String,
},
#[error("platform not currently supported: {0}")]
UnsupportedPlatform(String),
#[error("This machine does not meet {name}'s prerequisites, expected either...\n{bullets}")]
PrerequisitesFailed { name: &'static str, bullets: String },
#[error("failed to spawn process: {0:?}")]
ProcessSpawnFailed(std::io::Error),
#[error("failed to handshake spawned process: {0:?}")]
ProcessSpawnHandshakeFailed(std::io::Error),
#[error("download appears corrupted, please retry ({0})")]
CorruptDownload(&'static str),
#[error("port forwarding is not available in this context")]
PortForwardingNotAvailable,
#[error("'auth' call required")]
ServerAuthRequired,
#[error("challenge not yet issued")]
AuthChallengeNotIssued,
#[error("challenge token is invalid")]
AuthChallengeBadToken,
#[error("unauthorized client refused")]
AuthMismatch,
#[error("keyring communication timed out after 5s")]
KeyringTimeout,
#[error("no host is connected to the tunnel relay")]
NoTunnelEndpoint,
#[error("could not parse `host`: {0}")]
InvalidHostAddress(std::net::AddrParseError),
#[error("could not start server on the given host/port: {0}")]
CouldNotListenOnInterface(hyper::Error),
#[error(
"Run this command again with --accept-server-license-terms to indicate your agreement."
)]
NeedsInteractiveLegalConsent,
#[error("Sorry, you cannot use this CLI without accepting the terms.")]
DeniedLegalConset,
#[error("The server is not yet downloaded, try again shortly.")]
ServerNotYetDownloaded,
#[error("An error was encountered downloading the server, please retry: {0}")]
ServerDownloadError(String),
#[error("Updates are are not available: {0}")]
UpdatesNotConfigured(&'static str),
#[error("Could not check for update: {0}")]
UpdateCheckFailed(String),
#[error("Could not read connection token file: {0}")]
CouldNotReadConnectionTokenFile(std::io::Error),
#[error("Could not write connection token file: {0}")]
CouldNotCreateConnectionTokenFile(std::io::Error),
#[error("A tunnel with the name {0} exists and is in-use. Please pick a different name or stop the existing tunnel.")]
TunnelActiveAndInUse(String),
#[error("Timed out looking for port/socket")]
ServerOriginTimeout,
#[error("Server exited without writing port/socket: {0}")]
ServerUnexpectedExit(String),
}
makeAnyError!(
MismatchConnectionToken,
DevTunnelError,
StatusError,
WrappedError,
InvalidServerExtensionError,
MissingEntrypointError,
SetupError,
NoHomeForLauncherError,
TunnelCreationFailed,
TunnelHostFailed,
InvalidTunnelName,
ExtensionInstallFailed,
MismatchedLaunchModeError,
NoAttachedServerError,
RefreshTokenNotAvailableError,
NoInstallInUserProvidedPath,
UserCancelledInstallation,
InvalidRequestedVersion,
CannotForwardControlPort,
ServerHasClosed,
ServiceAlreadyRegistered,
WindowsNeedsElevation,
CorruptDownload,
MissingHomeDirectory,
OAuthError,
InvalidRpcDataError,
CodeError,
DbusConnectFailedError
);
impl From<reqwest::Error> for AnyError {
fn from(e: reqwest::Error) -> AnyError {
AnyError::WrappedError(WrappedError::from(e))
}
}