From 5770534fc33481ab4ea8340e231c361c79ae79d0 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 12 Apr 2026 18:40:00 -0400 Subject: [PATCH] error messages on failure to download (#1349) --- .../characters/create_character_handler.rs | 2 +- ...ce_2p0_multi_function_video_gen_handler.rs | 2 +- .../process_success/process_image_payload.rs | 4 +- .../process_success/process_images_payload.rs | 6 +- .../process_model_glb_payload.rs | 2 +- .../process_model_mesh_payload.rs | 4 +- .../process_success/process_video_payload.rs | 4 +- .../src/util/http_download_url_to_bytes.rs | 55 ++++++++++++++++++- 8 files changed, 66 insertions(+), 13 deletions(-) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/create_character_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/create_character_handler.rs index 8444910453..b8933dc69d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/characters/create_character_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/characters/create_character_handler.rs @@ -239,7 +239,7 @@ async fn upload_to_kinovi( .await .map_err(|err| { warn!("Error downloading media file from CDN: {:?}", err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })? .to_vec(); 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 6a5858a055..fba9a65f85 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 @@ -683,7 +683,7 @@ async fn upload_to_seedance2pro( .await .map_err(|err| { warn!("Error downloading media file from CDN: {:?}", err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })? .to_vec(); diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_image_payload.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_image_payload.rs index a0b3b3ee63..a7d0e3f637 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_image_payload.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_image_payload.rs @@ -35,7 +35,7 @@ pub async fn process_image_payload( .await .map_err(|err| { warn!("Failed to download image from {}: {:?}", image_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; if file_bytes.len() <= 10 { @@ -48,7 +48,7 @@ pub async fn process_image_payload( .await .map_err(|err| { warn!("Failed to download image on retry from {}: {:?}", image_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_images_payload.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_images_payload.rs index f0c689faac..78412f0125 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_images_payload.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_images_payload.rs @@ -35,7 +35,9 @@ pub async fn process_images_payload( maybe_batch_token = Some(BatchGenerationToken::generate()); } - for image in images_data { + for (i, image) in images_data.iter().enumerate() { + info!("Uploading image {} of {}: {:?}", i + 1, images_data.len(), image.url); + let media_token = upload_image(job, server_state, image, maybe_batch_token.as_ref()).await?; if maybe_media_token.is_none() { @@ -83,6 +85,8 @@ async fn upload_image( })?; } + info!("Resolving metadata..."); + // Resolve mime type: magic bytes first, fal content_type as fallback. let metadata = resolve_file_metadata(&file_bytes, image.content_type.as_deref()) .ok_or_else(|| { diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_glb_payload.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_glb_payload.rs index 2d668c8846..aba0e58fb4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_glb_payload.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_glb_payload.rs @@ -72,7 +72,7 @@ pub async fn process_model_glb_payload( .await .map_err(|err| { warn!("Failed to download mesh from {}: {:?}", mesh_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; if file_bytes.len() <= 10 { diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_mesh_payload.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_mesh_payload.rs index 482c8f3ad3..efb18b9a02 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_mesh_payload.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_model_mesh_payload.rs @@ -33,7 +33,7 @@ pub async fn process_model_mesh_payload( .await .map_err(|err| { warn!("Failed to download mesh from {}: {:?}", mesh_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; if file_bytes.len() <= 10 { @@ -46,7 +46,7 @@ pub async fn process_model_mesh_payload( .await .map_err(|err| { warn!("Failed to download mesh on retry from {}: {:?}", mesh_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_video_payload.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_video_payload.rs index f5dc1249f8..2ea2987639 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_video_payload.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/process_success/process_video_payload.rs @@ -37,7 +37,7 @@ pub async fn process_video_payload( .await .map_err(|err| { warn!("Failed to download video from {}: {:?}", video_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; if file_bytes.len() <= 10 { @@ -50,7 +50,7 @@ pub async fn process_video_payload( .await .map_err(|err| { warn!("Failed to download video on retry from {}: {:?}", video_url, err); - AdvancedCommonWebError::from_anyhow_error(err) + AdvancedCommonWebError::from_error(err) })?; } diff --git a/crates/service/web/storyteller_web/src/util/http_download_url_to_bytes.rs b/crates/service/web/storyteller_web/src/util/http_download_url_to_bytes.rs index ce0446db9f..8e86553f57 100644 --- a/crates/service/web/storyteller_web/src/util/http_download_url_to_bytes.rs +++ b/crates/service/web/storyteller_web/src/util/http_download_url_to_bytes.rs @@ -1,13 +1,25 @@ use bytes::Bytes; -use errors::AnyhowResult; -use wreq::Client; +use log::warn; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use wreq::{Client, StatusCode}; const USER_AGENT: &str = "storyteller-web/1.0"; // TODO(bt, 2025-06-03): Don't load the entire file into memory! +#[derive(Debug)] +pub enum DownloadFileToBytesError { + WreqError(wreq::Error), + BadResponseError { + status_code: StatusCode, + body: Bytes, + url: String, + } +} + /// Downloads a (binary) file to memory. Good for images, etc. Not great for large files. -pub async fn http_download_url_to_bytes(url: &str) -> AnyhowResult { +pub async fn http_download_url_to_bytes(url: &str) -> Result { let client = Client::builder() .gzip(true) .build()?; @@ -18,7 +30,44 @@ pub async fn http_download_url_to_bytes(url: &str) -> AnyhowResult { .send() .await?; + let status = response.status(); let bytes = response.bytes().await?; + if !status.is_success() { + warn!("Error downloading URL (status={}): {:?}", status.as_u16(), url); + + return Err(DownloadFileToBytesError::BadResponseError { + status_code: status, + body: bytes, + url: url.to_string(), + }); + } + Ok(bytes) } + +impl Error for DownloadFileToBytesError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + DownloadFileToBytesError::WreqError(e) => Some(e), + DownloadFileToBytesError::BadResponseError { .. } => None, + } + } +} + +impl Display for DownloadFileToBytesError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + DownloadFileToBytesError::WreqError(e) => write!(f, "Wreq error: {}", e), + DownloadFileToBytesError::BadResponseError { status_code, body, url } => { + write!(f, "Bad response (status={}) fetching {} : {:?}", status_code.as_u16(), url, body) + } + } + } +} + +impl From for DownloadFileToBytesError { + fn from(e: wreq::Error) -> Self { + DownloadFileToBytesError::WreqError(e) + } +}