diff --git a/backend/src/db.rs b/backend/src/db.rs index 8144d8a..a24d84b 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -10,7 +10,7 @@ use r2d2_sqlite::SqliteConnectionManager; use rusqlite_migration::Migrations; use tokio::sync::Mutex; -use crate::settings::get_settings; +use crate::{db::error::DbError, settings::get_settings}; static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/database_migrations"); static INITIALISED: Mutex = Mutex::const_new(false); @@ -40,7 +40,7 @@ pub fn get_db_connection() -> PooledConnection { } } -pub async fn init_db() -> Result<(), String> { +pub async fn init_db() -> Result<(), DbError> { let mut initialised = INITIALISED.lock().await; if *initialised { return Ok(()); @@ -59,7 +59,7 @@ pub async fn init_db() -> Result<(), String> { } Err(error) => { error!("Error updating database: {error}"); - Err(format!("Error updating database: {error}")) + Err(DbError::from(error)) } }; diff --git a/backend/src/db/devices.rs b/backend/src/db/devices.rs index bec0e48..2a767de 100644 --- a/backend/src/db/devices.rs +++ b/backend/src/db/devices.rs @@ -1,7 +1,10 @@ use log::{debug, error}; use rusqlite::params; -use crate::{db, model::devices::Device}; +use crate::{ + db::{self, error::DbError}, + model::devices::Device, +}; // Read device from its MAC address pub fn read(mac_address: String) -> Option { @@ -34,7 +37,7 @@ pub fn read(mac_address: String) -> Option { } } -pub fn insert(device: Device) -> Result<(), String> { +pub fn insert(device: Device) -> Result<(), DbError> { let conn = db::get_db_connection(); match conn.execute( @@ -46,12 +49,12 @@ pub fn insert(device: Device) -> Result<(), String> { }, Err(error) => { error!("Error inserting device ({device}) into database: {error}"); - Err(format!("Error inserting device ({device}) into database: {error}").to_string()) + Err(DbError::from(error)) }, } } -pub fn update(device: Device) -> Result<(), String> { +pub fn update(device: Device) -> Result<(), DbError> { let conn = db::get_db_connection(); match conn.execute( "UPDATE devices SET ipv4_address=?1, vendor=?2, last_seen=?3 WHERE mac_address=?4", @@ -68,7 +71,7 @@ pub fn update(device: Device) -> Result<(), String> { } Err(error) => { error!("Error updating device ({device}) in database: {error}"); - Err(format!("Error updating device ({device}) in database: {error}").to_string()) + Err(DbError::from(error)) } } } diff --git a/backend/src/db/error.rs b/backend/src/db/error.rs index cf95f6b..66d50cd 100644 --- a/backend/src/db/error.rs +++ b/backend/src/db/error.rs @@ -2,13 +2,15 @@ use std::{error, fmt}; #[derive(Debug)] pub enum DbError { - Parse(rusqlite::Error), + ParseRusqlite(rusqlite::Error), + ParseRusqliteMigration(rusqlite_migration::Error), } impl fmt::Display for DbError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - DbError::Parse(..) => write!(f, "Database access error"), + DbError::ParseRusqlite(..) => write!(f, "Database access error"), + DbError::ParseRusqliteMigration(..) => write!(f, "Database migration error"), } } } @@ -16,13 +18,19 @@ impl fmt::Display for DbError { impl error::Error for DbError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { - DbError::Parse(ref e) => Some(e), + DbError::ParseRusqlite(ref e) => Some(e), + DbError::ParseRusqliteMigration(ref e) => Some(e), } } } impl From for DbError { fn from(err: rusqlite::Error) -> DbError { - DbError::Parse(err) + DbError::ParseRusqlite(err) + } +} +impl From for DbError { + fn from(err: rusqlite_migration::Error) -> DbError { + DbError::ParseRusqliteMigration(err) } } diff --git a/backend/src/device_finders.rs b/backend/src/device_finders.rs index 6a5d36a..b2acbe0 100644 --- a/backend/src/device_finders.rs +++ b/backend/src/device_finders.rs @@ -1 +1,2 @@ pub mod arp; +pub mod error; diff --git a/backend/src/device_finders/arp.rs b/backend/src/device_finders/arp.rs index 3e4f41b..ab7ac07 100644 --- a/backend/src/device_finders/arp.rs +++ b/backend/src/device_finders/arp.rs @@ -1,5 +1,8 @@ mod packet_send_receive; +use crate::device_finders::error::{ + DataChannelError, InvalidDeviceError, NoIPAddressError, NoMACAddressError, +}; use crate::model::devices::Device; use crate::settings::get_settings; use duration_string::DurationString; @@ -11,7 +14,7 @@ use pnet::{ }; use tokio::time::{Duration, timeout}; -pub async fn find(interface: String) -> Result, String> { +pub async fn find(interface: String) -> Result, Box> { debug!("Looking up devices via ARP using interface {}", interface); // Get the network device to use @@ -24,7 +27,7 @@ pub async fn find(interface: String) -> Result, String> { Some(value) => value.clone(), None => { error!("Interface ({interface}) not found or not active."); - return Err(format!("Interface ({interface}) not found or not active.").to_string()); + return Err(InvalidDeviceError.into()); } }; @@ -41,9 +44,7 @@ pub async fn find(interface: String) -> Result, String> { Some(value) => value, None => { error!("No IP address found for selected interface ({interface})."); - return Err( - format!("No IP address found for selected interface ({interface}).").to_string(), - ); + return Err(NoIPAddressError.into()); } }; @@ -52,10 +53,7 @@ pub async fn find(interface: String) -> Result, String> { Some(mac) => mac, None => { error!("Could not get MAC address for selected interface ({interface})."); - return Err( - format!("Could not get MAC address for selected interface ({interface}).") - .to_string(), - ); + return Err(NoMACAddressError.into()); } }; @@ -68,7 +66,7 @@ pub async fn find(interface: String) -> Result, String> { Ok(value) => value, Err(error) => { error!("Could not create data channel: {error}"); - return Err(format!("Could not create data channel: {error}").to_string()); + return Err(DataChannelError.into()); } }; @@ -76,7 +74,7 @@ pub async fn find(interface: String) -> Result, String> { Channel::Ethernet(tx, rx) => (tx, rx), _ => { error!("Unsupported data channel type"); - return Err("Unsupported data channel type".to_string()); + return Err(DataChannelError.into()); } }; diff --git a/backend/src/device_finders/error.rs b/backend/src/device_finders/error.rs new file mode 100644 index 0000000..a926d74 --- /dev/null +++ b/backend/src/device_finders/error.rs @@ -0,0 +1,45 @@ +use std::{error, fmt}; + +#[derive(Debug)] +pub struct InvalidDeviceError; + +impl fmt::Display for InvalidDeviceError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Invalid network device") + } +} + +impl error::Error for InvalidDeviceError {} + +#[derive(Debug)] +pub struct NoIPAddressError; + +impl fmt::Display for NoIPAddressError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "No IP address found for device") + } +} + +impl error::Error for NoIPAddressError {} + +#[derive(Debug)] +pub struct NoMACAddressError; + +impl fmt::Display for NoMACAddressError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "No MAC address found for device") + } +} + +impl error::Error for NoMACAddressError {} + +#[derive(Debug)] +pub struct DataChannelError; + +impl fmt::Display for DataChannelError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Error creating or using network data channel") + } +} + +impl error::Error for DataChannelError {} diff --git a/backend/src/events.rs b/backend/src/events.rs index 37011e5..b60c2f5 100644 --- a/backend/src/events.rs +++ b/backend/src/events.rs @@ -1,15 +1,16 @@ +mod error; mod pushover; use std::time::Duration; -use crate::model::devices::Device; use crate::settings::get_settings; +use crate::{events::error::DeliveryError, model::devices::Device}; use chrono::Local; use duration_string::DurationString; use log::{debug, info, warn}; // Private helper function to deliver messages -fn send_message(body: String) -> Result<(), String> { +fn send_message(body: String) -> Result<(), DeliveryError> { debug!("About to send notification ({body})."); match get_settings().notifications.method.as_str() { @@ -25,12 +26,15 @@ fn send_message(body: String) -> Result<(), String> { Ok(()) } -pub fn trigger_new_device(device: Device) -> Result<(), String> { +pub fn trigger_new_device(device: Device) -> Result<(), DeliveryError> { send_message(format!("New device found in your network: {device}"))?; Ok(()) } -pub fn trigger_existing_device(existing_device: Device, new_device: Device) -> Result<(), String> { +pub fn trigger_existing_device( + existing_device: Device, + new_device: Device, +) -> Result<(), DeliveryError> { // Notify if the device comes back online after not being seen for the configured period let elapsed_since_last_seen: Duration = (Local::now().to_utc() - existing_device.last_seen) .to_std() diff --git a/backend/src/events/error.rs b/backend/src/events/error.rs new file mode 100644 index 0000000..5feb9eb --- /dev/null +++ b/backend/src/events/error.rs @@ -0,0 +1,30 @@ +use std::{error, fmt}; + +#[derive(Debug)] +pub enum DeliveryError { + ParsePushover(pushover::Error), +} + +impl fmt::Display for DeliveryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + DeliveryError::ParsePushover(..) => { + write!(f, "Error delivering notification via Pushover") + } + } + } +} + +impl error::Error for DeliveryError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + DeliveryError::ParsePushover(ref e) => Some(e), + } + } +} + +impl From for DeliveryError { + fn from(err: pushover::Error) -> DeliveryError { + DeliveryError::ParsePushover(err) + } +} diff --git a/backend/src/events/pushover.rs b/backend/src/events/pushover.rs index 4bd3935..91172e4 100644 --- a/backend/src/events/pushover.rs +++ b/backend/src/events/pushover.rs @@ -1,10 +1,10 @@ use log::{debug, error}; -use pushover::API; use pushover::requests::message::SendMessage; +use pushover::{API, Error}; use crate::settings::get_settings; -pub fn send_message(body: String) -> Result<(), String> { +pub fn send_message(body: String) -> Result<(), Error> { debug!("About to send message via pushover ({body})"); let api = API::new(); @@ -21,7 +21,7 @@ pub fn send_message(body: String) -> Result<(), String> { } Err(error) => { error!("Error sending message via pushover: {error}"); - Err(format!("Error sending message via pushover: {error}")) + Err(error) } } } diff --git a/backend/src/main.rs b/backend/src/main.rs index 36e0a10..a4bfa3d 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -24,7 +24,7 @@ struct Args { } #[tokio::main] -async fn main() -> Result<(), String> { +async fn main() -> Result<(), Box> { // Parse command line parameters and init settings // this is not thread safe so it needs to run just once let args = Args::parse(); diff --git a/backend/src/scanner.rs b/backend/src/scanner.rs index 78f922d..d51bd25 100644 --- a/backend/src/scanner.rs +++ b/backend/src/scanner.rs @@ -5,7 +5,7 @@ use crate::settings::get_settings; use log::{debug, info}; use tokio::time::{Duration, sleep}; -pub async fn scan() -> Result<(), String> { +pub async fn scan() -> Result<(), Box> { loop { // Find online devices via ARP let devices = diff --git a/backend/src/web_server.rs b/backend/src/web_server.rs index 6327670..e30b652 100644 --- a/backend/src/web_server.rs +++ b/backend/src/web_server.rs @@ -1,6 +1,8 @@ +use std::error::Error; + use axum::{Json, Router, http::StatusCode, routing::get}; use chrono::Local; -use log::{error, info}; +use log::{debug, error, info}; use tower_http::services::ServeDir; use crate::{ @@ -8,7 +10,7 @@ use crate::{ model::{devices::Device, notifications::Notification}, }; -pub async fn serve() -> Result<(), String> { +pub async fn serve() -> Result<(), Box> { info!("Starting web server"); let static_files = ServeDir::new("./web"); @@ -17,10 +19,13 @@ pub async fn serve() -> Result<(), String> { .route("/devices", get(get_devices)) .route("/notifications", get(list_notifications)) .nest_service("/web", static_files); - info!("Server running at http://0.0.0.0:3000"); + info!("Web server starting at http://0.0.0.0:3000"); // Start the server - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - axum::serve(listener, router).await.unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + + debug!("Web server bound to IP and port"); + + axum::serve(listener, router).await?; Ok(()) }