diff --git a/src/application/services/storage_usage_service.rs b/src/application/services/storage_usage_service.rs index c7337093..36ab045b 100644 --- a/src/application/services/storage_usage_service.rs +++ b/src/application/services/storage_usage_service.rs @@ -608,11 +608,11 @@ impl JobHandler for StorageUsageService { async fn run(&self, _args: &JobRunArgs) -> JobOutcome { let drives = match self.update_all_drives_storage_usage().await { Ok(n) => n, - Err(e) => return JobOutcome::Err(format!("drive reconciliation failed: {e}")), + Err(e) => return JobOutcome::err(format!("drive reconciliation failed: {e}")), }; let users = match self.update_all_users_storage_usage().await { Ok(n) => n, - Err(e) => return JobOutcome::Err(format!("user reconciliation failed: {e}")), + Err(e) => return JobOutcome::err(format!("user reconciliation failed: {e}")), }; JobOutcome::ok_with( drives + users, diff --git a/src/infrastructure/scheduler/engine.rs b/src/infrastructure/scheduler/engine.rs index 1f0f4f2b..35255d55 100644 --- a/src/infrastructure/scheduler/engine.rs +++ b/src/infrastructure/scheduler/engine.rs @@ -178,7 +178,7 @@ pub(super) async fn dispatch(name: &str, entry: Arc, args: &JobRunArgs // yield points may run to completion in the background. // We still record timeout and release the permit. ( - JobOutcome::Err(format!("wall-clock timeout of {:?} exceeded", dur)), + JobOutcome::err(format!("wall-clock timeout of {:?} exceeded", dur)), Some(ErrCause::Timeout), ) } @@ -249,12 +249,12 @@ fn translate_join( "unknown panic payload".to_string() }; ( - JobOutcome::Err(format!("handler panicked: {msg}")), + JobOutcome::err(format!("handler panicked: {msg}")), Some(ErrCause::Panicked), ) } Err(join_err) => ( - JobOutcome::Err(format!("task cancelled: {join_err}")), + JobOutcome::err(format!("task cancelled: {join_err}")), Some(ErrCause::Handler), ), } @@ -278,7 +278,7 @@ fn log_outcome(name: &str, outcome: &JobOutcome, cause: Option, elapse name, ); } - JobOutcome::Err(msg) => { + JobOutcome::Err { message: msg } => { tracing::warn!( target: "oxicloud::scheduler", event = "job.run", @@ -342,7 +342,7 @@ mod tests { let (outcome, cause) = translate_join(join.await); assert!(!outcome.is_ok()); assert_eq!(cause, Some(ErrCause::Panicked)); - if let JobOutcome::Err(msg) = outcome { + if let JobOutcome::Err { message: msg } = outcome { assert!(msg.contains("panicked"), "expected panic marker in: {msg}"); } } diff --git a/src/infrastructure/scheduler/types.rs b/src/infrastructure/scheduler/types.rs index 88c2fecb..ed56dac6 100644 --- a/src/infrastructure/scheduler/types.rs +++ b/src/infrastructure/scheduler/types.rs @@ -51,7 +51,12 @@ pub enum JobOutcome { #[serde(default, skip_serializing_if = "serde_json::Value::is_null")] extra: serde_json::Value, }, - Err(String), + /// `Err` is a struct variant (not tuple-newtype) so it composes + /// with `#[serde(tag = "outcome")]`. Serde's internal tagging + /// refuses to serialise a tuple variant wrapping a bare String + /// — the tag has nowhere to live. The struct form `{ message }` + /// lets serde emit `{"outcome":"err","message":"..."}` cleanly. + Err { message: String }, } impl JobOutcome { @@ -69,11 +74,19 @@ impl JobOutcome { JobOutcome::Ok { count, extra } } + /// Convenience constructor for `Err` — call-site ergonomics + /// match the retired tuple form. + pub fn err(message: impl Into) -> Self { + JobOutcome::Err { + message: message.into(), + } + } + /// Terse discriminant for logs / metrics: `"ok"` | `"err"`. pub fn kind(&self) -> &'static str { match self { JobOutcome::Ok { .. } => "ok", - JobOutcome::Err(_) => "err", + JobOutcome::Err { .. } => "err", } } @@ -125,7 +138,7 @@ mod tests { #[test] fn joboutcome_kind_label() { assert_eq!(JobOutcome::ok(0).kind(), "ok"); - assert_eq!(JobOutcome::Err("boom".into()).kind(), "err"); + assert_eq!(JobOutcome::err("boom").kind(), "err"); } #[test] diff --git a/src/infrastructure/services/dedup_service.rs b/src/infrastructure/services/dedup_service.rs index 17183957..77bbf417 100644 --- a/src/infrastructure/services/dedup_service.rs +++ b/src/infrastructure/services/dedup_service.rs @@ -3170,7 +3170,7 @@ impl crate::infrastructure::scheduler::JobHandler for DedupService { items, serde_json::json!({ "bytes_reclaimed": bytes, "forced": args.force }), ), - Err(e) => JobOutcome::Err(format!("dedup GC failed: {e}")), + Err(e) => JobOutcome::err(format!("dedup GC failed: {e}")), } } } diff --git a/src/infrastructure/services/grant_cleanup_service.rs b/src/infrastructure/services/grant_cleanup_service.rs index ce938d76..7dcde2aa 100644 --- a/src/infrastructure/services/grant_cleanup_service.rs +++ b/src/infrastructure/services/grant_cleanup_service.rs @@ -132,7 +132,7 @@ impl JobHandler for GrantCleanupService { "forced": args.force, }), ), - Err(e) => JobOutcome::Err(format!("grant cleanup failed: {e}")), + Err(e) => JobOutcome::err(format!("grant cleanup failed: {e}")), } } } diff --git a/src/infrastructure/services/trash_cleanup_service.rs b/src/infrastructure/services/trash_cleanup_service.rs index e008efa5..035ba992 100644 --- a/src/infrastructure/services/trash_cleanup_service.rs +++ b/src/infrastructure/services/trash_cleanup_service.rs @@ -183,7 +183,7 @@ impl JobHandler for TrashCleanupService { }), ) } - Err(e) => JobOutcome::Err(format!("trash cleanup failed: {e}")), + Err(e) => JobOutcome::err(format!("trash cleanup failed: {e}")), } } }