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 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<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;
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))
}
};
+8 -5
View File
@@ -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<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();
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))
}
}
}
+12 -4
View File
@@ -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<rusqlite::Error> for 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 error;
+9 -11
View File
@@ -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<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);
// Get the network device to use
@@ -24,7 +27,7 @@ pub async fn find(interface: String) -> Result<Vec<Device>, 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<Vec<Device>, 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<Vec<Device>, 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<Vec<Device>, 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<Vec<Device>, String> {
Channel::Ethernet(tx, rx) => (tx, rx),
_ => {
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;
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()
+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 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)
}
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ struct Args {
}
#[tokio::main]
async fn main() -> Result<(), String> {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command line parameters and init settings
// this is not thread safe so it needs to run just once
let args = Args::parse();
+1 -1
View File
@@ -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<dyn std::error::Error>> {
loop {
// Find online devices via ARP
let devices =
+10 -5
View File
@@ -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<dyn Error>> {
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(())
}