From 8749ff33c12fe4d860fe63c606f6cf70b0d3d7f0 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Sat, 6 Jun 2026 09:03:00 -0400 Subject: [PATCH] Consolidate backend scanner status, paging, and query-param duplication Add ActiveStatusCell/PassiveStatusCell wrappers in the scanners common module so the five per-scanner status.rs files reduce to a single static; replace parse_parameter_bool/int/string with one generic parse_parameter over FromStr; extract the shared LIMIT/OFFSET paging clause into db::apply_paging; and drop a no-op for-loop in the ARP sender. Co-Authored-By: Claude Opus 4.8 --- backend/src/db.rs | 20 ++++++++ backend/src/db/device_events.rs | 10 +--- backend/src/db/devices.rs | 11 +--- backend/src/db/notifications.rs | 11 +--- backend/src/main.rs | 10 ++-- .../src/scanners/arp/packet_send_receive.rs | 50 +++++++++---------- backend/src/scanners/arp/scanner.rs | 6 +-- backend/src/scanners/arp/status.rs | 35 +------------ backend/src/scanners/common/active_status.rs | 46 +++++++++++++++++ backend/src/scanners/common/passive_status.rs | 40 +++++++++++++++ backend/src/scanners/dhcp/scanner.rs | 4 +- backend/src/scanners/dhcp/status.rs | 27 +--------- backend/src/scanners/mdns/scanner.rs | 4 +- backend/src/scanners/mdns/status.rs | 27 +--------- backend/src/scanners/snmp/scanner.rs | 6 +-- backend/src/scanners/snmp/status.rs | 35 +------------ backend/src/scanners/ssdp/scanner.rs | 4 +- backend/src/scanners/ssdp/status.rs | 27 +--------- backend/src/web_server/arp_scanner.rs | 2 +- backend/src/web_server/device_events.rs | 4 +- backend/src/web_server/devices.rs | 16 +++--- backend/src/web_server/dhcp_scanner.rs | 2 +- backend/src/web_server/mdns_scanner.rs | 2 +- backend/src/web_server/notifications.rs | 6 +-- backend/src/web_server/snmp_scanner.rs | 2 +- backend/src/web_server/ssdp_scanner.rs | 2 +- backend/src/web_server/utils.rs | 40 ++++----------- 27 files changed, 189 insertions(+), 260 deletions(-) diff --git a/backend/src/db.rs b/backend/src/db.rs index c98f09b..8293686 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -41,6 +41,26 @@ pub fn get_db_connection() -> PooledConnection { } } +// Appends the shared `LIMIT ? OFFSET ?` paging clause (and its bound parameters) to a list query +// when both an offset and a limit are supplied. Used by the list endpoints (devices, notifications, +// device_events) so they page identically. +pub fn apply_paging( + sql: &mut String, + params: &mut Vec, + page_offset: Option, + page_limit: Option, +) { + if let (Some(page_offset), Some(page_limit)) = (page_offset, page_limit) { + debug!( + "Adding paging to list with offset={} and limit={}", + page_offset, page_limit + ); + sql.push_str(" LIMIT ? OFFSET ?"); + params.push(page_limit.into()); + params.push(page_offset.into()); + } +} + pub async fn init_db() -> Result<(), DbError> { let mut initialised = INITIALISED.lock().await; if *initialised { diff --git a/backend/src/db/device_events.rs b/backend/src/db/device_events.rs index 78fffad..16048c9 100644 --- a/backend/src/db/device_events.rs +++ b/backend/src/db/device_events.rs @@ -89,15 +89,7 @@ pub fn list( sql_statement.push_str(" ORDER BY created_on DESC, id DESC"); - if let (Some(page_offset), Some(page_limit)) = (page_offset, page_limit) { - debug!( - "Adding paging with offset={} and limit={}", - page_offset, page_limit - ); - sql_statement.push_str(" LIMIT ? OFFSET ?"); - params.push(page_limit.into()); - params.push(page_offset.into()); - } + db::apply_paging(&mut sql_statement, &mut params, page_offset, page_limit); let mut stmt = conn.prepare(sql_statement.as_str())?; diff --git a/backend/src/db/devices.rs b/backend/src/db/devices.rs index b597fe8..bf83975 100644 --- a/backend/src/db/devices.rs +++ b/backend/src/db/devices.rs @@ -118,16 +118,7 @@ pub fn list_devices( )); // Paging - if let (Some(page_offset), Some(page_limit)) = (page_offset, page_limit) { - debug!( - "Adding paging to list with offset={} and limit={}", - page_offset, page_limit - ); - sql_statement.push_str("LIMIT ? OFFSET ?"); - - params.push(page_limit.into()); - params.push(page_offset.into()); - }; + db::apply_paging(&mut sql_statement, &mut params, page_offset, page_limit); let mut stmt = conn.prepare(sql_statement.as_str())?; diff --git a/backend/src/db/notifications.rs b/backend/src/db/notifications.rs index f7ae66a..1e3cdae 100644 --- a/backend/src/db/notifications.rs +++ b/backend/src/db/notifications.rs @@ -32,16 +32,7 @@ pub fn list( sql_statement.push_str(" ORDER BY created_on DESC"); // Paging - if let (Some(page_offset), Some(page_limit)) = (page_offset, page_limit) { - debug!( - "Adding paging to list with offset={} and limit={}", - page_offset, page_limit - ); - sql_statement.push_str(" LIMIT ? OFFSET ?"); - - params.push(page_limit.into()); - params.push(page_offset.into()); - }; + db::apply_paging(&mut sql_statement, &mut params, page_offset, page_limit); let mut stmt = conn.prepare(sql_statement.as_str())?; diff --git a/backend/src/main.rs b/backend/src/main.rs index 3bc4e1b..3d385e8 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -57,11 +57,11 @@ async fn main() -> Result<(), Box> { db::init_db().await?; // Initialize scanner status tracking - scanners::arp::status::init(); - scanners::mdns::status::init(); - scanners::ssdp::status::init(); - scanners::dhcp::status::init(); - scanners::snmp::status::init(); + scanners::arp::status::STATUS.init(); + scanners::mdns::status::STATUS.init(); + scanners::ssdp::status::STATUS.init(); + scanners::dhcp::status::STATUS.init(); + scanners::snmp::status::STATUS.init(); // Start the device scanners, web server, and retention cleaner in parallel tokio::join!( diff --git a/backend/src/scanners/arp/packet_send_receive.rs b/backend/src/scanners/arp/packet_send_receive.rs index eceedcd..d630a2f 100644 --- a/backend/src/scanners/arp/packet_send_receive.rs +++ b/backend/src/scanners/arp/packet_send_receive.rs @@ -25,35 +25,35 @@ pub async fn send_packet( continue; } trace!("Sending ARP packet to {}", target_ip); - for _ in 0..1 { - //arp packet - let mut arp_buf = [0u8; 28]; - let mut arp_packet = MutableArpPacket::new(&mut arp_buf).unwrap(); - arp_packet.set_hardware_type(ArpHardwareTypes::Ethernet); - arp_packet.set_protocol_type(EtherTypes::Ipv4); - arp_packet.set_hw_addr_len(6); - arp_packet.set_operation(ArpOperations::Request); - arp_packet.set_proto_addr_len(4); - arp_packet.set_sender_hw_addr(sender_macaddr); - arp_packet.set_sender_proto_addr(sender_ip.ip()); - arp_packet.set_target_hw_addr(MacAddr::zero()); - arp_packet.set_target_proto_addr(target_ip); + //arp packet + let mut arp_buf = [0u8; 28]; + let mut arp_packet = MutableArpPacket::new(&mut arp_buf).unwrap(); - //ethernet packet - let mut ethernet_buf = [0u8; 42]; - let mut ethernet_packet = MutableEthernetPacket::new(&mut ethernet_buf).unwrap(); + arp_packet.set_hardware_type(ArpHardwareTypes::Ethernet); + arp_packet.set_protocol_type(EtherTypes::Ipv4); + arp_packet.set_hw_addr_len(6); + arp_packet.set_operation(ArpOperations::Request); + arp_packet.set_proto_addr_len(4); + arp_packet.set_sender_hw_addr(sender_macaddr); + arp_packet.set_sender_proto_addr(sender_ip.ip()); + arp_packet.set_target_hw_addr(MacAddr::zero()); + arp_packet.set_target_proto_addr(target_ip); - ethernet_packet.set_destination(MacAddr::broadcast()); - ethernet_packet.set_source(sender_macaddr); - ethernet_packet.set_ethertype(EtherTypes::Arp); - ethernet_packet.set_payload(arp_packet.packet_mut()); + //ethernet packet + let mut ethernet_buf = [0u8; 42]; + let mut ethernet_packet = MutableEthernetPacket::new(&mut ethernet_buf).unwrap(); + + ethernet_packet.set_destination(MacAddr::broadcast()); + ethernet_packet.set_source(sender_macaddr); + ethernet_packet.set_ethertype(EtherTypes::Arp); + ethernet_packet.set_payload(arp_packet.packet_mut()); + + tx.send_to( + ethernet_packet.to_immutable().packet(), + Some(interface.clone()), + ); - tx.send_to( - ethernet_packet.to_immutable().packet(), - Some(interface.clone()), - ); - } count += 1; // Sleep 1 millisecond every 255 packets if (count % 255) == 0 { diff --git a/backend/src/scanners/arp/scanner.rs b/backend/src/scanners/arp/scanner.rs index 3636495..38a86ec 100644 --- a/backend/src/scanners/arp/scanner.rs +++ b/backend/src/scanners/arp/scanner.rs @@ -14,14 +14,14 @@ pub async fn scan() -> Result<(), Box> { } loop { - status::set_running(); + status::STATUS.set_running(); // Find online devices via ARP let devices = finder::find(get_settings().networking.interface.clone()).await?; info!("Done with ARP probes"); info!("Found {} online devices", devices.len()); - status::record_scan(&devices); + status::STATUS.record_scan(&devices); // Process found devices for device in devices.iter() { @@ -35,7 +35,7 @@ pub async fn scan() -> Result<(), Box> { "Scan finished. Sleeping for {}", get_settings().arp_scanner.wait_between_scans ); - status::set_waiting(next_scan_at); + status::STATUS.set_waiting(next_scan_at); sleep(wait).await; } } diff --git a/backend/src/scanners/arp/status.rs b/backend/src/scanners/arp/status.rs index 912bb6b..ad159f6 100644 --- a/backend/src/scanners/arp/status.rs +++ b/backend/src/scanners/arp/status.rs @@ -1,34 +1,3 @@ -use chrono::{DateTime, Utc}; -use once_cell::sync::OnceCell; -use std::sync::Mutex; +use crate::scanners::common::active_status::ActiveStatusCell; -use crate::model::devices::Device; -use crate::scanners::common::active_status::{ActiveSnapshot, ActiveStatus}; - -static STATUS: OnceCell> = OnceCell::new(); - -pub fn init() { - STATUS.set(Mutex::new(ActiveStatus::new())).ok(); -} - -pub fn set_running() { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_running(); - } -} - -pub fn set_waiting(next_scan_at: DateTime) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_waiting(next_scan_at); - } -} - -pub fn record_scan(devices: &[Device]) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().record_scan(devices); - } -} - -pub fn get() -> Option { - STATUS.get().map(|m| m.lock().unwrap().snapshot()) -} +pub static STATUS: ActiveStatusCell = ActiveStatusCell::new(); diff --git a/backend/src/scanners/common/active_status.rs b/backend/src/scanners/common/active_status.rs index db30ed4..7f6d92b 100644 --- a/backend/src/scanners/common/active_status.rs +++ b/backend/src/scanners/common/active_status.rs @@ -1,6 +1,8 @@ use crate::model::devices::Device; use chrono::{DateTime, Utc}; +use once_cell::sync::OnceCell; use std::collections::HashSet; +use std::sync::Mutex; /// Status state for the active (polling) scanners — ARP and SNMP. Each scanner owns its own /// `OnceCell>` and delegates to these methods (see e.g. @@ -67,6 +69,50 @@ impl ActiveStatus { } } +/// A lazily-initialised, mutex-guarded [`ActiveStatus`] owned by a single active scanner. Each +/// scanner declares one as a `static` and the API layer reads it back via [`get`](Self::get). +/// All methods are no-ops until [`init`](Self::init) is called (mirroring the previous +/// per-scanner `OnceCell` behaviour). +pub struct ActiveStatusCell(OnceCell>); + +impl ActiveStatusCell { + pub const fn new() -> Self { + Self(OnceCell::new()) + } + + pub fn init(&self) { + self.0.set(Mutex::new(ActiveStatus::new())).ok(); + } + + pub fn set_running(&self) { + if let Some(m) = self.0.get() { + m.lock().unwrap().set_running(); + } + } + + pub fn set_waiting(&self, next_scan_at: DateTime) { + if let Some(m) = self.0.get() { + m.lock().unwrap().set_waiting(next_scan_at); + } + } + + pub fn record_scan(&self, devices: &[Device]) { + if let Some(m) = self.0.get() { + m.lock().unwrap().record_scan(devices); + } + } + + pub fn get(&self) -> Option { + self.0.get().map(|m| m.lock().unwrap().snapshot()) + } +} + +impl Default for ActiveStatusCell { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/backend/src/scanners/common/passive_status.rs b/backend/src/scanners/common/passive_status.rs index 9e235d4..dc7cbc4 100644 --- a/backend/src/scanners/common/passive_status.rs +++ b/backend/src/scanners/common/passive_status.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; +use std::sync::Mutex; use chrono::{DateTime, Duration, Utc}; +use once_cell::sync::OnceCell; /// A device counts as "seen" only if its most recent sighting falls within this rolling window. const RECENT_WINDOW_SECONDS: i64 = 3600; @@ -58,6 +60,44 @@ impl PassiveStatus { } } +/// A lazily-initialised, mutex-guarded [`PassiveStatus`] owned by a single passive scanner. Each +/// scanner declares one as a `static` and the API layer reads it back via [`get`](Self::get). +/// All methods are no-ops until [`init`](Self::init) is called (mirroring the previous +/// per-scanner `OnceCell` behaviour). +pub struct PassiveStatusCell(OnceCell>); + +impl PassiveStatusCell { + pub const fn new() -> Self { + Self(OnceCell::new()) + } + + pub fn init(&self) { + self.0.set(Mutex::new(PassiveStatus::new())).ok(); + } + + pub fn set_listening(&self) { + if let Some(m) = self.0.get() { + m.lock().unwrap().set_listening(); + } + } + + pub fn record_discovery(&self, mac: &str) { + if let Some(m) = self.0.get() { + m.lock().unwrap().record_discovery(mac); + } + } + + pub fn get(&self) -> Option { + self.0.get().map(|m| m.lock().unwrap().snapshot()) + } +} + +impl Default for PassiveStatusCell { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/backend/src/scanners/dhcp/scanner.rs b/backend/src/scanners/dhcp/scanner.rs index d55204e..4db8067 100644 --- a/backend/src/scanners/dhcp/scanner.rs +++ b/backend/src/scanners/dhcp/scanner.rs @@ -18,7 +18,7 @@ pub async fn listen() -> Result<(), Box> { } let socket = finder::open_socket()?; - status::set_listening(); + status::STATUS.set_listening(); info!("DHCP scanner listening for client requests"); let mut buf = [0u8; 1500]; @@ -54,5 +54,5 @@ fn process_discovery(discovery: finder::DhcpDiscovery) { let device = build_device(mac.clone(), ipv4, &[], discovery.hostname); pipeline::record_sighting(device, DeviceEventScanner::Dhcp); - status::record_discovery(&mac); + status::STATUS.record_discovery(&mac); } diff --git a/backend/src/scanners/dhcp/status.rs b/backend/src/scanners/dhcp/status.rs index f4dc1f5..eb17c3a 100644 --- a/backend/src/scanners/dhcp/status.rs +++ b/backend/src/scanners/dhcp/status.rs @@ -1,26 +1,3 @@ -use once_cell::sync::OnceCell; -use std::sync::Mutex; +use crate::scanners::common::passive_status::PassiveStatusCell; -use crate::scanners::common::passive_status::{PassiveSnapshot, PassiveStatus}; - -static STATUS: OnceCell> = OnceCell::new(); - -pub fn init() { - STATUS.set(Mutex::new(PassiveStatus::new())).ok(); -} - -pub fn set_listening() { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_listening(); - } -} - -pub fn record_discovery(mac: &str) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().record_discovery(mac); - } -} - -pub fn get() -> Option { - STATUS.get().map(|m| m.lock().unwrap().snapshot()) -} +pub static STATUS: PassiveStatusCell = PassiveStatusCell::new(); diff --git a/backend/src/scanners/mdns/scanner.rs b/backend/src/scanners/mdns/scanner.rs index 00e6ad1..4da0200 100644 --- a/backend/src/scanners/mdns/scanner.rs +++ b/backend/src/scanners/mdns/scanner.rs @@ -20,7 +20,7 @@ pub async fn listen() -> Result<(), Box> { let interface = get_settings().networking.interface.clone(); let socket = finder::open_socket(interface.clone())?; - status::set_listening(); + status::STATUS.set_listening(); info!("mDNS scanner listening for announcements"); let probe_timeout = Duration::from(get_settings().mdns_scanner.probe_timeout); @@ -83,5 +83,5 @@ async fn process_announcement( ); pipeline::record_sighting(device, DeviceEventScanner::Mdns); - status::record_discovery(&mac); + status::STATUS.record_discovery(&mac); } diff --git a/backend/src/scanners/mdns/status.rs b/backend/src/scanners/mdns/status.rs index f4dc1f5..eb17c3a 100644 --- a/backend/src/scanners/mdns/status.rs +++ b/backend/src/scanners/mdns/status.rs @@ -1,26 +1,3 @@ -use once_cell::sync::OnceCell; -use std::sync::Mutex; +use crate::scanners::common::passive_status::PassiveStatusCell; -use crate::scanners::common::passive_status::{PassiveSnapshot, PassiveStatus}; - -static STATUS: OnceCell> = OnceCell::new(); - -pub fn init() { - STATUS.set(Mutex::new(PassiveStatus::new())).ok(); -} - -pub fn set_listening() { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_listening(); - } -} - -pub fn record_discovery(mac: &str) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().record_discovery(mac); - } -} - -pub fn get() -> Option { - STATUS.get().map(|m| m.lock().unwrap().snapshot()) -} +pub static STATUS: PassiveStatusCell = PassiveStatusCell::new(); diff --git a/backend/src/scanners/snmp/scanner.rs b/backend/src/scanners/snmp/scanner.rs index eb657d7..c809a95 100644 --- a/backend/src/scanners/snmp/scanner.rs +++ b/backend/src/scanners/snmp/scanner.rs @@ -25,14 +25,14 @@ pub async fn scan() -> Result<(), Box> { info!("SNMP scanner polling agent at {}", config.target); loop { - status::set_running(); + status::STATUS.set_running(); // A failed poll (unreachable agent, timeout, bad community) must not stop the loop; // log it and try again next cycle. match finder::find(config).await { Ok(devices) => { info!("SNMP poll found {} devices in the ARP cache", devices.len()); - status::record_scan(&devices); + status::STATUS.record_scan(&devices); for device in devices.iter() { pipeline::record_sighting(device.clone(), DeviceEventScanner::Snmp); } @@ -46,7 +46,7 @@ pub async fn scan() -> Result<(), Box> { "SNMP scan finished. Sleeping for {}", config.wait_between_scans ); - status::set_waiting(next_scan_at); + status::STATUS.set_waiting(next_scan_at); sleep(wait).await; } } diff --git a/backend/src/scanners/snmp/status.rs b/backend/src/scanners/snmp/status.rs index 912bb6b..ad159f6 100644 --- a/backend/src/scanners/snmp/status.rs +++ b/backend/src/scanners/snmp/status.rs @@ -1,34 +1,3 @@ -use chrono::{DateTime, Utc}; -use once_cell::sync::OnceCell; -use std::sync::Mutex; +use crate::scanners::common::active_status::ActiveStatusCell; -use crate::model::devices::Device; -use crate::scanners::common::active_status::{ActiveSnapshot, ActiveStatus}; - -static STATUS: OnceCell> = OnceCell::new(); - -pub fn init() { - STATUS.set(Mutex::new(ActiveStatus::new())).ok(); -} - -pub fn set_running() { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_running(); - } -} - -pub fn set_waiting(next_scan_at: DateTime) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_waiting(next_scan_at); - } -} - -pub fn record_scan(devices: &[Device]) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().record_scan(devices); - } -} - -pub fn get() -> Option { - STATUS.get().map(|m| m.lock().unwrap().snapshot()) -} +pub static STATUS: ActiveStatusCell = ActiveStatusCell::new(); diff --git a/backend/src/scanners/ssdp/scanner.rs b/backend/src/scanners/ssdp/scanner.rs index c89a8cc..e794ce3 100644 --- a/backend/src/scanners/ssdp/scanner.rs +++ b/backend/src/scanners/ssdp/scanner.rs @@ -20,7 +20,7 @@ pub async fn listen() -> Result<(), Box> { let interface = get_settings().networking.interface.clone(); let socket = finder::open_socket(interface.clone())?; - status::set_listening(); + status::STATUS.set_listening(); info!("SSDP scanner listening for announcements"); let probe_timeout = Duration::from(get_settings().ssdp_scanner.probe_timeout); @@ -77,5 +77,5 @@ async fn process_announcement( let device = build_device(mac.clone(), src_ip.to_string(), &device_types, server_hint); pipeline::record_sighting(device, DeviceEventScanner::Ssdp); - status::record_discovery(&mac); + status::STATUS.record_discovery(&mac); } diff --git a/backend/src/scanners/ssdp/status.rs b/backend/src/scanners/ssdp/status.rs index f4dc1f5..eb17c3a 100644 --- a/backend/src/scanners/ssdp/status.rs +++ b/backend/src/scanners/ssdp/status.rs @@ -1,26 +1,3 @@ -use once_cell::sync::OnceCell; -use std::sync::Mutex; +use crate::scanners::common::passive_status::PassiveStatusCell; -use crate::scanners::common::passive_status::{PassiveSnapshot, PassiveStatus}; - -static STATUS: OnceCell> = OnceCell::new(); - -pub fn init() { - STATUS.set(Mutex::new(PassiveStatus::new())).ok(); -} - -pub fn set_listening() { - if let Some(m) = STATUS.get() { - m.lock().unwrap().set_listening(); - } -} - -pub fn record_discovery(mac: &str) { - if let Some(m) = STATUS.get() { - m.lock().unwrap().record_discovery(mac); - } -} - -pub fn get() -> Option { - STATUS.get().map(|m| m.lock().unwrap().snapshot()) -} +pub static STATUS: PassiveStatusCell = PassiveStatusCell::new(); diff --git a/backend/src/web_server/arp_scanner.rs b/backend/src/web_server/arp_scanner.rs index d2c3e3b..c68e68f 100644 --- a/backend/src/web_server/arp_scanner.rs +++ b/backend/src/web_server/arp_scanner.rs @@ -13,5 +13,5 @@ use crate::web_server::scanner_status::{ActiveScannerStatusResponse, active_resp security(("bearer_auth" = [])) )] pub async fn status() -> Result, StatusCode> { - active_response(crate::scanners::arp::status::get()).map(Json) + active_response(crate::scanners::arp::status::STATUS.get()).map(Json) } diff --git a/backend/src/web_server/device_events.rs b/backend/src/web_server/device_events.rs index e423595..d8671b3 100644 --- a/backend/src/web_server/device_events.rs +++ b/backend/src/web_server/device_events.rs @@ -30,8 +30,8 @@ pub async fn list( Query(params): Query>, ) -> Result>, StatusCode> { let created_from = utils::parse_parameter_date(¶ms, "created_from"); - let page_offset: Option = utils::parse_parameter_int(¶ms, "page_offset"); - let page_limit: Option = utils::parse_parameter_int(¶ms, "page_limit"); + let page_offset: Option = utils::parse_parameter(¶ms, "page_offset"); + let page_limit: Option = utils::parse_parameter(¶ms, "page_limit"); match db::device_events::list(Some(mac_address), created_from, page_offset, page_limit) { Ok(value) => Ok(Json(value)), diff --git a/backend/src/web_server/devices.rs b/backend/src/web_server/devices.rs index e842f75..e96c3c7 100644 --- a/backend/src/web_server/devices.rs +++ b/backend/src/web_server/devices.rs @@ -40,17 +40,17 @@ use crate::web_server::utils; pub async fn list( Query(params): Query>, ) -> Result, StatusCode> { - let is_registered: Option = utils::parse_parameter_bool(¶ms, "is_registered"); + let is_registered: Option = utils::parse_parameter(¶ms, "is_registered"); let last_seen_from: Option> = utils::parse_parameter_date(¶ms, "last_seen_from"); let last_seen_to: Option> = utils::parse_parameter_date(¶ms, "last_seen_to"); - let owner: Option = utils::parse_parameter_string(¶ms, "owner"); - let device_type: Option = utils::parse_parameter_string(¶ms, "device_type"); - let vendor: Option = utils::parse_parameter_string(¶ms, "vendor"); - let sort_by: Option = utils::parse_parameter_string(¶ms, "sort_by"); - let sort_order: Option = utils::parse_parameter_string(¶ms, "sort_order"); - let page_offset: Option = utils::parse_parameter_int(¶ms, "page_offset"); - let page_limit: Option = utils::parse_parameter_int(¶ms, "page_limit"); + let owner: Option = utils::parse_parameter(¶ms, "owner"); + let device_type: Option = utils::parse_parameter(¶ms, "device_type"); + let vendor: Option = utils::parse_parameter(¶ms, "vendor"); + let sort_by: Option = utils::parse_parameter(¶ms, "sort_by"); + let sort_order: Option = utils::parse_parameter(¶ms, "sort_order"); + let page_offset: Option = utils::parse_parameter(¶ms, "page_offset"); + let page_limit: Option = utils::parse_parameter(¶ms, "page_limit"); let items = match db::devices::list_devices( is_registered, diff --git a/backend/src/web_server/dhcp_scanner.rs b/backend/src/web_server/dhcp_scanner.rs index 8b7071f..7be317a 100644 --- a/backend/src/web_server/dhcp_scanner.rs +++ b/backend/src/web_server/dhcp_scanner.rs @@ -13,5 +13,5 @@ use crate::web_server::scanner_status::{PassiveScannerStatusResponse, passive_re security(("bearer_auth" = [])) )] pub async fn status() -> Result, StatusCode> { - passive_response(crate::scanners::dhcp::status::get()).map(Json) + passive_response(crate::scanners::dhcp::status::STATUS.get()).map(Json) } diff --git a/backend/src/web_server/mdns_scanner.rs b/backend/src/web_server/mdns_scanner.rs index bfcf62f..da5c27e 100644 --- a/backend/src/web_server/mdns_scanner.rs +++ b/backend/src/web_server/mdns_scanner.rs @@ -13,5 +13,5 @@ use crate::web_server::scanner_status::{PassiveScannerStatusResponse, passive_re security(("bearer_auth" = [])) )] pub async fn status() -> Result, StatusCode> { - passive_response(crate::scanners::mdns::status::get()).map(Json) + passive_response(crate::scanners::mdns::status::STATUS.get()).map(Json) } diff --git a/backend/src/web_server/notifications.rs b/backend/src/web_server/notifications.rs index 78514b7..6a63738 100644 --- a/backend/src/web_server/notifications.rs +++ b/backend/src/web_server/notifications.rs @@ -130,9 +130,9 @@ pub async fn mark_all_as_old() -> impl IntoResponse { pub async fn list( Query(params): Query>, ) -> Result, StatusCode> { - let is_new: Option = utils::parse_parameter_bool(¶ms, "is_new"); - let page_offset: Option = utils::parse_parameter_int(¶ms, "page_offset"); - let page_limit: Option = utils::parse_parameter_int(¶ms, "page_limit"); + let is_new: Option = utils::parse_parameter(¶ms, "is_new"); + let page_offset: Option = utils::parse_parameter(¶ms, "page_offset"); + let page_limit: Option = utils::parse_parameter(¶ms, "page_limit"); let items = match db::notifications::list(is_new, page_offset, page_limit) { Ok(value) => value, diff --git a/backend/src/web_server/snmp_scanner.rs b/backend/src/web_server/snmp_scanner.rs index 90fd5bd..50e8c78 100644 --- a/backend/src/web_server/snmp_scanner.rs +++ b/backend/src/web_server/snmp_scanner.rs @@ -13,5 +13,5 @@ use crate::web_server::scanner_status::{ActiveScannerStatusResponse, active_resp security(("bearer_auth" = [])) )] pub async fn status() -> Result, StatusCode> { - active_response(crate::scanners::snmp::status::get()).map(Json) + active_response(crate::scanners::snmp::status::STATUS.get()).map(Json) } diff --git a/backend/src/web_server/ssdp_scanner.rs b/backend/src/web_server/ssdp_scanner.rs index 9150521..cdf413c 100644 --- a/backend/src/web_server/ssdp_scanner.rs +++ b/backend/src/web_server/ssdp_scanner.rs @@ -13,5 +13,5 @@ use crate::web_server::scanner_status::{PassiveScannerStatusResponse, passive_re security(("bearer_auth" = [])) )] pub async fn status() -> Result, StatusCode> { - passive_response(crate::scanners::ssdp::status::get()).map(Json) + passive_response(crate::scanners::ssdp::status::STATUS.get()).map(Json) } diff --git a/backend/src/web_server/utils.rs b/backend/src/web_server/utils.rs index 2644df1..f4c2414 100644 --- a/backend/src/web_server/utils.rs +++ b/backend/src/web_server/utils.rs @@ -1,41 +1,21 @@ use std::collections::HashMap; +use std::str::FromStr; use chrono::{DateTime, Utc}; use log::{debug, warn}; -pub fn parse_parameter_bool(params: &HashMap, name: &str) -> Option { - if params.contains_key(name) { - let param_value = params.get(name).unwrap().as_str(); - debug!("Found parameter {name} with value {}", param_value); - param_value.parse::().ok() - } else { - None - } -} - -pub fn parse_parameter_int(params: &HashMap, name: &str) -> Option { - if params.contains_key(name) { - let param_value = params.get(name).unwrap().as_str(); - debug!("Found parameter {name} with value {}", param_value); - param_value.parse::().ok() - } else { - None - } -} - -pub fn parse_parameter_string(params: &HashMap, name: &str) -> Option { - if params.contains_key(name) { - let param_value = params.get(name).unwrap().as_str(); - debug!("Found parameter {name} with value {}", param_value); - Some(param_value.to_string()) - } else { - None - } +// Parses a query parameter into any `FromStr` type (e.g. bool, i64, String). Returns `None` when +// the parameter is absent or fails to parse. The target type is inferred from the call site, so +// callers annotate the binding: `let limit: Option = parse_parameter(¶ms, "page_limit");` +pub fn parse_parameter(params: &HashMap, name: &str) -> Option { + let param_value = params.get(name)?; + debug!("Found parameter {name} with value {param_value}"); + param_value.parse::().ok() } pub fn parse_parameter_date(params: &HashMap, name: &str) -> Option> { - if params.contains_key(name) { - let mut param_value = params.get(name).unwrap().to_ascii_uppercase(); + if let Some(value) = params.get(name) { + let mut param_value = value.to_ascii_uppercase(); if !param_value.ends_with("Z") { param_value.push('Z'); }