From 2225fd4777df3d29ca0bacc555620faca5aafd10 Mon Sep 17 00:00:00 2001 From: Geoffrey Oxberry Date: Sat, 23 May 2026 03:03:16 +0000 Subject: [PATCH] fix(lading): surface tooling/FUSE-mount panics as errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit captool's JSONL paths panicked on a failed line read or malformed JSON via `.expect()` inside a stream-map closure. Convert each closure to return `Result` and fail-fast on the first error; captool is an offline analyzer where silently skipping corrupt lines would mask the corruption being investigated. The fn-level `#[expect(clippy::expect_used)]` on `main` and `validate_capture` stays — the remaining `.expect()` sites are on `JoinHandle`s where a `JoinError` is the intentional propagation of an inner blocking-task panic. The reason text is updated accordingly so it no longer reads as tracked debt. logrotate_fs::Server::new panicked when `fuser::spawn_mount2` failed (e.g. missing /dev/fuse permissions). The function already has `#[from] std::io::Error -> Error::Io`, so the fix is just `?` on the `io::Result`; no new variant needed. The fn-level FIXME `#[expect]` is removed. Co-Authored-By: Claude Opus 4.7 --- lading/src/bin/captool/main.rs | 40 ++++++++++--------- lading/src/generator/file_gen/logrotate_fs.rs | 10 +---- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/lading/src/bin/captool/main.rs b/lading/src/bin/captool/main.rs index 2b152fd4b..9b9c9dc98 100644 --- a/lading/src/bin/captool/main.rs +++ b/lading/src/bin/captool/main.rs @@ -74,7 +74,7 @@ pub enum Error { #[expect(clippy::too_many_lines)] #[expect( clippy::expect_used, - reason = "FIXME: line read and JSON deserialization should surface as Error variants rather than panicking; tracked for follow-up. Task join panics are intentional propagation of inner panics." + reason = "Remaining .expect() sites are on tokio::task::JoinHandle; a JoinError fires only when the blocking task itself panicked, and we intentionally propagate that inner panic." )] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() @@ -137,12 +137,14 @@ async fn main() -> Result<(), Error> { }; let lines_vec: Vec = lines - .map(|l| { - let line_str = l.expect("failed to read line"); - serde_json::from_str(&line_str).expect("failed to deserialize line") + .map(|l| -> Result { + let line_str = l?; + Ok(serde_json::from_str(&line_str)?) }) - .collect() - .await; + .collect::>>() + .await + .into_iter() + .collect::, _>>()?; analyze::jsonl::list_metrics(&lines_vec) } @@ -190,12 +192,14 @@ async fn main() -> Result<(), Error> { }; let lines_vec: Vec = lines - .map(|l| { - let line_str = l.expect("failed to read line"); - serde_json::from_str(&line_str).expect("failed to deserialize line") + .map(|l| -> Result { + let line_str = l?; + Ok(serde_json::from_str(&line_str)?) }) - .collect() - .await; + .collect::>>() + .await + .into_iter() + .collect::, _>>()?; analyze::jsonl::analyze_metric(&lines_vec, metric_name) } @@ -233,7 +237,7 @@ async fn main() -> Result<(), Error> { #[expect( clippy::expect_used, - reason = "FIXME: line read and JSON deserialization should surface as Error variants rather than panicking; tracked for follow-up. Task join panics are intentional propagation of inner panics." + reason = "Remaining .expect() site is on tokio::task::JoinHandle; a JoinError fires only when the blocking task itself panicked, and we intentionally propagate that inner panic." )] async fn validate_capture(capture_path_str: &str, min_seconds: Option) -> Result<(), Error> { let capture_path = path::Path::new(capture_path_str); @@ -272,16 +276,14 @@ async fn validate_capture(capture_path_str: &str, min_seconds: Option) -> R either::Either::Left(LinesStream::new(reader.lines())) }; - let mut lines_vec = Vec::new(); - let mut lines_stream = lines.map(|l| { - let line_str = l.expect("failed to read line"); - let line: Line = - serde_json::from_str(&line_str).expect("failed to deserialize line"); - line + let mut lines_vec: Vec = Vec::new(); + let mut lines_stream = lines.map(|l| -> Result { + let line_str = l?; + Ok(serde_json::from_str(&line_str)?) }); while let Some(line) = lines_stream.next().await { - lines_vec.push(line); + lines_vec.push(line?); } jsonl::validate_lines(&lines_vec, min_seconds) diff --git a/lading/src/generator/file_gen/logrotate_fs.rs b/lading/src/generator/file_gen/logrotate_fs.rs index e59aea5be..9228ce8e9 100644 --- a/lading/src/generator/file_gen/logrotate_fs.rs +++ b/lading/src/generator/file_gen/logrotate_fs.rs @@ -167,13 +167,6 @@ impl Server { /// /// Function will error if block cache cannot be built. /// - /// # Panics - /// - /// Function will panic if the filesystem cannot be started. - #[expect( - clippy::expect_used, - reason = "FIXME: fuse_mount2 spawn failure should propagate as an Error variant rather than panic; tracked for follow-up" - )] pub fn new( _: generator::General, config: Config, @@ -242,8 +235,7 @@ impl Server { ]; // Mount the filesystem in the background - let background_session = spawn_mount2(fs, config.mount_point, &options) - .expect("Failed to mount FUSE filesystem"); + let background_session = spawn_mount2(fs, config.mount_point, &options)?; Ok(Self { shutdown,