use super::errors::{ConfigError, InterruptedError, UsageError};
use bairelay_neolink_core::bc_protocol::Error as CoreError;
pub const EXIT_OK: i32 = 0;
pub const EXIT_UNEXPECTED: i32 = 1;
pub const EXIT_USAGE: i32 = 2;
pub const EXIT_CONFIG: i32 = 3;
pub const EXIT_CONNECTION: i32 = 4;
pub const EXIT_PROTOCOL: i32 = 5;
pub const EXIT_UNSUPPORTED: i32 = 6;
pub const EXIT_INTERRUPTED: i32 = 130;
pub fn classify(err: &anyhow::Error) -> i32 {
for cause in err.chain() {
if cause.is::<InterruptedError>() {
return EXIT_INTERRUPTED;
}
if cause.is::<UsageError>() {
return EXIT_USAGE;
}
if cause.is::<ConfigError>() {
return EXIT_CONFIG;
}
if let Some(core_err) = cause.downcast_ref::<CoreError>() {
return classify_core(core_err);
}
}
EXIT_UNEXPECTED
}
fn classify_core(err: &CoreError) -> i32 {
match err {
CoreError::CameraLoginFail
| CoreError::AuthFailed
| CoreError::DiscoveryTimeout
| CoreError::DiscoveryIgnored
| CoreError::NoDmap
| CoreError::NoDev
| CoreError::RegisterError
| CoreError::DroppedConnection
| CoreError::DroppedConnectionTry(_)
| CoreError::BroadcastDroppedConnectionTry(_)
| CoreError::ConnectionShutdown
| CoreError::ConnectionUnavailable
| CoreError::CannotInitCamera
| CoreError::RelayTerminate
| CoreError::CameraTerminate
| CoreError::AddrResolutionError
| CoreError::AddrParseError(_)
| CoreError::Timeout(_)
| CoreError::TimeoutError(_)
| CoreError::TimeoutDisconnected
| CoreError::BcUdpTimeout
| CoreError::BcUdpReconnectTimeout
| CoreError::BcUdpDropReceiver(_)
| CoreError::BcUdpDropSender
| CoreError::BcUdpPayloadDroppedInner
| CoreError::Io(_) => EXIT_CONNECTION,
CoreError::MissingAbility { .. } => EXIT_UNSUPPORTED,
_ => EXIT_PROTOCOL,
}
}
#[cfg(test)]
mod tests {
use super::*;
use bairelay_neolink_core::bc_protocol::Error as CoreError;
#[test]
fn classify_config_error() {
let e: anyhow::Error = ConfigError::new("camera not found").into();
assert_eq!(classify(&e), EXIT_CONFIG);
}
#[test]
fn classify_usage_error() {
let e: anyhow::Error = UsageError::new("snapshot stdout + --json").into();
assert_eq!(classify(&e), EXIT_USAGE);
}
#[test]
fn classify_interrupted() {
let e: anyhow::Error = InterruptedError::new().into();
assert_eq!(classify(&e), EXIT_INTERRUPTED);
}
#[test]
fn classify_login_failure_is_connection() {
let e: anyhow::Error = CoreError::CameraLoginFail.into();
assert_eq!(classify(&e), EXIT_CONNECTION);
}
#[test]
fn classify_dropped_connection_is_connection() {
let e: anyhow::Error = CoreError::DroppedConnection.into();
assert_eq!(classify(&e), EXIT_CONNECTION);
}
#[test]
fn classify_service_unavailable_is_protocol() {
let e: anyhow::Error = CoreError::CameraServiceUnavailable { id: 23, code: 500 }.into();
assert_eq!(classify(&e), EXIT_PROTOCOL);
}
#[test]
fn classify_unknown_is_unexpected() {
let e: anyhow::Error = anyhow::anyhow!("something weird");
assert_eq!(classify(&e), EXIT_UNEXPECTED);
}
#[test]
fn classify_wrapped_error_walks_chain() {
let e: anyhow::Error = anyhow::Error::from(ConfigError::new("missing"))
.context("while loading config")
.context("during bairelay startup");
assert_eq!(classify(&e), EXIT_CONFIG);
}
#[test]
fn classify_io_error_is_connection() {
use std::sync::Arc;
let io = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "no route");
let e: anyhow::Error = CoreError::Io(Arc::new(io)).into();
assert_eq!(classify(&e), EXIT_CONNECTION);
}
#[test]
fn classify_auth_failure_is_connection() {
let e: anyhow::Error = CoreError::AuthFailed.into();
assert_eq!(classify(&e), EXIT_CONNECTION);
}
#[test]
fn classify_missing_ability_is_unsupported() {
let e: anyhow::Error = CoreError::MissingAbility {
name: "ledState".into(),
requested: "read".into(),
actual: "none".into(),
}
.into();
assert_eq!(classify(&e), EXIT_UNSUPPORTED);
}
}