Refactored errors from String to custom error types

This commit is contained in:
rzuasti
2026-02-23 09:57:22 -05:00
parent 851a9419dc
commit 47788a6738
12 changed files with 131 additions and 37 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ use r2d2_sqlite::SqliteConnectionManager;
use rusqlite_migration::Migrations; use rusqlite_migration::Migrations;
use tokio::sync::Mutex; 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 MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/database_migrations");
static INITIALISED: Mutex<bool> = Mutex::const_new(false); static INITIALISED: Mutex<bool> = Mutex::const_new(false);
@@ -40,7 +40,7 @@ pub fn get_db_connection() -> PooledConnection<SqliteConnectionManager> {
} }
} }
pub async fn init_db() -> Result<(), String> { pub async fn init_db() -> Result<(), DbError> {
let mut initialised = INITIALISED.lock().await; let mut initialised = INITIALISED.lock().await;
if *initialised { if *initialised {
return Ok(()); return Ok(());
@@ -59,7 +59,7 @@ pub async fn init_db() -> Result<(), String> {
} }
Err(error) => { Err(error) => {
error!("Error updating database: {error}"); error!("Error updating database: {error}");
Err(format!("Error updating database: {error}")) Err(DbError::from(error))
} }
}; };
+8 -5
View File
@@ -1,7 +1,10 @@
use log::{debug, error}; use log::{debug, error};
use rusqlite::params; use rusqlite::params;
use crate::{db, model::devices::Device}; use crate::{
db::{self, error::DbError},
model::devices::Device,
};
// Read device from its MAC address // Read device from its MAC address
pub fn read(mac_address: String) -> Option<Device> { pub fn read(mac_address: String) -> Option<Device> {
@@ -34,7 +37,7 @@ pub fn read(mac_address: String) -> Option<Device> {
} }
} }
pub fn insert(device: Device) -> Result<(), String> { pub fn insert(device: Device) -> Result<(), DbError> {
let conn = db::get_db_connection(); let conn = db::get_db_connection();
match conn.execute( match conn.execute(
@@ -46,12 +49,12 @@ pub fn insert(device: Device) -> Result<(), String> {
}, },
Err(error) => { Err(error) => {
error!("Error inserting device ({device}) into database: {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(); let conn = db::get_db_connection();
match conn.execute( match conn.execute(
"UPDATE devices SET ipv4_address=?1, vendor=?2, last_seen=?3 WHERE mac_address=?4", "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) => { Err(error) => {
error!("Error updating device ({device}) in database: {error}"); error!("Error updating device ({device}) in database: {error}");
Err(format!("Error updating device ({device}) in database: {error}").to_string()) Err(DbError::from(error))
} }
} }
} }
+12 -4
View File
@@ -2,13 +2,15 @@ use std::{error, fmt};
#[derive(Debug)] #[derive(Debug)]
pub enum DbError { pub enum DbError {
Parse(rusqlite::Error), ParseRusqlite(rusqlite::Error),
ParseRusqliteMigration(rusqlite_migration::Error),
} }
impl fmt::Display for DbError { impl fmt::Display for DbError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { 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 { impl error::Error for DbError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> { fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self { match *self {
DbError::Parse(ref e) => Some(e), DbError::ParseRusqlite(ref e) => Some(e),
DbError::ParseRusqliteMigration(ref e) => Some(e),
} }
} }
} }
impl From<rusqlite::Error> for DbError { impl From<rusqlite::Error> for DbError {
fn from(err: rusqlite::Error) -> DbError { fn from(err: rusqlite::Error) -> DbError {
DbError::Parse(err) DbError::ParseRusqlite(err)
}
}
impl From<rusqlite_migration::Error> for DbError {
fn from(err: rusqlite_migration::Error) -> DbError {
DbError::ParseRusqliteMigration(err)
} }
} }
+1
View File
@@ -1 +1,2 @@
pub mod arp; pub mod arp;
pub mod error;
+9 -11
View File
@@ -1,5 +1,8 @@
mod packet_send_receive; mod packet_send_receive;
use crate::device_finders::error::{
DataChannelError, InvalidDeviceError, NoIPAddressError, NoMACAddressError,
};
use crate::model::devices::Device; use crate::model::devices::Device;
use crate::settings::get_settings; use crate::settings::get_settings;
use duration_string::DurationString; use duration_string::DurationString;
@@ -11,7 +14,7 @@ use pnet::{
}; };
use tokio::time::{Duration, timeout}; use tokio::time::{Duration, timeout};
pub async fn find(interface: String) -> Result<Vec<Device>, String> { pub async fn find(interface: String) -> Result<Vec<Device>, Box<dyn std::error::Error>> {
debug!("Looking up devices via ARP using interface {}", interface); debug!("Looking up devices via ARP using interface {}", interface);
// Get the network device to use // Get the network device to use
@@ -24,7 +27,7 @@ pub async fn find(interface: String) -> Result<Vec<Device>, String> {
Some(value) => value.clone(), Some(value) => value.clone(),
None => { None => {
error!("Interface ({interface}) not found or not active."); 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<Vec<Device>, String> {
Some(value) => value, Some(value) => value,
None => { None => {
error!("No IP address found for selected interface ({interface})."); error!("No IP address found for selected interface ({interface}).");
return Err( return Err(NoIPAddressError.into());
format!("No IP address found for selected interface ({interface}).").to_string(),
);
} }
}; };
@@ -52,10 +53,7 @@ pub async fn find(interface: String) -> Result<Vec<Device>, String> {
Some(mac) => mac, Some(mac) => mac,
None => { None => {
error!("Could not get MAC address for selected interface ({interface})."); error!("Could not get MAC address for selected interface ({interface}).");
return Err( return Err(NoMACAddressError.into());
format!("Could not get MAC address for selected interface ({interface}).")
.to_string(),
);
} }
}; };
@@ -68,7 +66,7 @@ pub async fn find(interface: String) -> Result<Vec<Device>, String> {
Ok(value) => value, Ok(value) => value,
Err(error) => { Err(error) => {
error!("Could not create data channel: {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<Vec<Device>, String> {
Channel::Ethernet(tx, rx) => (tx, rx), Channel::Ethernet(tx, rx) => (tx, rx),
_ => { _ => {
error!("Unsupported data channel type"); error!("Unsupported data channel type");
return Err("Unsupported data channel type".to_string()); return Err(DataChannelError.into());
} }
}; };
+45
View File
@@ -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 {}
+8 -4
View File
@@ -1,15 +1,16 @@
mod error;
mod pushover; mod pushover;
use std::time::Duration; use std::time::Duration;
use crate::model::devices::Device;
use crate::settings::get_settings; use crate::settings::get_settings;
use crate::{events::error::DeliveryError, model::devices::Device};
use chrono::Local; use chrono::Local;
use duration_string::DurationString; use duration_string::DurationString;
use log::{debug, info, warn}; use log::{debug, info, warn};
// Private helper function to deliver messages // 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})."); debug!("About to send notification ({body}).");
match get_settings().notifications.method.as_str() { match get_settings().notifications.method.as_str() {
@@ -25,12 +26,15 @@ fn send_message(body: String) -> Result<(), String> {
Ok(()) 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}"))?; send_message(format!("New device found in your network: {device}"))?;
Ok(()) 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 // 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) let elapsed_since_last_seen: Duration = (Local::now().to_utc() - existing_device.last_seen)
.to_std() .to_std()
+30
View File
@@ -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<pushover::Error> for DeliveryError {
fn from(err: pushover::Error) -> DeliveryError {
DeliveryError::ParsePushover(err)
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
use log::{debug, error}; use log::{debug, error};
use pushover::API;
use pushover::requests::message::SendMessage; use pushover::requests::message::SendMessage;
use pushover::{API, Error};
use crate::settings::get_settings; 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})"); debug!("About to send message via pushover ({body})");
let api = API::new(); let api = API::new();
@@ -21,7 +21,7 @@ pub fn send_message(body: String) -> Result<(), String> {
} }
Err(error) => { Err(error) => {
error!("Error sending message via pushover: {error}"); error!("Error sending message via pushover: {error}");
Err(format!("Error sending message via pushover: {error}")) Err(error)
} }
} }
} }
+1 -1
View File
@@ -24,7 +24,7 @@ struct Args {
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), String> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command line parameters and init settings // Parse command line parameters and init settings
// this is not thread safe so it needs to run just once // this is not thread safe so it needs to run just once
let args = Args::parse(); let args = Args::parse();
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::settings::get_settings;
use log::{debug, info}; use log::{debug, info};
use tokio::time::{Duration, sleep}; use tokio::time::{Duration, sleep};
pub async fn scan() -> Result<(), String> { pub async fn scan() -> Result<(), Box<dyn std::error::Error>> {
loop { loop {
// Find online devices via ARP // Find online devices via ARP
let devices = let devices =
+10 -5
View File
@@ -1,6 +1,8 @@
use std::error::Error;
use axum::{Json, Router, http::StatusCode, routing::get}; use axum::{Json, Router, http::StatusCode, routing::get};
use chrono::Local; use chrono::Local;
use log::{error, info}; use log::{debug, error, info};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use crate::{ use crate::{
@@ -8,7 +10,7 @@ use crate::{
model::{devices::Device, notifications::Notification}, model::{devices::Device, notifications::Notification},
}; };
pub async fn serve() -> Result<(), String> { pub async fn serve() -> Result<(), Box<dyn Error>> {
info!("Starting web server"); info!("Starting web server");
let static_files = ServeDir::new("./web"); let static_files = ServeDir::new("./web");
@@ -17,10 +19,13 @@ pub async fn serve() -> Result<(), String> {
.route("/devices", get(get_devices)) .route("/devices", get(get_devices))
.route("/notifications", get(list_notifications)) .route("/notifications", get(list_notifications))
.nest_service("/web", static_files); .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 // Start the server
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, router).await.unwrap();
debug!("Web server bound to IP and port");
axum::serve(listener, router).await?;
Ok(()) Ok(())
} }