feat: improve error management in adb_cli
- adds an explicit error message when error needs to be filled with an issue - remove anyhow dependency in adb_cli - update deps
This commit is contained in:
@@ -11,11 +11,10 @@ rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adb_client = { version = "^2.1.17" }
|
||||
anyhow = { version = "1.0.100" }
|
||||
clap = { version = "4.5.51", features = ["derive"] }
|
||||
adb_client = { version = "^2.1.18" }
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
env_logger = { version = "0.11.8" }
|
||||
log = { version = "0.4.28" }
|
||||
log = { version = "0.4.29" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
termios = { version = "0.3.3" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# adb_cli
|
||||
# `adb_cli`
|
||||
|
||||
[](./LICENSE-MIT)
|
||||

|
||||
|
||||
@@ -4,7 +4,7 @@ use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||
|
||||
use termios::{TCSANOW, Termios, VMIN, VTIME, tcsetattr};
|
||||
|
||||
use crate::Result;
|
||||
use crate::models::{ADBCliError, ADBCliResult};
|
||||
|
||||
pub struct ADBTermios {
|
||||
fd: RawFd,
|
||||
@@ -13,7 +13,7 @@ pub struct ADBTermios {
|
||||
}
|
||||
|
||||
impl ADBTermios {
|
||||
pub fn new(fd: &impl AsRawFd) -> Result<Self> {
|
||||
pub fn new(fd: &impl AsRawFd) -> Result<Self, ADBCliError> {
|
||||
let mut new_termios = Termios::from_fd(fd.as_raw_fd())?;
|
||||
let old_termios = new_termios; // Saves previous state
|
||||
new_termios.c_lflag = 0;
|
||||
@@ -27,7 +27,7 @@ impl ADBTermios {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_adb_termios(&mut self) -> Result<()> {
|
||||
pub fn set_adb_termios(&mut self) -> ADBCliResult<()> {
|
||||
Ok(tcsetattr(self.fd, TCSANOW, &self.new_termios)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use adb_client::ADBEmulatorDevice;
|
||||
|
||||
use crate::models::{EmuCommand, EmulatorCommand};
|
||||
use crate::models::{ADBCliResult, EmuCommand, EmulatorCommand};
|
||||
|
||||
pub fn handle_emulator_commands(emulator_command: EmulatorCommand) -> anyhow::Result<()> {
|
||||
pub fn handle_emulator_commands(emulator_command: EmulatorCommand) -> ADBCliResult<()> {
|
||||
let mut emulator = ADBEmulatorDevice::new(emulator_command.serial, None)?;
|
||||
|
||||
match emulator_command.command {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use adb_client::ADBServerDevice;
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
use crate::models::LocalDeviceCommand;
|
||||
use crate::models::{ADBCliResult, LocalDeviceCommand};
|
||||
|
||||
pub fn handle_local_commands(
|
||||
mut device: ADBServerDevice,
|
||||
local_device_commands: LocalDeviceCommand,
|
||||
) -> Result<()> {
|
||||
) -> ADBCliResult<()> {
|
||||
match local_device_commands {
|
||||
LocalDeviceCommand::HostFeatures => {
|
||||
let features = device
|
||||
@@ -16,7 +15,7 @@ pub fn handle_local_commands(
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.reduce(|a, b| format!("{a},{b}"))
|
||||
.ok_or(anyhow!("cannot list features"))?;
|
||||
.unwrap_or_default();
|
||||
log::info!("Available host features: {features}");
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -14,7 +14,6 @@ use adb_client::{
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use adb_termios::ADBTermios;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use handlers::{handle_emulator_commands, handle_host_commands, handle_local_commands};
|
||||
use models::{DeviceCommands, LocalCommand, MainCommand, Opts};
|
||||
@@ -24,87 +23,10 @@ use std::io::Write;
|
||||
use std::path::Path;
|
||||
use utils::setup_logger;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// This depends on `clap`
|
||||
let opts = Opts::parse();
|
||||
use crate::models::{ADBCliError, ADBCliResult};
|
||||
|
||||
// SAFETY:
|
||||
// We are assuming the entire process is single-threaded
|
||||
// at this point.
|
||||
// This seems true for the current version of `clap`,
|
||||
// but there's no guarantee for future updates
|
||||
unsafe { setup_logger(opts.debug) };
|
||||
|
||||
// Directly handling methods / commands that aren't linked to [`ADBDeviceExt`] trait.
|
||||
// Other methods just have to create a concrete [`ADBDeviceExt`] instance, and return it.
|
||||
// This instance will then be used to execute desired command.
|
||||
let (mut device, commands) = match opts.command {
|
||||
MainCommand::Host(server_command) => return Ok(handle_host_commands(server_command)?),
|
||||
MainCommand::Emu(emulator_command) => return handle_emulator_commands(emulator_command),
|
||||
MainCommand::Local(server_command) => {
|
||||
// Must start server to communicate with device, but only if this is a local one.
|
||||
let server_address_ip = server_command.address.ip();
|
||||
if server_address_ip.is_loopback() || server_address_ip.is_unspecified() {
|
||||
ADBServer::start(&HashMap::default(), &None);
|
||||
}
|
||||
|
||||
let device = match server_command.serial {
|
||||
Some(serial) => ADBServerDevice::new(serial, Some(server_command.address)),
|
||||
None => ADBServerDevice::autodetect(Some(server_command.address)),
|
||||
};
|
||||
|
||||
match server_command.command {
|
||||
LocalCommand::DeviceCommands(device_commands) => (device.boxed(), device_commands),
|
||||
LocalCommand::LocalDeviceCommand(local_device_command) => {
|
||||
return handle_local_commands(device, local_device_command);
|
||||
}
|
||||
}
|
||||
}
|
||||
MainCommand::Usb(usb_command) => {
|
||||
let device = match (usb_command.vendor_id, usb_command.product_id) {
|
||||
(Some(vid), Some(pid)) => match usb_command.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?,
|
||||
None => ADBUSBDevice::new(vid, pid)?,
|
||||
},
|
||||
(None, None) => match usb_command.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?,
|
||||
None => ADBUSBDevice::autodetect()?,
|
||||
},
|
||||
_ => {
|
||||
anyhow::bail!(
|
||||
"please either supply values for both the --vendor-id and --product-id flags or none."
|
||||
);
|
||||
}
|
||||
};
|
||||
(device.boxed(), usb_command.commands)
|
||||
}
|
||||
MainCommand::Tcp(tcp_command) => {
|
||||
let device = match tcp_command.path_to_private_key {
|
||||
Some(pk) => ADBTcpDevice::new_with_custom_private_key(tcp_command.address, pk)?,
|
||||
None => ADBTcpDevice::new(tcp_command.address)?,
|
||||
};
|
||||
(device.boxed(), tcp_command.commands)
|
||||
}
|
||||
MainCommand::Mdns => {
|
||||
let mut service = MDNSDiscoveryService::new()?;
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
service.start(tx)?;
|
||||
|
||||
log::info!("Starting mdns discovery...");
|
||||
while let Ok(device) = rx.recv() {
|
||||
log::info!(
|
||||
"Found device {} with addresses {:?}",
|
||||
device.fullname,
|
||||
device.addresses
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(service.shutdown()?);
|
||||
}
|
||||
};
|
||||
|
||||
match commands {
|
||||
fn run_command(mut device: Box<dyn ADBDeviceExt>, command: DeviceCommands) -> ADBCliResult<()> {
|
||||
match command {
|
||||
DeviceCommands::Shell { commands } => {
|
||||
if commands.is_empty() {
|
||||
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
|
||||
@@ -166,3 +88,84 @@ fn main() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ADBCliResult<()> {
|
||||
// This depends on `clap`
|
||||
let opts = Opts::parse();
|
||||
|
||||
setup_logger(opts.debug);
|
||||
|
||||
// Directly handling methods / commands that aren't linked to [`ADBDeviceExt`] trait.
|
||||
// Other methods just have to create a concrete [`ADBDeviceExt`] instance, and return it.
|
||||
// This instance will then be used to execute desired command.
|
||||
let (device, commands) = match opts.command {
|
||||
MainCommand::Host(server_command) => return Ok(handle_host_commands(server_command)?),
|
||||
MainCommand::Emu(emulator_command) => return handle_emulator_commands(emulator_command),
|
||||
MainCommand::Local(server_command) => {
|
||||
// Must start server to communicate with device, but only if this is a local one.
|
||||
let server_address_ip = server_command.address.ip();
|
||||
if server_address_ip.is_loopback() || server_address_ip.is_unspecified() {
|
||||
ADBServer::start(&HashMap::default(), &None);
|
||||
}
|
||||
|
||||
let device = match server_command.serial {
|
||||
Some(serial) => ADBServerDevice::new(serial, Some(server_command.address)),
|
||||
None => ADBServerDevice::autodetect(Some(server_command.address)),
|
||||
};
|
||||
|
||||
match server_command.command {
|
||||
LocalCommand::DeviceCommands(device_commands) => (device.boxed(), device_commands),
|
||||
LocalCommand::LocalDeviceCommand(local_device_command) => {
|
||||
return handle_local_commands(device, local_device_command);
|
||||
}
|
||||
}
|
||||
}
|
||||
MainCommand::Usb(usb_command) => {
|
||||
let device = match (usb_command.vendor_id, usb_command.product_id) {
|
||||
(Some(vid), Some(pid)) => match usb_command.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?,
|
||||
None => ADBUSBDevice::new(vid, pid)?,
|
||||
},
|
||||
(None, None) => match usb_command.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?,
|
||||
None => ADBUSBDevice::autodetect()?,
|
||||
},
|
||||
_ => {
|
||||
return Err(ADBCliError::Standard(
|
||||
"cannot specify flags --vendor-id without --product-id or vice versa"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
(device.boxed(), usb_command.commands)
|
||||
}
|
||||
MainCommand::Tcp(tcp_command) => {
|
||||
let device = match tcp_command.path_to_private_key {
|
||||
Some(pk) => ADBTcpDevice::new_with_custom_private_key(tcp_command.address, pk)?,
|
||||
None => ADBTcpDevice::new(tcp_command.address)?,
|
||||
};
|
||||
(device.boxed(), tcp_command.commands)
|
||||
}
|
||||
MainCommand::Mdns => {
|
||||
let mut service = MDNSDiscoveryService::new()?;
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
service.start(tx)?;
|
||||
|
||||
log::info!("Starting mdns discovery...");
|
||||
while let Ok(device) = rx.recv() {
|
||||
log::info!(
|
||||
"Found device {} with addresses {:?}",
|
||||
device.fullname,
|
||||
device.addresses
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(service.shutdown()?);
|
||||
}
|
||||
};
|
||||
|
||||
run_command(device, commands)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
84
adb_cli/src/models/adb_cli_error.rs
Normal file
84
adb_cli/src/models/adb_cli_error.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use adb_client::RustADBError;
|
||||
|
||||
pub type ADBCliResult<T> = Result<T, ADBCliError>;
|
||||
|
||||
pub enum ADBCliError {
|
||||
Standard(Box<dyn std::error::Error>),
|
||||
MayNeedAnIssue(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
impl Debug for ADBCliError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ADBCliError::Standard(error) => write!(f, "{error}"),
|
||||
ADBCliError::MayNeedAnIssue(error) => write!(
|
||||
f,
|
||||
r"
|
||||
This error is abnormal and may need to fill an issue.
|
||||
Please submit it to this project's repository here: https://github.com/cocool97/adb_client/issues.
|
||||
Error source:
|
||||
{error}
|
||||
",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ADBCliError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
// We do not consider adb_cli related `std::io::error` as critical
|
||||
Self::Standard(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<adb_client::RustADBError> for ADBCliError {
|
||||
fn from(value: adb_client::RustADBError) -> Self {
|
||||
let value = Box::new(value);
|
||||
|
||||
match value.as_ref() {
|
||||
// List of [`RustADBError`] that may need an issue as abnormal
|
||||
RustADBError::RegexParsingError
|
||||
| RustADBError::WrongResponseReceived(_, _)
|
||||
| RustADBError::FramebufferImageError(_)
|
||||
| RustADBError::FramebufferConversionError
|
||||
| RustADBError::UnimplementedFramebufferImageVersion(_)
|
||||
| RustADBError::IOError(_)
|
||||
| RustADBError::ADBRequestFailed(_)
|
||||
| RustADBError::UnknownDeviceState(_)
|
||||
| RustADBError::Utf8StrError(_)
|
||||
| RustADBError::Utf8StringError(_)
|
||||
| RustADBError::RegexError(_)
|
||||
| RustADBError::ParseIntError(_)
|
||||
| RustADBError::ConversionError
|
||||
| RustADBError::IntegerConversionError(_)
|
||||
| RustADBError::HomeError
|
||||
| RustADBError::NoHomeDirectory
|
||||
| RustADBError::UsbError(_)
|
||||
| RustADBError::InvalidIntegrity(_, _)
|
||||
| RustADBError::Base64DecodeError(_)
|
||||
| RustADBError::Base64EncodeError(_)
|
||||
| RustADBError::RSAError(_)
|
||||
| RustADBError::TryFromSliceError(_)
|
||||
| RustADBError::RsaPkcs8Error(_)
|
||||
| RustADBError::CertificateGenerationError(_)
|
||||
| RustADBError::TLSError(_)
|
||||
| RustADBError::PemCertError(_)
|
||||
| RustADBError::PoisonError
|
||||
| RustADBError::UpgradeError(_)
|
||||
| RustADBError::MDNSError(_)
|
||||
| RustADBError::SendError(_)
|
||||
| RustADBError::UnknownTransport(_) => Self::MayNeedAnIssue(value),
|
||||
// List of [`RustADBError`] that may occur in standard contexts and therefore do not require for issues
|
||||
RustADBError::ADBDeviceNotPaired
|
||||
| RustADBError::UnknownResponseType(_)
|
||||
| RustADBError::DeviceNotFound(_)
|
||||
| RustADBError::USBNoDescriptorFound
|
||||
| RustADBError::ADBShellNotSupported
|
||||
| RustADBError::USBDeviceNotFound(_, _)
|
||||
| RustADBError::WrongFileExtension(_)
|
||||
| RustADBError::AddrParseError(_) => Self::Standard(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::net::SocketAddrV4;
|
||||
use std::{net::SocketAddrV4, str::FromStr};
|
||||
|
||||
use adb_client::{RustADBError, WaitForDeviceTransport};
|
||||
use clap::Parser;
|
||||
@@ -6,7 +6,7 @@ use clap::Parser;
|
||||
fn parse_wait_for_device_device_transport(
|
||||
value: &str,
|
||||
) -> Result<WaitForDeviceTransport, RustADBError> {
|
||||
WaitForDeviceTransport::try_from(value)
|
||||
WaitForDeviceTransport::from_str(value)
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod adb_cli_error;
|
||||
mod device;
|
||||
mod emu;
|
||||
mod host;
|
||||
@@ -7,6 +8,7 @@ mod reboot_type;
|
||||
mod tcp;
|
||||
mod usb;
|
||||
|
||||
pub use adb_cli_error::{ADBCliError, ADBCliResult};
|
||||
pub use device::DeviceCommands;
|
||||
pub use emu::{EmuCommand, EmulatorCommand};
|
||||
pub use host::{HostCommand, MdnsCommand};
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
/// # Safety
|
||||
///
|
||||
/// This conditionally mutates the process' environment.
|
||||
/// See [`std::env::set_var`] for more info.
|
||||
pub unsafe fn setup_logger(debug: bool) {
|
||||
// RUST_LOG variable has more priority then "--debug" flag
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
let level = if debug { "trace" } else { "info" };
|
||||
use env_logger::{Builder, Env};
|
||||
|
||||
unsafe { std::env::set_var("RUST_LOG", level) };
|
||||
}
|
||||
|
||||
env_logger::init();
|
||||
/// Sets up appropriate logger level:
|
||||
/// - if `RUST_LOG` environment variable is set, use its value
|
||||
/// - else, use `debug` CLI option
|
||||
pub fn setup_logger(debug: bool) {
|
||||
Builder::from_env(Env::default().default_filter_or(if debug { "debug" } else { "info" }))
|
||||
.init();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user