diff --git a/crates/lib/pager/src/notification/generate_deduplication_key.rs b/crates/lib/pager/src/notification/generate_deduplication_key.rs index bb910738f3..ea8a75fc42 100644 --- a/crates/lib/pager/src/notification/generate_deduplication_key.rs +++ b/crates/lib/pager/src/notification/generate_deduplication_key.rs @@ -63,6 +63,7 @@ mod tests { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } @@ -222,6 +223,7 @@ mod tests { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } diff --git a/crates/lib/pager/src/notification/notification_details.rs b/crates/lib/pager/src/notification/notification_details.rs index 09e78e610f..3e825e94b8 100644 --- a/crates/lib/pager/src/notification/notification_details.rs +++ b/crates/lib/pager/src/notification/notification_details.rs @@ -5,7 +5,7 @@ use crate::notification::generate_deduplication_key::generate_deduplication_key; use crate::notification::notification_urgency::NotificationUrgency; /// Details for a pager notification. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct NotificationDetails { /// Title or summary of the alert. pub title: String, @@ -42,6 +42,9 @@ pub struct NotificationDetails { /// Third-party identifier associated with the event, if any. pub third_party_id: Option, + + /// The error that triggered this notification, if any. + pub maybe_error: Option>, } impl NotificationDetails { @@ -50,6 +53,7 @@ impl NotificationDetails { } /// Create a notification with a title and description. + #[deprecated(note = "Use NotificationDetailsBuilder::from_title() instead")] pub fn with_title_and_description(title: String, description: String) -> Self { Self { title, @@ -64,10 +68,12 @@ impl NotificationDetails { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } /// Create a notification with just a title. + #[deprecated(note = "Use NotificationDetailsBuilder::from_title() instead")] pub fn with_title(title: String) -> Self { Self { title, @@ -82,6 +88,7 @@ impl NotificationDetails { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } @@ -92,7 +99,11 @@ impl NotificationDetails { /// - The error's source chain (if any) /// - A backtrace (if available via `std::backtrace`) /// - The timestamp of the event - pub fn from_error(error: &E) -> Self { + /// + /// Note: this does not retain the error object itself. Use + /// `NotificationDetailsBuilder::from_title().set_error()` to attach an error. + #[deprecated(note = "Use NotificationDetailsBuilder::from_title().set_error() instead")] + pub fn from_error_info(error: &E) -> Self { let title = format!("{}", error); // Truncate the title to a reasonable length for alert titles. @@ -147,12 +158,15 @@ impl NotificationDetails { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } /// Create a notification from an error, with a custom title prefix. - pub fn from_error_with_context(context: &str, error: &E) -> Self { - let mut notification = Self::from_error(error); + #[deprecated(note = "Use NotificationDetailsBuilder::from_title().set_error() instead")] + pub fn from_error_info_with_context(context: &str, error: &E) -> Self { + #[allow(deprecated)] + let mut notification = Self::from_error_info(error); notification.title = format!("{}: {}", context, notification.title); if notification.title.len() > 200 { notification.title = format!("{}...", ¬ification.title[..197]); diff --git a/crates/lib/pager/src/notification/notification_details_builder.rs b/crates/lib/pager/src/notification/notification_details_builder.rs index bcb2d7824c..428e612785 100644 --- a/crates/lib/pager/src/notification/notification_details_builder.rs +++ b/crates/lib/pager/src/notification/notification_details_builder.rs @@ -17,6 +17,7 @@ pub struct NotificationDetailsBuilder { media_file_token: Option, inference_job_token: Option, third_party_id: Option, + maybe_error: Option>, } impl NotificationDetailsBuilder { @@ -35,16 +36,20 @@ impl NotificationDetailsBuilder { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } - /// Create a builder from an error. + /// Create a builder from an error's Display/Debug info. /// - /// Sets `is_from_error` to true and derives the summary and description - /// from the error, matching the behavior of `NotificationDetails::from_error`. - pub fn from_error(error: &E) -> Self { - // TODO(bt,2026-03-30): Clean this up - let details = NotificationDetails::from_error(error); + /// Sets `is_from_error` to true and derives the title and description + /// from the error. Does NOT retain the error object itself. + /// + /// Use `from_title().set_error()` to attach the actual error. + #[deprecated(note = "Use from_title().set_error() instead")] + pub fn from_error_info(error: &E) -> Self { + #[allow(deprecated)] + let details = NotificationDetails::from_error_info(error); Self { title: details.title, description: details.description, @@ -58,9 +63,10 @@ impl NotificationDetailsBuilder { media_file_token: None, inference_job_token: None, third_party_id: None, + maybe_error: None, } } - + pub fn set_title(mut self, title: String) -> Self { self.title = title; self @@ -111,6 +117,11 @@ impl NotificationDetailsBuilder { self } + pub fn set_error(mut self, error: Option>) -> Self { + self.maybe_error = error; + self + } + pub fn build(self) -> NotificationDetails { NotificationDetails { title: self.title, @@ -125,6 +136,7 @@ impl NotificationDetailsBuilder { media_file_token: self.media_file_token, inference_job_token: self.inference_job_token, third_party_id: self.third_party_id, + maybe_error: self.maybe_error, } } } diff --git a/crates/service/job/seedance2_pro_job/src/jobs/alert_on_error.rs b/crates/service/job/seedance2_pro_job/src/jobs/alert_on_error.rs index a4654ffe86..b408a6a222 100644 --- a/crates/service/job/seedance2_pro_job/src/jobs/alert_on_error.rs +++ b/crates/service/job/seedance2_pro_job/src/jobs/alert_on_error.rs @@ -11,7 +11,7 @@ pub fn alert_pager_and_return_err( err: anyhow::Error, job: Option<&PendingSeedance2ProJob>, ) -> anyhow::Result { - let mut builder = NotificationDetailsBuilder::from_error(&err) + let mut builder = NotificationDetailsBuilder::from_error_info(&err) .set_title(title.to_string()) .set_urgency(Some(NotificationUrgency::Medium)); diff --git a/crates/service/job/seedance2_pro_job/src/jobs/process_job/process_failed_job.rs b/crates/service/job/seedance2_pro_job/src/jobs/process_job/process_failed_job.rs index ed0d0eb8f4..1641dc02e6 100644 --- a/crates/service/job/seedance2_pro_job/src/jobs/process_job/process_failed_job.rs +++ b/crates/service/job/seedance2_pro_job/src/jobs/process_job/process_failed_job.rs @@ -85,7 +85,7 @@ pub async fn process_failed_job( err, ); - let notification = NotificationDetailsBuilder::from_error(&err) + let notification = NotificationDetailsBuilder::from_error_info(&err) .set_title("Seedance2Pro refund failed".to_string()) .set_inference_job_token(Some(job.job_token.to_string())) .set_third_party_id(Some(job.order_id.to_string())) @@ -140,7 +140,7 @@ pub async fn process_failed_job( err ); - let notification = NotificationDetailsBuilder::from_error(&err) + let notification = NotificationDetailsBuilder::from_error_info(&err) .set_title("Seedance2Pro mark job failed error".to_string()) .set_urgency(Some(NotificationUrgency::Medium)) .build(); diff --git a/crates/service/job/video_thumbnail_job/src/job/alert_on_error.rs b/crates/service/job/video_thumbnail_job/src/job/alert_on_error.rs index 9dd1816b3c..2c671bc9bb 100644 --- a/crates/service/job/video_thumbnail_job/src/job/alert_on_error.rs +++ b/crates/service/job/video_thumbnail_job/src/job/alert_on_error.rs @@ -9,7 +9,7 @@ pub fn alert_pager_and_return_err( title: &str, err: anyhow::Error, ) -> anyhow::Result { - let notification = NotificationDetailsBuilder::from_error(&err) + let notification = NotificationDetailsBuilder::from_error_info(&err) .set_title(title.to_string()) .set_urgency(Some(NotificationUrgency::Medium)) .build(); diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs index 8aa94d3cd2..0e4c1b69b6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/multi_function/seedance_2p0_multi_function_video_gen_handler.rs @@ -222,7 +222,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler( Err(err) => { warn!("Regular session fallback also failed for user {:?}: {:?}", user_token, err); if let Err(page_err) = server_state.pager.enqueue_page( - NotificationDetailsBuilder::from_error(&err) + NotificationDetailsBuilder::from_error_info(&err) .set_title("Seedance 2.0 generation failed (whitelist + fallback)".to_string()) .set_urgency(Some(NotificationUrgency::High)) .build() @@ -256,7 +256,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler( Err(err) => { warn!("Error calling seedance2pro generate_video: {:?}", err); if let Err(page_err) = server_state.pager.enqueue_page( - NotificationDetailsBuilder::from_error(&err) + NotificationDetailsBuilder::from_error_info(&err) .set_title("Seedance 2.0 generation failed".to_string()) .set_urgency(Some(NotificationUrgency::High)) .build() diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs index a1aa17441c..3272658448 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs @@ -120,7 +120,7 @@ pub async fn fal_webhook_handler( if let Err(ref err) = result { error!("FAL webhook error for request_id {}: {:?}", request_id, err); - let notification = NotificationDetailsBuilder::from_error(err) + let notification = NotificationDetailsBuilder::from_error_info(err) .set_title("FAL webhook processing failed".to_string()) .set_description(Some(format!( "FAL webhook failed for request_id: {}\n\nError: {:?}\n\nWebhook JSON Payload: {}", diff --git a/crates/service/web/storyteller_web/src/threads/db_health_checker_thread/db_health_checker_thread.rs b/crates/service/web/storyteller_web/src/threads/db_health_checker_thread/db_health_checker_thread.rs index f61400205d..44958d9598 100644 --- a/crates/service/web/storyteller_web/src/threads/db_health_checker_thread/db_health_checker_thread.rs +++ b/crates/service/web/storyteller_web/src/threads/db_health_checker_thread/db_health_checker_thread.rs @@ -35,7 +35,7 @@ pub async fn db_health_checker_thread( Ok(_) => {}, } - let notification = NotificationDetailsBuilder::from_error(&database_error) + let notification = NotificationDetailsBuilder::from_error_info(&database_error) .set_title("DB health check thread failed check".to_string()) .set_urgency(Some(NotificationUrgency::High)) .build();