use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AdbError {
#[error("Connection failed: {message}")]
ConnectionFailed { message: String },
#[error("Device not found: {serial}")]
DeviceNotFound { serial: String },
#[error("Command execution failed: {command}, reason: {reason}")]
CommandFailed { command: String, reason: String },
#[error("Protocol error: {message}")]
ProtocolError { message: String },
#[error("Parse error: {message}")]
ParseError { message: String },
#[error("File operation failed: {operation} on {path}")]
FileOperationFailed { operation: String, path: String },
#[error("Network error: {message}")]
NetworkError { message: String },
#[error("Operation timed out after {seconds} seconds")]
Timeout { seconds: u64 },
#[error("Permission denied: {message}")]
PermissionDenied { message: String },
#[error("Application error: {package_name} - {message}")]
ApplicationError {
package_name: String,
message: String,
},
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Regex error: {0}")]
Regex(#[from] regex::Error),
#[error("UTF-8 error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("Parse number error: {0}")]
ParseInt(#[from] std::num::ParseIntError),
#[cfg(feature = "serde")]
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Time error: {0}")]
SystemTime(#[from] std::time::SystemTimeError),
#[error("Anyhow error: {0}")]
Anyhow(#[from] anyhow::Error),
#[error("Unknown error: {message}")]
Unknown { message: String },
}
pub type AdbResult<T> = Result<T, AdbError>;
impl AdbError {
pub fn from_display<E: fmt::Display>(err: E) -> Self {
AdbError::Unknown {
message: err.to_string(),
}
}
pub fn connection_failed<S: Into<String>>(message: S) -> Self {
AdbError::ConnectionFailed {
message: message.into(),
}
}
pub fn device_not_found<S: Into<String>>(serial: S) -> Self {
AdbError::DeviceNotFound {
serial: serial.into(),
}
}
pub fn command_failed<S1: Into<String>, S2: Into<String>>(command: S1, reason: S2) -> Self {
AdbError::CommandFailed {
command: command.into(),
reason: reason.into(),
}
}
pub fn protocol_error<S: Into<String>>(message: S) -> Self {
AdbError::ProtocolError {
message: message.into(),
}
}
pub fn parse_error<S: Into<String>>(message: S) -> Self {
AdbError::ParseError {
message: message.into(),
}
}
pub fn file_operation_failed<S1: Into<String>, S2: Into<String>>(
operation: S1,
path: S2,
) -> Self {
AdbError::FileOperationFailed {
operation: operation.into(),
path: path.into(),
}
}
pub fn network_error<S: Into<String>>(message: S) -> Self {
AdbError::NetworkError {
message: message.into(),
}
}
pub fn timeout(seconds: u64) -> Self {
AdbError::Timeout { seconds }
}
pub fn permission_denied<S: Into<String>>(message: S) -> Self {
AdbError::PermissionDenied {
message: message.into(),
}
}
pub fn application_error<S1: Into<String>, S2: Into<String>>(
package_name: S1,
message: S2,
) -> Self {
AdbError::ApplicationError {
package_name: package_name.into(),
message: message.into(),
}
}
pub fn unknown<S: Into<String>>(message: S) -> Self {
AdbError::Unknown {
message: message.into(),
}
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
AdbError::ConnectionFailed { .. }
| AdbError::NetworkError { .. }
| AdbError::Timeout { .. }
| AdbError::Io(_)
)
}
pub fn is_fatal(&self) -> bool {
matches!(
self,
AdbError::DeviceNotFound { .. }
| AdbError::PermissionDenied { .. }
| AdbError::ParseError { .. }
)
}
pub fn error_code(&self) -> &'static str {
match self {
AdbError::ConnectionFailed { .. } => "CONNECTION_FAILED",
AdbError::DeviceNotFound { .. } => "DEVICE_NOT_FOUND",
AdbError::CommandFailed { .. } => "COMMAND_FAILED",
AdbError::ProtocolError { .. } => "PROTOCOL_ERROR",
AdbError::ParseError { .. } => "PARSE_ERROR",
AdbError::FileOperationFailed { .. } => "FILE_OPERATION_FAILED",
AdbError::NetworkError { .. } => "NETWORK_ERROR",
AdbError::Timeout { .. } => "TIMEOUT",
AdbError::PermissionDenied { .. } => "PERMISSION_DENIED",
AdbError::ApplicationError { .. } => "APPLICATION_ERROR",
AdbError::Io(_) => "IO_ERROR",
AdbError::Regex(_) => "REGEX_ERROR",
AdbError::Utf8(_) => "UTF8_ERROR",
AdbError::ParseInt(_) => "PARSE_INT_ERROR",
#[cfg(feature = "serde")]
AdbError::Json(_) => "JSON_ERROR",
AdbError::SystemTime(_) => "SYSTEM_TIME_ERROR",
AdbError::Anyhow(_) => "ANYHOW_ERROR",
AdbError::Unknown { .. } => "UNKNOWN_ERROR",
}
}
}
pub trait AdbResultExt<T> {
fn to_adb_error(self) -> AdbResult<T>;
fn with_adb_context<F>(self, f: F) -> AdbResult<T>
where
F: FnOnce() -> String;
}
impl<T> AdbResultExt<T> for anyhow::Result<T> {
fn to_adb_error(self) -> AdbResult<T> {
self.map_err(AdbError::Anyhow)
}
fn with_adb_context<F>(self, f: F) -> AdbResult<T>
where
F: FnOnce() -> String,
{
self.map_err(|e| AdbError::Anyhow(e.context(f())))
}
}
impl<T> AdbResultExt<T> for Result<T, std::io::Error> {
fn to_adb_error(self) -> AdbResult<T> {
self.map_err(AdbError::Io)
}
fn with_adb_context<F>(self, f: F) -> AdbResult<T>
where
F: FnOnce() -> String,
{
self.map_err(|e| AdbError::Io(std::io::Error::new(e.kind(), format!("{}: {}", f(), e))))
}
}
#[macro_export]
macro_rules! adb_bail {
($err:expr) => {
return Err($err.into())
};
($fmt:expr, $($arg:tt)*) => {
return Err($crate::errors::AdbError::unknown(format!($fmt, $($arg)*)))
};
}
#[macro_export]
macro_rules! adb_ensure {
($cond:expr, $err:expr) => {
if !$cond {
return Err($err.into());
}
};
($cond:expr, $fmt:expr, $($arg:tt)*) => {
if !$cond {
return Err($crate::errors::AdbError::unknown(format!($fmt, $($arg)*)));
}
};
}
#[macro_export]
macro_rules! adb_context {
($result:expr, $msg:expr) => {
$result.context($msg).map_err(AdbError::Anyhow)
};
($result:expr, $fmt:expr, $($arg:tt)*) => {
$result.context(format!($fmt, $($arg)*)).map_err(AdbError::Anyhow)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = AdbError::connection_failed("Test connection failed");
assert_eq!(err.error_code(), "CONNECTION_FAILED");
assert!(err.is_retryable());
assert!(!err.is_fatal());
}
#[test]
fn test_device_not_found() {
let err = AdbError::device_not_found("emulator-5554");
assert_eq!(err.error_code(), "DEVICE_NOT_FOUND");
assert!(!err.is_retryable());
assert!(err.is_fatal());
}
#[test]
fn test_command_failed() {
let err = AdbError::command_failed("shell ls", "permission denied");
assert_eq!(err.error_code(), "COMMAND_FAILED");
assert!(!err.is_retryable());
}
#[test]
fn test_timeout_error() {
let err = AdbError::timeout(30);
assert_eq!(err.error_code(), "TIMEOUT");
assert!(err.is_retryable());
assert!(!err.is_fatal());
}
#[test]
fn test_error_display() {
let err = AdbError::application_error("com.example.app", "App crashed");
let display_str = format!("{}", err);
assert!(display_str.contains("com.example.app"));
assert!(display_str.contains("App crashed"));
}
#[test]
fn test_anyhow_conversion() {
let anyhow_err = anyhow::anyhow!("Some error");
let adb_err: AdbResult<()> = Err(anyhow_err).to_adb_error();
assert!(matches!(adb_err, Err(AdbError::Anyhow(_))));
}
#[test]
fn test_anyhow_from_conversion() {
let anyhow_err = anyhow::anyhow!("Some error");
let adb_err: AdbError = anyhow_err.into();
assert!(matches!(adb_err, AdbError::Anyhow(_)));
}
}
mod examples {
use super::*;
use anyhow::Context;
async fn example_function() -> AdbResult<()> {
let _result = some_anyhow_function().context("Failed to create port forward")?;
let _result = some_anyhow_function()
.with_adb_context(|| "Failed to create port forward".to_string())?;
let _result = adb_context!(some_anyhow_function(), "Failed to create port forward")?;
Ok(())
}
fn some_anyhow_function() -> anyhow::Result<()> {
Ok(())
}
}