From 8ecc8cf7b3cd2cfaf6808160b334fdde3795bbca Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 25 May 2026 21:54:32 -0400 Subject: [PATCH] clean up and consolidate bespoke error types (#1571) --- .../web/storyteller_web/src/docs/api_doc.rs | 11 --- .../common_responses/common_web_error.rs | 20 ++++ .../engine/create_scene_handler.rs | 65 ++++-------- .../image_gen/enqueue_image_generation.rs | 82 +++++---------- .../enqueue_face_fusion_workflow_handler.rs | 64 +++--------- .../enqueue_live_portrait_workflow_handler.rs | 64 +++--------- .../workflows/enqueue_comfy_ui_handler.rs | 63 +++--------- .../enqueue_video_style_transfer_handler.rs | 75 ++++---------- .../enqueue_workflow_upload_request.rs | 66 +++---------- .../beta_keys/redeem_beta_key_handler.rs | 99 +++++-------------- ...nqueue_gptsovits_model_download_handler.rs | 55 +++-------- .../enqueue_studio_gen2_handler.rs | 66 +++---------- .../endpoints/tts/edit_tts_model.rs | 72 +++----------- .../endpoints/tts/edit_tts_result.rs | 58 ++--------- .../endpoints/tts/enqueue_upload_tts_model.rs | 68 +++---------- .../endpoints/tts/get_tts_model_use_count.rs | 34 +------ .../endpoints/users/edit_profile_handler.rs | 67 ++++--------- .../enqueue_seed_vc_inference_handler.rs | 75 +++----------- ...ueue_voice_conversion_inference_handler.rs | 88 +++++------------ .../inference/enqueue_tts_request.rs | 66 +++---------- .../voice_dataset_samples/upload_zs_sample.rs | 75 +++++--------- .../voice_designer/voices/create_voice.rs | 54 ++-------- 22 files changed, 329 insertions(+), 1058 deletions(-) diff --git a/crates/service/web/storyteller_web/src/docs/api_doc.rs b/crates/service/web/storyteller_web/src/docs/api_doc.rs index 275ced2f5..faf04fb17 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -16,7 +16,6 @@ use crate::http_server::common_responses::tag_info::TagInfo; use crate::http_server::common_responses::user_details_lite::{UserDefaultAvatarInfo, UserDetailsLight}; use crate::http_server::deprecated_endpoints::conversion::enqueue_fbx_to_gltf_handler::*; use crate::http_server::deprecated_endpoints::engine::create_scene_handler::*; -use crate::http_server::deprecated_endpoints::image_gen::enqueue_image_generation::EnqueueImageGenRequestError; use crate::http_server::deprecated_endpoints::image_gen::enqueue_image_generation::EnqueueImageGenRequestSuccessResponse; use crate::http_server::deprecated_endpoints::workflows::enqueue::enqueue_face_fusion_workflow_handler::*; use crate::http_server::deprecated_endpoints::workflows::enqueue::enqueue_live_portrait_workflow_handler::*; @@ -691,7 +690,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux CreateFeaturedItemError, CreateFeaturedItemRequest, CreateFeaturedItemSuccessResponse, - CreateSceneError, CreateSceneSuccessResponse, CreateUserBookmarkError, CreateUserBookmarkRequest, @@ -802,31 +800,23 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux EditUsernameRequest, EditUsernameResponse, EnqueueFaceFusionCropDimensions, - EnqueueFaceFusionWorkflowError, EnqueueFaceFusionWorkflowRequest, EnqueueFaceFusionWorkflowSuccessResponse, EnqueueFbxToGltfRequest, EnqueueFbxToGltfRequestError, EnqueueFbxToGltfRequestSuccessResponse, - EnqueueGptSovitsModelDownloadError, EnqueueGptSovitsModelDownloadRequest, EnqueueGptSovitsModelDownloadSuccessResponse, - EnqueueImageGenRequestError, EnqueueImageGenRequestSuccessResponse, EnqueueLivePortraitCropDimensions, - EnqueueLivePortraitWorkflowError, EnqueueLivePortraitWorkflowRequest, EnqueueLivePortraitWorkflowSuccessResponse, - EnqueueStudioGen2Error, EnqueueStudioGen2Request, EnqueueStudioGen2Response, EnqueueTTSRequest, - EnqueueTTSRequestError, EnqueueTTSRequestSuccessResponse, - EnqueueVideoStyleTransferError, EnqueueVideoStyleTransferRequest, EnqueueVideoStyleTransferSuccessResponse, - EnqueueVoiceConversionInferenceError, EnqueueVoiceConversionInferenceRequest, EnqueueVoiceConversionInferenceSuccessResponse, FeaturedMediaFile, @@ -993,7 +983,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux ProfileError, PromptInfo, RatingRow, - RedeemBetaKeyError, RedeemBetaKeyRequest, RedeemBetaKeySuccessResponse, RemoveImageBackgroundRequest, diff --git a/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs b/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs index 4104c5c4f..8aaebb97b 100644 --- a/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs +++ b/crates/service/web/storyteller_web/src/http_server/common_responses/common_web_error.rs @@ -68,6 +68,9 @@ pub enum CommonWebError { /// 403 Forbidden — content was rejected, with a user-facing message. ContentPolicyRejectedWithMessage(String), + /// 429 Too Many Requests - user is sending too many requests and is being rate limited. + TooManyRequests, + /// Uncaught errors are always 500 Internal Server Error. /// The user will never see the error cause or message, but our /// middleware will handle alerting, logging, etc. @@ -174,6 +177,7 @@ impl Display for CommonWebError { Self::Forbidden => write!(f, "Forbidden"), Self::ContentPolicyRejected => write!(f, "Content policy rejected"), Self::ContentPolicyRejectedWithMessage(msg) => write!(f, "Content policy rejected: {}", msg), + Self::TooManyRequests => write!(f, "Too many requests"), Self::UncaughtServerError(err) => write!(f, "Server error: {}", err), Self::UncaughtServerErrorWithInternalMessage { internal_message, error } => { write!(f, "Server error: {}: {}", internal_message, error) @@ -198,6 +202,7 @@ impl std::fmt::Debug for CommonWebError { Self::Forbidden => write!(f, "Forbidden"), Self::ContentPolicyRejected => write!(f, "ContentPolicyRejected"), Self::ContentPolicyRejectedWithMessage(msg) => write!(f, "ContentPolicyRejectedWithMessage({:?})", msg), + Self::TooManyRequests => write!(f, "TooManyRequests"), Self::UncaughtServerError(err) => write!(f, "UncaughtServerError({:?})", err), Self::UncaughtServerErrorWithInternalMessage { internal_message, error } => { write!(f, "UncaughtServerErrorWithInternalMessage({:?}, {:?})", internal_message, error) @@ -229,6 +234,7 @@ impl ResponseError for CommonWebError { Self::Forbidden => StatusCode::FORBIDDEN, Self::ContentPolicyRejected => StatusCode::FORBIDDEN, Self::ContentPolicyRejectedWithMessage(_) => StatusCode::FORBIDDEN, + Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS, Self::UncaughtServerError(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::UncaughtServerErrorWithInternalMessage { .. } => StatusCode::INTERNAL_SERVER_ERROR, } @@ -479,6 +485,20 @@ mod tests { assert_eq!(error.status_code(), StatusCode::PAYMENT_REQUIRED); } + #[test] + fn too_many_requests_returns_429() { + let error = CommonWebError::TooManyRequests; + assert_eq!(error.status_code(), StatusCode::TOO_MANY_REQUESTS); + assert!(!error.is_server_error()); + assert!(error.cause().is_none()); + + let response = error.error_response(); + let bytes = response.into_body().try_into_bytes().unwrap(); + let body = String::from_utf8(bytes.to_vec()).unwrap(); + assert!(body.contains("\"error_code\":429")); + assert!(body.contains("\"error_code_str\":\"Too Many Requests\"")); + } + #[test] fn uncaught_io_error_returns_500_and_hides_cause() { let io_err = std::io::Error::new(std::io::ErrorKind::Other, "disk exploded"); diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/engine/create_scene_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/engine/create_scene_handler.rs index d53c37ea2..13778518b 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/engine/create_scene_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/engine/create_scene_handler.rs @@ -20,40 +20,9 @@ use mysql_queries::queries::media_files::create::specialized_insert::insert_medi use tokens::tokens::media_files::MediaFileToken; use crate::http_server::deprecated_endpoints::engine::drain_multipart_request::drain_multipart_request; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; - -#[derive(Debug, Serialize, ToSchema)] -pub enum CreateSceneError { - BadInput(String), - NotAuthorized, - MustBeLoggedIn, - ServerError, - RateLimited, -} - -impl ResponseError for CreateSceneError { - fn status_code(&self) -> StatusCode { - match *self { - CreateSceneError::BadInput(_) => StatusCode::BAD_REQUEST, - CreateSceneError::NotAuthorized => StatusCode::UNAUTHORIZED, - CreateSceneError::MustBeLoggedIn => StatusCode::UNAUTHORIZED, - CreateSceneError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - CreateSceneError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - -impl std::fmt::Display for CreateSceneError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - #[derive(Serialize, ToSchema)] pub struct CreateSceneSuccessResponse { pub success: bool, @@ -68,22 +37,22 @@ pub struct CreateSceneSuccessResponse { path = "/v1/engine/create_scene", responses( (status = 200, description = "Found", body = CreateSceneSuccessResponse), - (status = 404, description = "Not found", body = CreateSceneError), - (status = 500, description = "Server error", body = CreateSceneError), + (status = 404, description = "Not found", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError), ), )] pub async fn create_scene_handler( http_request: HttpRequest, server_state: web::Data>, mut multipart_payload: Multipart, -) -> Result { +) -> Result { let mut mysql_connection = server_state.mysql_pool .acquire() .await .map_err(|err| { error!("MySql pool error: {:?}", err); - CreateSceneError::ServerError + CommonWebError::from_error(err) })?; // ==================== READ SESSION ==================== // @@ -94,7 +63,7 @@ pub async fn create_scene_handler( .await .map_err(|e| { error!("Session checker error: {:?}", e); - CreateSceneError::ServerError + CommonWebError::from_error(e) })?; let maybe_avt_token = server_state @@ -105,7 +74,7 @@ pub async fn create_scene_handler( if let Some(ref user) = maybe_user_session { if user.is_banned { - return Err(CreateSceneError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -117,7 +86,7 @@ pub async fn create_scene_handler( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(CreateSceneError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== READ MULTIPART REQUEST ==================== // @@ -126,16 +95,16 @@ pub async fn create_scene_handler( .await .map_err(|e| { // TODO: Error handling could be nicer. - CreateSceneError::BadInput("bad request".to_string()) + CommonWebError::BadInputWithSimpleMessage("bad request".to_string()) })?; let uuid_idempotency_token = upload_media_request.uuid_idempotency_token - .ok_or(CreateSceneError::BadInput("no uuid".to_string()))?; + .ok_or(CommonWebError::BadInputWithSimpleMessage("no uuid".to_string()))?; // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&uuid_idempotency_token) { - return Err(CreateSceneError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } // TODO(bt, 2024-02-22): This should be a transaction. @@ -143,7 +112,7 @@ pub async fn create_scene_handler( .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - CreateSceneError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== USER DATA ==================== // @@ -164,7 +133,7 @@ pub async fn create_scene_handler( .unwrap_or("text/plain"); let bytes = match upload_media_request.file_bytes { - None => return Err(CreateSceneError::BadInput("missing file contents".to_string())), + None => return Err(CommonWebError::BadInputWithSimpleMessage("missing file contents".to_string())), Some(bytes) => bytes, }; @@ -173,7 +142,7 @@ pub async fn create_scene_handler( let hash = sha256_hash_bytes(&bytes) .map_err(|io_error| { error!("Problem hashing bytes: {:?}", io_error); - CreateSceneError::ServerError + CommonWebError::from_anyhow_error(io_error) })?; const PREFIX : Option<&str> = Some("scene_"); @@ -190,7 +159,7 @@ pub async fn create_scene_handler( .await .map_err(|e| { warn!("Upload media bytes to bucket error: {:?}", e); - CreateSceneError::ServerError + CommonWebError::from_anyhow_error(e) })?; // TODO(bt, 2024-02-22): This should be a transaction. @@ -222,7 +191,7 @@ pub async fn create_scene_handler( .await .map_err(|err| { warn!("New file creation DB error: {:?}", err); - CreateSceneError::ServerError + CommonWebError::from_anyhow_error(err) })?; info!("new media file id: {} token: {:?}", record_id, &token); @@ -233,7 +202,7 @@ pub async fn create_scene_handler( }; let body = serde_json::to_string(&response) - .map_err(|e| CreateSceneError::ServerError)?; + .map_err(CommonWebError::from_error)?; return Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs index eff017e1c..49ba8989c 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/image_gen/enqueue_image_generation.rs @@ -37,6 +37,7 @@ use tokens::tokens::model_weights::ModelWeightToken; use tokens::tokens::users::UserToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; @@ -138,43 +139,6 @@ pub struct EnqueueImageGenRequestSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueImageGenRequestError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueImageGenRequestError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueImageGenRequestError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueImageGenRequestError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueImageGenRequestError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueImageGenRequestError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueImageGenRequestError::BadInput(reason) => reason.to_string(), - EnqueueImageGenRequestError::NotAuthorized => "unauthorized".to_string(), - EnqueueImageGenRequestError::ServerError => "server error".to_string(), - EnqueueImageGenRequestError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -impl Display for EnqueueImageGenRequestError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - // Implementation for enqueuing a TTS request // Reference enqueue_infer_tts_handler.rs for checks: rate limiting / user sessions // insert generic inference job.rs @@ -188,10 +152,10 @@ impl Display for EnqueueImageGenRequestError { description = "Enqueue TTS generically", body = EnqueueImageGenRequestSuccessResponse, ), - (status = 400, description = "Bad input", body = EnqueueImageGenRequestError), - (status = 401, description = "Not authorized", body = EnqueueImageGenRequestError), - (status = 429, description = "Rate limited", body = EnqueueImageGenRequestError), - (status = 500, description = "Server error", body = EnqueueImageGenRequestError) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params(("request" = EnqueueImageGenRequest, description = "Payload for TTS Request")) )] @@ -199,10 +163,10 @@ pub async fn enqueue_image_generation_request( http_request: HttpRequest, request: Json, server_state: Data> -) -> Result { +) -> Result { let inference_mode = inference_mode_from_http_request(&http_request) - .ok_or(EnqueueImageGenRequestError::BadInput("Invalid request".to_string()))?; + .ok_or(CommonWebError::BadInputWithSimpleMessage("Invalid request".to_string()))?; validate_request(&request, inference_mode)?; @@ -213,7 +177,7 @@ pub async fn enqueue_image_generation_request( let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueImageGenRequestError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -222,7 +186,7 @@ pub async fn enqueue_image_generation_request( .maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueImageGenRequestError::ServerError + CommonWebError::from_error(e) })?; @@ -254,7 +218,7 @@ pub async fn enqueue_image_generation_request( if let Some(ref user) = maybe_user_session { if user.role.is_banned { - return Err(EnqueueImageGenRequestError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -268,7 +232,7 @@ pub async fn enqueue_image_generation_request( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueImageGenRequestError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // Get up IP address @@ -279,14 +243,14 @@ pub async fn enqueue_image_generation_request( // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueImageGenRequestError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueImageGenRequestError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== INFERENCE ARGS ==================== // @@ -349,7 +313,7 @@ pub async fn enqueue_image_generation_request( match both_fields { (Some(_), Some(_)) => { - return Err(EnqueueImageGenRequestError::BadInput("Can't upload both lora and model".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Can't upload both lora and model".to_string())); } (Some(sd_url), None) => { maybe_sd_upload_url = Some(sd_url.to_string()); @@ -438,9 +402,9 @@ pub async fn enqueue_image_generation_request( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueImageGenRequestError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueImageGenRequestError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -451,7 +415,7 @@ pub async fn enqueue_image_generation_request( let body = serde_json ::to_string(&response) - .map_err(|_e| EnqueueImageGenRequestError::ServerError)?; + ?; // Error handling 101 rust result type returned like so. Ok(HttpResponse::Ok().content_type("application/json").body(body)) @@ -460,7 +424,7 @@ pub async fn enqueue_image_generation_request( fn validate_request( request: &Json, inference_mode: TypeOfInference -) -> Result<(), EnqueueImageGenRequestError> { +) -> Result<(), CommonWebError> { let mut requires_name = false; let mut requires_description = false; @@ -485,19 +449,19 @@ fn validate_request( } if requires_name && request.maybe_name.is_none() { - return Err(EnqueueImageGenRequestError::BadInput("Missing Model / Lora Name".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model / Lora Name".to_string())); } if requires_description && request.maybe_description.is_none() { - return Err(EnqueueImageGenRequestError::BadInput("Missing Model / Lora Description".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model / Lora Description".to_string())); } if requires_sd_model_token && request.maybe_sd_model_token.is_none() { - return Err(EnqueueImageGenRequestError::BadInput("Missing Model Token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model Token".to_string())); } if requires_lora_upload_path && request.maybe_lora_upload_path.is_none() { - return Err(EnqueueImageGenRequestError::BadInput("Missing Lora Upload Path".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Lora Upload Path".to_string())); } if requires_upload_path && request.maybe_upload_path.is_none() { - return Err(EnqueueImageGenRequestError::BadInput("Missing Model Upload Path".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Model Upload Path".to_string())); } Ok(()) diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs index 00c26df47..f33830238 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_face_fusion_workflow_handler.rs @@ -21,6 +21,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::media_files::MediaFileToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::requests::get_request_domain_branding::{get_request_domain_branding, DomainBranding}; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; @@ -75,44 +76,7 @@ pub struct EnqueueFaceFusionWorkflowSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueFaceFusionWorkflowError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueFaceFusionWorkflowError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueFaceFusionWorkflowError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueFaceFusionWorkflowError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueFaceFusionWorkflowError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueFaceFusionWorkflowError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueFaceFusionWorkflowError::BadInput(reason) => reason.to_string(), - EnqueueFaceFusionWorkflowError::NotAuthorized => "unauthorized".to_string(), - EnqueueFaceFusionWorkflowError::ServerError => "server error".to_string(), - EnqueueFaceFusionWorkflowError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for EnqueueFaceFusionWorkflowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - /// Enqueue "lipsync" (Face Fusion) video workflows. /// /// We've renamed this as to not give away what we're doing to users. @@ -122,10 +86,10 @@ impl std::fmt::Display for EnqueueFaceFusionWorkflowError { path = "/v1/workflows/enqueue_lipsync", responses( (status = 200, description = "Success", body = EnqueueFaceFusionWorkflowSuccessResponse), - (status = 400, description = "Bad input", body = EnqueueFaceFusionWorkflowError), - (status = 401, description = "Not authorized", body = EnqueueFaceFusionWorkflowError), - (status = 429, description = "Rate limited", body = EnqueueFaceFusionWorkflowError), - (status = 500, description = "Server error", body = EnqueueFaceFusionWorkflowError) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params(("request" = EnqueueFaceFusionWorkflowRequest, description = "Payload for request")) )] @@ -133,7 +97,7 @@ pub async fn enqueue_face_fusion_workflow_handler( http_request: HttpRequest, request: Json, server_state: web::Data> -) -> Result, EnqueueFaceFusionWorkflowError> +) -> Result, CommonWebError> { // ==================== DB ==================== // @@ -142,7 +106,7 @@ pub async fn enqueue_face_fusion_workflow_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueFaceFusionWorkflowError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -156,8 +120,8 @@ pub async fn enqueue_face_fusion_workflow_handler( &mut mysql_connection) .await .map_err(|err| match err { - RequireUserSessionError::ServerError => EnqueueFaceFusionWorkflowError::ServerError, - RequireUserSessionError::NotAuthorized => EnqueueFaceFusionWorkflowError::NotAuthorized, + RequireUserSessionError::ServerError => CommonWebError::from_error(err), + RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized, })?; // ==================== PAID PLAN + PRIORITY ==================== // @@ -179,20 +143,20 @@ pub async fn enqueue_face_fusion_workflow_handler( // ==================== RATE LIMIT ==================== // if let Err(_err) = server_state.redis_rate_limiters.logged_in.rate_limit_request(&http_request).await { - return Err(EnqueueFaceFusionWorkflowError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueFaceFusionWorkflowError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueFaceFusionWorkflowError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== HANDLE REQUEST ==================== // @@ -275,9 +239,9 @@ pub async fn enqueue_face_fusion_workflow_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueFaceFusionWorkflowError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueFaceFusionWorkflowError::ServerError); + return Err(CommonWebError::from_error(err)); } }; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs index 68bdd2467..6925e7ed5 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue/enqueue_live_portrait_workflow_handler.rs @@ -22,6 +22,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::media_files::MediaFileToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::requests::get_request_domain_branding::{get_request_domain_branding, DomainBranding}; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; @@ -84,44 +85,7 @@ pub struct EnqueueLivePortraitWorkflowSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueLivePortraitWorkflowError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueLivePortraitWorkflowError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueLivePortraitWorkflowError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueLivePortraitWorkflowError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueLivePortraitWorkflowError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueLivePortraitWorkflowError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueLivePortraitWorkflowError::BadInput(reason) => reason.to_string(), - EnqueueLivePortraitWorkflowError::NotAuthorized => "unauthorized".to_string(), - EnqueueLivePortraitWorkflowError::ServerError => "server error".to_string(), - EnqueueLivePortraitWorkflowError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for EnqueueLivePortraitWorkflowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - /// Enqueue "face mirror" (Live Portrait) video workflows. /// /// We've renamed this as to not give away what we're doing to users. @@ -131,17 +95,17 @@ impl std::fmt::Display for EnqueueLivePortraitWorkflowError { path = "/v1/workflows/enqueue_face_mirror", responses( (status = 200, description = "Success", body = EnqueueLivePortraitWorkflowSuccessResponse), - (status = 400, description = "Bad input", body = EnqueueLivePortraitWorkflowError), - (status = 401, description = "Not authorized", body = EnqueueLivePortraitWorkflowError), - (status = 429, description = "Rate limited", body = EnqueueLivePortraitWorkflowError), - (status = 500, description = "Server error", body = EnqueueLivePortraitWorkflowError) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params(("request" = EnqueueLivePortraitWorkflowRequest, description = "Payload for request")) )] pub async fn enqueue_live_portrait_workflow_handler( http_request: HttpRequest, request: Json, - server_state: web::Data>) -> Result, EnqueueLivePortraitWorkflowError> + server_state: web::Data>) -> Result, CommonWebError> { // ==================== DB ==================== // @@ -150,7 +114,7 @@ pub async fn enqueue_live_portrait_workflow_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueLivePortraitWorkflowError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -164,8 +128,8 @@ pub async fn enqueue_live_portrait_workflow_handler( &mut mysql_connection) .await .map_err(|err| match err { - RequireUserSessionError::ServerError => EnqueueLivePortraitWorkflowError::ServerError, - RequireUserSessionError::NotAuthorized => EnqueueLivePortraitWorkflowError::NotAuthorized, + RequireUserSessionError::ServerError => CommonWebError::from_error(err), + RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized, })?; // ==================== PAID PLAN + PRIORITY ==================== // @@ -187,20 +151,20 @@ pub async fn enqueue_live_portrait_workflow_handler( // ==================== RATE LIMIT ==================== // if let Err(_err) = server_state.redis_rate_limiters.logged_in.rate_limit_request(&http_request).await { - return Err(EnqueueLivePortraitWorkflowError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueLivePortraitWorkflowError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueLivePortraitWorkflowError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== HANDLE REQUEST ==================== // @@ -293,9 +257,9 @@ pub async fn enqueue_live_portrait_workflow_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueLivePortraitWorkflowError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueLivePortraitWorkflowError::ServerError); + return Err(CommonWebError::from_error(err)); } }; diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs index f2ede430b..60fd6bf04 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_comfy_ui_handler.rs @@ -6,8 +6,6 @@ use std::collections::HashMap; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::{error, info, warn}; @@ -30,12 +28,12 @@ use tokens::tokens::model_weights::ModelWeightToken; use tokens::tokens::users::UserToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access; @@ -68,48 +66,11 @@ pub struct EnqueueComfySuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug)] -pub enum EnqueueComfyError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueComfyError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueComfyError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueComfyError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueComfyError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueComfyError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueComfyError::BadInput(reason) => reason.to_string(), - EnqueueComfyError::NotAuthorized => "unauthorized".to_string(), - EnqueueComfyError::ServerError => "server error".to_string(), - EnqueueComfyError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for EnqueueComfyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - pub async fn enqueue_comfy_ui_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result { let mut maybe_user_token : Option = None; @@ -118,7 +79,7 @@ pub async fn enqueue_comfy_ui_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueComfyError::ServerError + CommonWebError::from_error(err) })?; let maybe_avt_token = server_state.avt_cookie_manager @@ -132,7 +93,7 @@ pub async fn enqueue_comfy_ui_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueComfyError::ServerError + CommonWebError::from_error(e) })?; if let Some(user_session) = maybe_user_session.as_ref() { @@ -143,7 +104,7 @@ pub async fn enqueue_comfy_ui_handler( if !allowed_video_style_transfer_access(maybe_user_session.as_ref(), &server_state.flags) { warn!("Video style transfer access is not permitted for user"); - return Err(EnqueueComfyError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } // ==================== PAID PLAN + PRIORITY ==================== // @@ -168,27 +129,27 @@ pub async fn enqueue_comfy_ui_handler( None => &server_state.redis_rate_limiters.logged_out, Some(ref user) => { if user.role.is_banned { - return Err(EnqueueComfyError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } &server_state.redis_rate_limiters.logged_in }, }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueComfyError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueComfyError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueComfyError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== LOOK UP MODEL INFO ==================== // @@ -327,9 +288,9 @@ pub async fn enqueue_comfy_ui_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueComfyError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueComfyError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -339,7 +300,7 @@ pub async fn enqueue_comfy_ui_handler( }; let body = serde_json::to_string(&response) - .map_err(|_e| EnqueueComfyError::ServerError)?; + ?; Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs index d60864835..1eeb8cc71 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_video_style_transfer_handler.rs @@ -5,8 +5,6 @@ use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::{error, info, warn}; use utoipa::ToSchema; @@ -30,13 +28,13 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::media_files::MediaFileToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::deprecated_endpoints::workflows::coordinate_workflow_args::{coordinate_workflow_args, CoordinatedWorkflowArgs}; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access; use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null; @@ -166,44 +164,7 @@ pub struct EnqueueVideoStyleTransferSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueVideoStyleTransferError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueVideoStyleTransferError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueVideoStyleTransferError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueVideoStyleTransferError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueVideoStyleTransferError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueVideoStyleTransferError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueVideoStyleTransferError::BadInput(reason) => reason.to_string(), - EnqueueVideoStyleTransferError::NotAuthorized => "unauthorized".to_string(), - EnqueueVideoStyleTransferError::ServerError => "server error".to_string(), - EnqueueVideoStyleTransferError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for EnqueueVideoStyleTransferError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - /// DEPRECATED. Use /v1/workflows/enqueue_studio or /v1/workflows/enqueue_vst #[deprecated(note = "Use /v1/workflows/enqueue_studio")] #[utoipa::path( @@ -212,24 +173,24 @@ impl std::fmt::Display for EnqueueVideoStyleTransferError { path = "/v1/video/enqueue_vst", responses( (status = 200, description = "Success", body = EnqueueVideoStyleTransferSuccessResponse), - (status = 400, description = "Bad input", body = EnqueueVideoStyleTransferError), - (status = 401, description = "Not authorized", body = EnqueueVideoStyleTransferError), - (status = 429, description = "Rate limited", body = EnqueueVideoStyleTransferError), - (status = 500, description = "Server error", body = EnqueueVideoStyleTransferError) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params(("request" = EnqueueVideoStyleTransferRequest, description = "Payload for request")) )] pub async fn enqueue_video_style_transfer_handler( http_request: HttpRequest, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result { // ==================== VALIDATION ==================== // match request.frame_skip { None | Some(1) | Some(2) => {} // Allowed _ => { - return Err(EnqueueVideoStyleTransferError::BadInput("Invalid frame skip value".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Invalid frame skip value".to_string())); } } @@ -240,7 +201,7 @@ pub async fn enqueue_video_style_transfer_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueVideoStyleTransferError::ServerError + CommonWebError::from_error(err) })?; let maybe_avt_token = server_state.avt_cookie_manager @@ -254,7 +215,7 @@ pub async fn enqueue_video_style_transfer_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueVideoStyleTransferError::ServerError + CommonWebError::from_error(e) })?; let maybe_user_token = maybe_user_session @@ -265,7 +226,7 @@ pub async fn enqueue_video_style_transfer_handler( if !allowed_video_style_transfer_access(maybe_user_session.as_ref(), &server_state.flags) { warn!("Video style transfer access is not permitted for user"); - return Err(EnqueueVideoStyleTransferError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } // ==================== PAID PLAN + PRIORITY ==================== // @@ -295,27 +256,27 @@ pub async fn enqueue_video_style_transfer_handler( None => &server_state.redis_rate_limiters.logged_out, Some(ref user) => { if user.role.is_banned { - return Err(EnqueueVideoStyleTransferError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } &server_state.redis_rate_limiters.logged_in }, }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueVideoStyleTransferError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueVideoStyleTransferError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueVideoStyleTransferError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== LOOK UP MODEL INFO ==================== // @@ -353,7 +314,7 @@ pub async fn enqueue_video_style_transfer_handler( let maybe_strength = request.use_strength .map(|strength| { if strength < MINIMUM_STRENGTH || strength > MAXIMUM_STRENGTH { - Err(EnqueueVideoStyleTransferError::BadInput("Strength must be between 0.0 and 1.0".to_string())) + Err(CommonWebError::BadInputWithSimpleMessage("Strength must be between 0.0 and 1.0".to_string())) } else { Ok(strength) } @@ -481,9 +442,9 @@ pub async fn enqueue_video_style_transfer_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueVideoStyleTransferError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueVideoStyleTransferError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -493,7 +454,7 @@ pub async fn enqueue_video_style_transfer_handler( }; let body = serde_json::to_string(&response) - .map_err(|_e| EnqueueVideoStyleTransferError::ServerError)?; + ?; Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_workflow_upload_request.rs b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_workflow_upload_request.rs index a13eaaa2f..c30912172 100644 --- a/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_workflow_upload_request.rs +++ b/crates/service/web/storyteller_web/src/http_server/deprecated_endpoints/workflows/enqueue_workflow_upload_request.rs @@ -39,6 +39,7 @@ use tokens::tokens::media_files::MediaFileToken; use tokens::tokens::users::UserToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; @@ -62,43 +63,6 @@ pub struct EnqueueWorkFlowRequestSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueWorkFlowRequestError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueWorkFlowRequestError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueWorkFlowRequestError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueWorkFlowRequestError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueWorkFlowRequestError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueWorkFlowRequestError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueWorkFlowRequestError::BadInput(reason) => reason.to_string(), - EnqueueWorkFlowRequestError::NotAuthorized => "unauthorized".to_string(), - EnqueueWorkFlowRequestError::ServerError => "server error".to_string(), - EnqueueWorkFlowRequestError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -impl Display for EnqueueWorkFlowRequestError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - // Implementation for enqueuing a TTS request // Reference enqueue_infer_tts_handler.rs for checks: rate limiting / user sessions // insert generic inference job.rs @@ -108,23 +72,23 @@ pub async fn enqueue_workflow_upload_request( http_request: HttpRequest, request: Json, server_state: Data> -) -> Result { +) -> Result { if request.google_drive_link.is_empty() { - return Err(EnqueueWorkFlowRequestError::BadInput("Missing Google Drive Link".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Google Drive Link".to_string())); } if request.title.is_empty() { - return Err(EnqueueWorkFlowRequestError::BadInput("Missing Title".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Title".to_string())); } if request.description.is_empty() { - return Err(EnqueueWorkFlowRequestError::BadInput("Missing Description".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing Description".to_string())); } if request.commit_hash.is_empty() { - return Err(EnqueueWorkFlowRequestError::BadInput("Missing commit hash".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing commit hash".to_string())); } let mut maybe_user_token: Option = None; @@ -134,7 +98,7 @@ pub async fn enqueue_workflow_upload_request( let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueWorkFlowRequestError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -143,7 +107,7 @@ pub async fn enqueue_workflow_upload_request( .maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueWorkFlowRequestError::ServerError + CommonWebError::from_error(e) })?; @@ -172,7 +136,7 @@ pub async fn enqueue_workflow_upload_request( if let Some(ref user) = maybe_user_session { if user.role.is_banned { - return Err(EnqueueWorkFlowRequestError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -186,7 +150,7 @@ pub async fn enqueue_workflow_upload_request( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueWorkFlowRequestError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // Get up IP address @@ -197,14 +161,14 @@ pub async fn enqueue_workflow_upload_request( // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueWorkFlowRequestError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueWorkFlowRequestError::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== INFERENCE ARGS ==================== // @@ -300,9 +264,9 @@ pub async fn enqueue_workflow_upload_request( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueWorkFlowRequestError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueWorkFlowRequestError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -313,7 +277,7 @@ pub async fn enqueue_workflow_upload_request( let body = serde_json ::to_string(&response) - .map_err(|_e| EnqueueWorkFlowRequestError::ServerError)?; + ?; // Error handling 101 rust result type returned like so. Ok(HttpResponse::Ok().content_type("application/json").body(body)) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs index 13b72bbe4..7e8545835 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/redeem_beta_key_handler.rs @@ -1,12 +1,10 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::warn; use utoipa::ToSchema; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; use enums::by_table::beta_keys::beta_key_product::BetaKeyProduct; use enums::by_table::users::user_feature_flag::UserFeatureFlag; @@ -17,9 +15,8 @@ use mysql_queries::queries::users::user::update::set_can_access_studio_transacti use mysql_queries::queries::users::user::update::set_user_feature_flags_transactional::{set_user_feature_flags_transactional, SetUserFeatureFlagTransactionalArgs}; use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::http_server::web_utils::try_delete_session_cache::try_delete_session_cache; -use crate::http_server::web_utils::user_session::require_user_session::{require_user_session, RequireUserSessionError}; +use crate::http_server::web_utils::user_session::require_user_session::require_user_session; use crate::state::server_state::ServerState; #[derive(Deserialize, ToSchema)] @@ -32,46 +29,6 @@ pub struct RedeemBetaKeySuccessResponse { pub success: bool, } -#[derive(Debug, ToSchema)] -pub enum RedeemBetaKeyError { - BadInput(String), - NotAuthorized, - NotFound, - RateLimited, - ServerError, -} - -impl ResponseError for RedeemBetaKeyError { - fn status_code(&self) -> StatusCode { - match *self { - RedeemBetaKeyError::BadInput(_) => StatusCode::BAD_REQUEST, - RedeemBetaKeyError::NotAuthorized => StatusCode::UNAUTHORIZED, - RedeemBetaKeyError::NotFound => StatusCode::NOT_FOUND, - RedeemBetaKeyError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - RedeemBetaKeyError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - RedeemBetaKeyError::BadInput(reason) => reason.to_string(), - RedeemBetaKeyError::NotAuthorized => "unauthorized".to_string(), - RedeemBetaKeyError::NotFound => "not found".to_string(), - RedeemBetaKeyError::RateLimited => "rate limited".to_string(), - RedeemBetaKeyError::ServerError => "server error".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -// NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for RedeemBetaKeyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - /// Redeem a beta key to gain access to a feature #[utoipa::path( post, @@ -79,11 +36,11 @@ impl fmt::Display for RedeemBetaKeyError { path = "/v1/beta_keys/redeem", responses( (status = 200, description = "Success", body = RedeemBetaKeySuccessResponse), - (status = 400, description = "Bad input", body = RedeemBetaKeyError), - (status = 401, description = "Not authorized", body = RedeemBetaKeyError), - (status = 404, description = "Not found", body = RedeemBetaKeyError), - (status = 429, description = "Rate limited", body = RedeemBetaKeyError), - (status = 500, description = "Server error", body = RedeemBetaKeyError), + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 404, description = "Not found", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError), ), params( ("request" = RedeemBetaKeyRequest, description = "Payload for Request"), @@ -93,47 +50,37 @@ pub async fn redeem_beta_key_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data>, -) -> Result +) -> Result { - let user_session = require_user_session(&http_request, &server_state) - .await - .map_err(|err| match err { - RequireUserSessionError::ServerError => RedeemBetaKeyError::ServerError, - RequireUserSessionError::NotAuthorized => RedeemBetaKeyError::NotAuthorized, - })?; + let user_session = require_user_session(&http_request, &server_state).await?; let rate_limiter = &server_state.redis_rate_limiters.logged_out; - if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(RedeemBetaKeyError::RateLimited); + if rate_limiter.rate_limit_request(&http_request).await.is_err() { + return Err(CommonWebError::TooManyRequests); } let maybe_beta_key = get_beta_key_by_value(&request.beta_key, &server_state.mysql_pool) .await .map_err(|err| { warn!("Error getting beta key by value: {:?}", &err); - RedeemBetaKeyError::ServerError + CommonWebError::from_anyhow_error(err) })?; let beta_key = match maybe_beta_key { Some(beta_key) => beta_key, - None => return Err(RedeemBetaKeyError::NotFound), + None => return Err(CommonWebError::NotFound), }; if beta_key.maybe_redeemed_at.is_some() || beta_key.maybe_redeemer_user_token.is_some() { - return Err(RedeemBetaKeyError::BadInput("beta key already redeemed".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("beta key already redeemed".to_string())); } let ip_address = get_request_ip(&http_request); match beta_key.product { BetaKeyProduct::Studio => { - enroll_in_studio(&request, &server_state, &user_session, &ip_address) - .await - .map_err(|err| { - warn!("Error enrolling in studio: {:?}", &err); - RedeemBetaKeyError::ServerError - })?; + enroll_in_studio(&request, &server_state, &user_session, &ip_address).await?; } } @@ -143,8 +90,8 @@ pub async fn redeem_beta_key_handler( success: true, }; - let body = serde_json::to_string(&response) - .map_err(|_e| RedeemBetaKeyError::ServerError)?; + // `?` via the `From` impl on CommonWebError. + let body = serde_json::to_string(&response)?; Ok(HttpResponse::Ok() .content_type("application/json") @@ -156,7 +103,7 @@ async fn enroll_in_studio( server_state: &ServerState, user_session: &SessionUserRecord, ip_address: &str, -) -> Result<(), RedeemBetaKeyError> { +) -> Result<(), CommonWebError> { let mut user_feature_flags = UserSessionFeatureFlags::new(user_session.maybe_feature_flags.as_deref()); @@ -169,7 +116,7 @@ async fn enroll_in_studio( .await .map_err(|e| { warn!("Could not open transaction: {:?}", e); - RedeemBetaKeyError::ServerError + CommonWebError::from_error(e) })?; set_user_feature_flags_transactional(SetUserFeatureFlagTransactionalArgs { @@ -181,7 +128,7 @@ async fn enroll_in_studio( }).await .map_err(|e| { warn!("Could not set flags: {:?}", e); - RedeemBetaKeyError::ServerError + CommonWebError::from_anyhow_error(e) })?; // NB: This isn't a necessary field, but can be useful for analytics. @@ -192,21 +139,21 @@ async fn enroll_in_studio( }).await .map_err(|e| { warn!("Could not set can_access_studio: {:?}", e); - RedeemBetaKeyError::ServerError + CommonWebError::from_anyhow_error(e) })?; redeem_beta_key(&request.beta_key, &user_session.user_token, &mut transaction) .await .map_err(|e| { warn!("Could not redeem beta key: {:?}", e); - RedeemBetaKeyError::ServerError + CommonWebError::from_anyhow_error(e) })?; transaction.commit() .await .map_err(|e| { warn!("Could not commit transaction: {:?}", e); - RedeemBetaKeyError::ServerError + CommonWebError::from_error(e) })?; Ok(()) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs index d5f0f98b5..4834597f4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/model_download/enqueue_gptsovits_model_download_handler.rs @@ -23,6 +23,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::media_files::MediaFileToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; @@ -50,36 +51,6 @@ pub struct EnqueueGptSovitsModelDownloadSuccessResponse { /// This is how frontend clients can request the job execution status. pub job_token: InferenceJobToken, } - -#[derive(Debug, Serialize, ToSchema)] -pub enum EnqueueGptSovitsModelDownloadError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueGptSovitsModelDownloadError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueGptSovitsModelDownloadError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueGptSovitsModelDownloadError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueGptSovitsModelDownloadError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueGptSovitsModelDownloadError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - -impl fmt::Display for EnqueueGptSovitsModelDownloadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - /// Enqueue a GptSoVits model for download (eg. from Google Drive) #[utoipa::path( post, @@ -88,13 +59,13 @@ impl fmt::Display for EnqueueGptSovitsModelDownloadError { request_body = EnqueueGptSovitsModelDownloadRequest, responses( (status = 200, body = EnqueueGptSovitsModelDownloadSuccessResponse), - (status = 400, body = EnqueueGptSovitsModelDownloadError), + (status = 400, body = CommonWebError), ) )] pub async fn enqueue_gptsovits_model_download_handler( http_request: HttpRequest, request: Json, - server_state: web::Data>) -> Result, EnqueueGptSovitsModelDownloadError> + server_state: web::Data>) -> Result, CommonWebError> { // ==================== DB ==================== // @@ -103,7 +74,7 @@ pub async fn enqueue_gptsovits_model_download_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueGptSovitsModelDownloadError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -117,8 +88,8 @@ pub async fn enqueue_gptsovits_model_download_handler( &mut mysql_connection) .await .map_err(|err| match err { - RequireUserSessionError::ServerError => EnqueueGptSovitsModelDownloadError::ServerError, - RequireUserSessionError::NotAuthorized => EnqueueGptSovitsModelDownloadError::NotAuthorized, + RequireUserSessionError::ServerError => CommonWebError::from_error(err), + RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized, })?; // ==================== PAID PLAN + PRIORITY ==================== // @@ -133,7 +104,7 @@ pub async fn enqueue_gptsovits_model_download_handler( let maybe_routing_tag= get_routing_tag_header(&http_request); if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request).await { - return Err(EnqueueGptSovitsModelDownloadError::RateLimited); + return Err(CommonWebError::TooManyRequests); } let ip_address = get_request_ip(&http_request); @@ -145,23 +116,23 @@ pub async fn enqueue_gptsovits_model_download_handler( let creator_set_visibility = request.creator_set_visibility.unwrap_or(Visibility::Public); if let Err(reason) = validate_idempotency_token_format(&uuid) { - return Err(EnqueueGptSovitsModelDownloadError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } if let Some(title) = title { if let Err(reason) = validate_model_title(title) { - return Err(EnqueueGptSovitsModelDownloadError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } } match is_bad_tts_model_download_url(&download_url) { Ok(false) => {} // Ok case Ok(true) => { - return Err(EnqueueGptSovitsModelDownloadError::BadInput("Bad model download URL".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Bad model download URL".to_string())); } Err(err) => { warn!("Error parsing url: {:?}", err); - return Err(EnqueueGptSovitsModelDownloadError::BadInput("Bad model download URL".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Bad model download URL".to_string())); } } @@ -206,9 +177,9 @@ pub async fn enqueue_gptsovits_model_download_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueGptSovitsModelDownloadError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueGptSovitsModelDownloadError::ServerError); + return Err(CommonWebError::from_error(err)); } }; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs index 498ea05e0..33a3f38aa 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/studio_gen2/enqueue_studio_gen2_handler.rs @@ -26,6 +26,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::media_files::MediaFileToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::plan_category::PlanCategory; use crate::http_server::requests::request_headers::get_routing_tag_header::get_routing_tag_header; use crate::http_server::requests::request_headers::has_debug_header::has_debug_header; @@ -84,44 +85,7 @@ pub struct EnqueueStudioGen2Response { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueStudioGen2Error { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueStudioGen2Error { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueStudioGen2Error::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueStudioGen2Error::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueStudioGen2Error::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueStudioGen2Error::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueStudioGen2Error::BadInput(reason) => reason.to_string(), - EnqueueStudioGen2Error::NotAuthorized => "unauthorized".to_string(), - EnqueueStudioGen2Error::ServerError => "server error".to_string(), - EnqueueStudioGen2Error::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for EnqueueStudioGen2Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - /// Enqueue Studio Gen2 jobs. #[utoipa::path( post, @@ -129,10 +93,10 @@ impl std::fmt::Display for EnqueueStudioGen2Error { path = "/v1/studio_gen2/enqueue", responses( (status = 200, description = "Success", body = EnqueueStudioGen2Response), - (status = 400, description = "Bad input", body = EnqueueStudioGen2Error), - (status = 401, description = "Not authorized", body = EnqueueStudioGen2Error), - (status = 429, description = "Rate limited", body = EnqueueStudioGen2Error), - (status = 500, description = "Server error", body = EnqueueStudioGen2Error) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params(("request" = EnqueueStudioGen2Request, description = "Payload for request")) )] @@ -140,7 +104,7 @@ pub async fn enqueue_studio_gen2_handler( http_request: HttpRequest, request: Json, server_state: web::Data> -) -> Result, EnqueueStudioGen2Error> { +) -> Result, CommonWebError> { let a = 1; // ==================== DB ==================== // @@ -150,7 +114,7 @@ pub async fn enqueue_studio_gen2_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueStudioGen2Error::ServerError + CommonWebError::from_error(err) })?; let maybe_avt_token = server_state.avt_cookie_manager @@ -164,7 +128,7 @@ pub async fn enqueue_studio_gen2_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueStudioGen2Error::ServerError + CommonWebError::from_error(e) })?; let maybe_user_token = maybe_user_session @@ -175,7 +139,7 @@ pub async fn enqueue_studio_gen2_handler( //if !allowed_video_style_transfer_access(maybe_user_session.as_ref(), &server_state.flags) { // warn!("Video style transfer access is not permitted for user"); - // return Err(EnqueueStudioGen2Error::NotAuthorized); + // return Err(CommonWebError::NotAuthorized); //} // ==================== PAID PLAN + PRIORITY ==================== // @@ -205,27 +169,27 @@ pub async fn enqueue_studio_gen2_handler( None => &server_state.redis_rate_limiters.logged_out, Some(ref user) => { if user.role.is_banned { - return Err(EnqueueStudioGen2Error::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } &server_state.redis_rate_limiters.logged_in }, }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueStudioGen2Error::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== HANDLE IDEMPOTENCY ==================== // if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { - return Err(EnqueueStudioGen2Error::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) .await .map_err(|err| { error!("Error inserting idempotency token: {:?}", err); - EnqueueStudioGen2Error::BadInput("invalid idempotency token".to_string()) + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) })?; // ==================== LOOK UP MODEL INFO ==================== // @@ -317,9 +281,9 @@ pub async fn enqueue_studio_gen2_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueStudioGen2Error::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueStudioGen2Error::ServerError); + return Err(CommonWebError::from_error(err)); } }; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs index 5d7661bfc..3f26e6f4c 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_model.rs @@ -3,11 +3,8 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::{Json, Path}; use actix_web::{web, HttpRequest, HttpResponse}; use language_tags::LanguageTag; @@ -15,7 +12,7 @@ use log::{error, info, warn}; use sqlx::MySqlPool; use crate::configs::supported_languages_for_models::get_canonicalized_language_tag_for_model; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; use enums::common::visibility::Visibility; @@ -90,54 +87,17 @@ pub struct EditTtsModelRequest { pub use_default_m_factor: Option, pub maybe_custom_m_factor: Option, } - -#[derive(Debug)] -pub enum EditTtsModelError { - BadInput(String), - NotAuthorized, - ModelNotFound, - ServerError, -} - -impl ResponseError for EditTtsModelError { - fn status_code(&self) -> StatusCode { - match *self { - EditTtsModelError::BadInput(_) => StatusCode::BAD_REQUEST, - EditTtsModelError::NotAuthorized => StatusCode::UNAUTHORIZED, - EditTtsModelError::ModelNotFound => StatusCode::NOT_FOUND, - EditTtsModelError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EditTtsModelError::BadInput(reason) => reason.to_string(), - EditTtsModelError::NotAuthorized=> "unauthorized".to_string(), - EditTtsModelError::ModelNotFound => "not found".to_string(), - EditTtsModelError::ServerError => "server error".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for EditTtsModelError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - pub async fn edit_tts_model_handler( http_request: HttpRequest, path: Path, request: Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result { // NB: Disable if we've migrated to model_weights if server_state.flags.switch_tts_to_model_weights { warn!("Migration to model_weights for tts. Cannot delete old model."); - return Err(EditTtsModelError::ServerError); + return Err(CommonWebError::server_error_with_message("TTS migrated to model_weights; legacy edit endpoint disabled")); } let maybe_user_session = server_state @@ -146,14 +106,14 @@ pub async fn edit_tts_model_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EditTtsModelError::ServerError + CommonWebError::from_error(e) })?; let user_session = match maybe_user_session { Some(session) => session, None => { warn!("not logged in"); - return Err(EditTtsModelError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } }; @@ -173,11 +133,11 @@ pub async fn edit_tts_model_handler( }, Ok(None) => { warn!("could not find model"); - return Err(EditTtsModelError::ModelNotFound); + return Err(CommonWebError::NotFound); }, Err(err) => { warn!("error looking up model: {:?}", err); - return Err(EditTtsModelError::ModelNotFound); + return Err(CommonWebError::NotFound); }, }; @@ -187,12 +147,12 @@ pub async fn edit_tts_model_handler( if !is_author && !is_mod { warn!("user is not allowed to edit model: {:?}", user_session.user_token); - return Err(EditTtsModelError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } if !is_mod { if model_record.is_locked_from_user_modification || model_record.is_locked_from_use { - return Err(EditTtsModelError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -213,7 +173,7 @@ pub async fn edit_tts_model_handler( if let Some(payload) = request.title.as_deref() { if contains_slurs(payload) { - return Err(EditTtsModelError::BadInput("title contains slurs".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("title contains slurs".to_string())); } title = Some(payload.trim().to_string()); @@ -221,7 +181,7 @@ pub async fn edit_tts_model_handler( if let Some(markdown) = request.description_markdown.as_deref() { if contains_slurs(markdown) { - return Err(EditTtsModelError::BadInput("description contains slurs".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("description contains slurs".to_string())); } let markdown = markdown.trim().to_string(); @@ -243,7 +203,7 @@ pub async fn edit_tts_model_handler( .transpose() .map_err(|e| { error!("Error parsing language tag '{}': {:?}", tag, e); - EditTtsModelError::BadInput("bad locale string".to_string()) + CommonWebError::BadInputWithSimpleMessage("bad locale string".to_string()) })?; if let Some(full_tag) = maybe_full_canonical_tag { @@ -262,7 +222,7 @@ pub async fn edit_tts_model_handler( if let Some(visibility) = request.creator_set_visibility.as_deref() { creator_set_visibility = Visibility::from_str(visibility) - .map_err(|_| EditTtsModelError::BadInput("bad record visibility".to_string()))?; + .map_err(|_| CommonWebError::BadInputWithSimpleMessage("bad record visibility".to_string()))?; } let text_pipeline_type = request.text_pipeline_type @@ -330,7 +290,7 @@ pub async fn edit_tts_model_handler( Ok(_) => {}, Err(err) => { warn!("Update W2L model edit DB error: {:?}", err); - return Err(EditTtsModelError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -348,7 +308,7 @@ async fn update_mod_details( moderator_user_token: &str, tts_model_token: &str, mysql_pool: &MySqlPool -) -> Result<(), EditTtsModelError> { +) -> Result<(), CommonWebError> { let is_public_listing_approved= request.is_public_listing_approved.unwrap_or(false); let is_locked_from_user_modification = request.is_locked_from_user_modification.unwrap_or(false); @@ -392,7 +352,7 @@ async fn update_mod_details( Ok(_) => Ok(()), Err(err) => { warn!("Update TTS model (mod details) DB error: {:?}", err); - Err(EditTtsModelError::ServerError) + Err(CommonWebError::from_error(err)) } } } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs index ddc83fb28..b82629cd5 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/edit_tts_result.rs @@ -3,11 +3,8 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest, HttpResponse}; use log::{error, warn}; @@ -17,7 +14,7 @@ use http_server_common::request::get_request_ip::get_request_ip; use mysql_queries::queries::tts::tts_results::edit_tts_result::{edit_tts_result, CreatorOrModFields, EditTtsResultArgs}; use mysql_queries::queries::tts::tts_results::query_tts_result::select_tts_result_by_token; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; @@ -32,49 +29,12 @@ pub struct EditTtsResultRequest { // ========== Author + Moderator options ========== pub creator_set_visibility: Option, } - -#[derive(Debug)] -pub enum EditTtsResultError { - BadInput(String), - NotAuthorized, - ResultNotFound, - ServerError, -} - -impl ResponseError for EditTtsResultError { - fn status_code(&self) -> StatusCode { - match *self { - EditTtsResultError::BadInput(_) => StatusCode::BAD_REQUEST, - EditTtsResultError::NotAuthorized => StatusCode::UNAUTHORIZED, - EditTtsResultError::ResultNotFound => StatusCode::NOT_FOUND, - EditTtsResultError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EditTtsResultError::BadInput(reason) => reason.to_string(), - EditTtsResultError::NotAuthorized=> "unauthorized".to_string(), - EditTtsResultError::ResultNotFound => "not found".to_string(), - EditTtsResultError::ServerError => "server error".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for EditTtsResultError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - pub async fn edit_tts_inference_result_handler( http_request: HttpRequest, path: Path, request: web::Json, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result { let maybe_user_session = server_state .session_checker @@ -82,14 +42,14 @@ pub async fn edit_tts_inference_result_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EditTtsResultError::ServerError + CommonWebError::from_error(e) })?; let user_session = match maybe_user_session { Some(session) => session, None => { warn!("not logged in"); - return Err(EditTtsResultError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } }; @@ -110,9 +70,9 @@ pub async fn edit_tts_inference_result_handler( let inference_result = match inference_result_query_result { Err(e) => { warn!("query error: {:?}", e); - return Err(EditTtsResultError::ServerError); + return Err(CommonWebError::from_anyhow_error(e)); } - Ok(None) => return Err(EditTtsResultError::ResultNotFound), + Ok(None) => return Err(CommonWebError::NotFound), Ok(Some(inference_result)) => inference_result, }; @@ -124,7 +84,7 @@ pub async fn edit_tts_inference_result_handler( if !is_author && !is_moderator { warn!("user is not allowed to edit result: {:?}", user_session.user_token); - return Err(EditTtsResultError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } // Author + Mod fields. @@ -133,7 +93,7 @@ pub async fn edit_tts_inference_result_handler( if let Some(visibility) = request.creator_set_visibility.as_deref() { creator_set_visibility = Visibility::from_str(visibility) - .map_err(|_| EditTtsResultError::BadInput("bad record visibility".to_string()))?; + .map_err(|_| CommonWebError::BadInputWithSimpleMessage("bad record visibility".to_string()))?; } let ip_address = get_request_ip(&http_request); @@ -157,7 +117,7 @@ pub async fn edit_tts_inference_result_handler( .await .map_err(|err| { error!("Update TTS result DB error: {:?}", err); - EditTtsResultError::ServerError + CommonWebError::from_anyhow_error(err) })?; Ok(simple_json_success()) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs index 1e62c0bbe..3434a89b6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/enqueue_upload_tts_model.rs @@ -3,16 +3,13 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::warn; use crate::http_server::validations::validate_model_title::validate_model_title; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; use config::bad_urls::is_bad_tts_model_download_url; use enums::by_table::generic_download_jobs::generic_download_type::GenericDownloadType; @@ -69,56 +66,19 @@ pub struct UploadTtsModelSuccessResponse { /// This is a transitional field to tell the frontend how to process the job checking. pub job_type: DownloadJobType, } - -#[derive(Debug)] -pub enum UploadTtsModelError { - BadInput(String), - MustBeLoggedIn, - ServerError, - RateLimited, -} - -impl ResponseError for UploadTtsModelError { - fn status_code(&self) -> StatusCode { - match *self { - UploadTtsModelError::BadInput(_) => StatusCode::BAD_REQUEST, - UploadTtsModelError::MustBeLoggedIn => StatusCode::UNAUTHORIZED, - UploadTtsModelError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - UploadTtsModelError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - UploadTtsModelError::BadInput(reason) => reason.to_string(), - UploadTtsModelError::MustBeLoggedIn => "user must be logged in".to_string(), - UploadTtsModelError::ServerError => "server error".to_string(), - UploadTtsModelError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for UploadTtsModelError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - pub async fn upload_tts_model_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result { if server_state.flags.disable_tts { - return Err(UploadTtsModelError::RateLimited); + return Err(CommonWebError::TooManyRequests); } if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request).await { - return Err(UploadTtsModelError::RateLimited); + return Err(CommonWebError::TooManyRequests); } let maybe_user_session = server_state @@ -127,23 +87,23 @@ pub async fn upload_tts_model_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - UploadTtsModelError::ServerError + CommonWebError::from_error(e) })?; let user_session = match maybe_user_session { Some(session) => session, None => { warn!("not logged in"); - return Err(UploadTtsModelError::MustBeLoggedIn); + return Err(CommonWebError::NotAuthorized); } }; if let Err(reason) = validate_idempotency_token(&request.idempotency_token) { - return Err(UploadTtsModelError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } if let Err(reason) = validate_model_title(&request.title) { - return Err(UploadTtsModelError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } let ip_address = get_request_ip(&http_request); @@ -155,11 +115,11 @@ pub async fn upload_tts_model_handler( match is_bad_tts_model_download_url(&download_url) { Ok(false) => {} // Ok case Ok(true) => { - return Err(UploadTtsModelError::BadInput("Bad model download URL".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Bad model download URL".to_string())); } Err(err) => { warn!("Error parsing url: {:?}", err); - return Err(UploadTtsModelError::BadInput("Bad model download URL".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Bad model download URL".to_string())); } } @@ -189,7 +149,7 @@ pub async fn upload_tts_model_handler( Ok(token) => token, Err(err) => { warn!("Error inserting new job record: {:?}", err); - return Err(UploadTtsModelError::ServerError); + return Err(CommonWebError::from_anyhow_error(err)); } }; download_job_type = DownloadJobType::LegacyTts; @@ -211,7 +171,7 @@ pub async fn upload_tts_model_handler( .await .map_err(|err| { warn!("New generic download creation DB error (for TTS model): {:?}", err); - UploadTtsModelError::ServerError + CommonWebError::from_anyhow_error(err) })?; job_token = download_job_token.to_string(); @@ -223,7 +183,7 @@ pub async fn upload_tts_model_handler( .await .map_err(|e| { warn!("error publishing event: {:?}", e); - UploadTtsModelError::ServerError + CommonWebError::from_anyhow_error(e) })?; let response = UploadTtsModelSuccessResponse { @@ -233,7 +193,7 @@ pub async fn upload_tts_model_handler( }; let body = serde_json::to_string(&response) - .map_err(|_e| UploadTtsModelError::ServerError)?; + ?; Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs index 10b5557d0..eefae3652 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/tts/get_tts_model_use_count.rs @@ -11,6 +11,7 @@ use redis::Commands; use redis_common::redis_keys::RedisKeys; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; /// For the URL PathInfo @@ -24,41 +25,16 @@ pub struct GetTtsModelUseCountSuccessResponse { pub success: bool, pub count: Option, } - -#[derive(Debug, Display)] -pub enum GetTtsModelUseCountError { - ServerError, - NotFoundError, -} - -impl ResponseError for GetTtsModelUseCountError { - fn status_code(&self) -> StatusCode { - match *self { - GetTtsModelUseCountError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR, - GetTtsModelUseCountError::NotFoundError => StatusCode::NOT_FOUND, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - GetTtsModelUseCountError::ServerError => "server error".to_string(), - GetTtsModelUseCountError::NotFoundError => "not found".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - pub async fn get_tts_model_use_count_handler( http_request: HttpRequest, path: Path, - server_state: web::Data>) -> Result + server_state: web::Data>) -> Result { let mut redis = server_state.redis_pool .get() .map_err(|e| { warn!("redis error: {:?}", e); - GetTtsModelUseCountError::ServerError + CommonWebError::from_error(e) })?; let redis_count_key = RedisKeys::tts_model_usage_count(&path.model_token); @@ -66,7 +42,7 @@ pub async fn get_tts_model_use_count_handler( let count : Option = redis.get(&redis_count_key) .map_err(|e| { warn!("redis error: {:?}", e); - GetTtsModelUseCountError::ServerError + CommonWebError::from_error(e) })?; let response = GetTtsModelUseCountSuccessResponse { @@ -75,7 +51,7 @@ pub async fn get_tts_model_use_count_handler( }; let body = serde_json::to_string(&response) - .map_err(|e| GetTtsModelUseCountError::ServerError)?; + .map_err(CommonWebError::from_error)?; Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs index d50b3911a..58cbd7447 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/edit_profile_handler.rs @@ -3,10 +3,7 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Path; use actix_web::{web, HttpRequest, HttpResponse}; use log::warn; @@ -14,7 +11,6 @@ use sqlx::MySqlPool; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use markdown::simple_markdown_to_html::simple_markdown_to_html; use mysql_queries::queries::users::user_profiles::edit_user_profile_as_account_holder::edit_user_profile_as_account_holder; use mysql_queries::queries::users::user_profiles::edit_user_profile_as_mod::edit_user_profile_as_mod; @@ -25,6 +21,7 @@ use redis_common::redis_cache_keys::RedisCacheKeys; use user_input_common::check_for_slurs::contains_slurs; use crate::http_server::session::session_checker::SessionChecker; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::validations::validate_profile_cashapp_username::{normalize_cashapp_username_for_storage, validate_profile_cashapp_username}; use crate::http_server::validations::validate_profile_discord_username::validate_profile_discord_username; use crate::http_server::validations::validate_profile_github_username::validate_profile_github_username; @@ -60,37 +57,7 @@ pub struct EditProfileRequest { pub struct EditProfileSuccessResponse { pub success: bool, } - -#[derive(Debug, Serialize)] -pub enum EditProfileError { - BadInput(String), - NotAuthorized, - UserNotFound, - ServerError, -} - -impl ResponseError for EditProfileError { - fn status_code(&self) -> StatusCode { - match *self { - EditProfileError::BadInput(_) => StatusCode::BAD_REQUEST, - EditProfileError::NotAuthorized => StatusCode::UNAUTHORIZED, - EditProfileError::UserNotFound => StatusCode::NOT_FOUND, - EditProfileError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for EditProfileError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - pub async fn edit_profile_handler( http_request: HttpRequest, path: Path, @@ -98,27 +65,27 @@ pub async fn edit_profile_handler( mysql_pool: web::Data, redis_ttl_cache: web::Data, session_checker: web::Data, -) -> Result +) -> Result { let maybe_user_session = session_checker .maybe_get_user_session(&http_request, &mysql_pool) .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EditProfileError::ServerError + CommonWebError::from_error(e) })?; let user_session = match maybe_user_session { Some(session) => session, None => { warn!("not logged in"); - return Err(EditProfileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } }; if user_session.is_banned { // Banned users can't edit anything - return Err(EditProfileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } let user_lookup_result = @@ -127,10 +94,10 @@ pub async fn edit_profile_handler( let user_record = match user_lookup_result { Ok(Some(result)) => result, - Ok(None) => return Err(EditProfileError::UserNotFound), + Ok(None) => return Err(CommonWebError::NotFound), Err(err) => { warn!("lookup error: {:?}", err); - return Err(EditProfileError::ServerError); + return Err(CommonWebError::from_anyhow_error(err)); } }; @@ -146,7 +113,7 @@ pub async fn edit_profile_handler( } if !editor_is_original_user && !editor_is_moderator { - return Err(EditProfileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } // Fields to set @@ -165,7 +132,7 @@ pub async fn edit_profile_handler( twitter_username = None; } else { if let Err(reason) = validate_profile_twitter_username(trimmed) { - return Err(EditProfileError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } let normalized = normalize_twitter_username_for_storage(trimmed); twitter_username = Some(normalized); @@ -178,7 +145,7 @@ pub async fn edit_profile_handler( twitch_username = None; } else { if let Err(reason) = validate_profile_twitch_username(trimmed) { - return Err(EditProfileError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } twitch_username = Some(trimmed); } @@ -190,7 +157,7 @@ pub async fn edit_profile_handler( discord_username = None; } else { if let Err(reason) = validate_profile_discord_username(trimmed) { - return Err(EditProfileError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } discord_username = Some(trimmed); } @@ -202,7 +169,7 @@ pub async fn edit_profile_handler( github_username = None; } else { if let Err(reason) = validate_profile_github_username(trimmed) { - return Err(EditProfileError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } github_username = Some(trimmed); } @@ -214,7 +181,7 @@ pub async fn edit_profile_handler( cashapp_username = None; } else { if let Err(reason) = validate_profile_cashapp_username(trimmed) { - return Err(EditProfileError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } let normalized = normalize_cashapp_username_for_storage(trimmed); cashapp_username = Some(normalized); @@ -227,7 +194,7 @@ pub async fn edit_profile_handler( website_url = None; } else { if let Err(reason) = validate_profile_website_url(trimmed) { - return Err(EditProfileError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } website_url = Some(trimmed); } @@ -235,7 +202,7 @@ pub async fn edit_profile_handler( if let Some(markdown) = request.profile_markdown.as_deref() { if contains_slurs(markdown) { - return Err(EditProfileError::BadInput("profile contains slurs".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("profile contains slurs".to_string())); } let markdown = markdown.trim().to_string(); @@ -294,7 +261,7 @@ pub async fn edit_profile_handler( Ok(_) => {}, Err(err) => { warn!("Profile edit DB error: {:?}", err); - return Err(EditProfileError::ServerError); + return Err(CommonWebError::from_anyhow_error(err)); } }; @@ -319,7 +286,7 @@ pub async fn edit_profile_handler( }; let body = serde_json::to_string(&response) - .map_err(|_e| EditProfileError::BadInput("".to_string()))?; + .map_err(|_e| CommonWebError::BadInputWithSimpleMessage("".to_string()))?; Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs index a4bf376c2..34659709d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_seed_vc_inference_handler.rs @@ -3,13 +3,10 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Json; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{info, warn}; use utoipa::ToSchema; @@ -30,10 +27,10 @@ use tokens::tokens::users::UserToken; use tts_common::priority::{FAKEYOU_DEFAULT_VALID_API_TOKEN_PRIORITY_LEVEL, FAKEYOU_INVESTOR_PRIORITY_LEVEL}; use crate::configs::app_startup::username_set::UsernameSet; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::http_server::deprecated_endpoints::investor_demo::demo_cookie::request_has_demo_cookie; use crate::http_server::session::lookup::user_session_extended::UserSessionExtended; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; // TODO: Temporary for investor demo @@ -62,57 +59,17 @@ pub struct InferSeedVcSuccessResponse { pub success: bool, pub inference_job_token: String, } - -#[derive(Debug, ToSchema)] -pub enum InferSeedVcError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, - NotFound, -} - -impl ResponseError for InferSeedVcError { - fn status_code(&self) -> StatusCode { - match *self { - InferSeedVcError::BadInput(_) => StatusCode::BAD_REQUEST, - InferSeedVcError::NotAuthorized => StatusCode::UNAUTHORIZED, - InferSeedVcError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - InferSeedVcError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - InferSeedVcError::NotFound => StatusCode::NOT_FOUND, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - InferSeedVcError::BadInput(reason) => reason.to_string(), - InferSeedVcError::NotAuthorized => "unauthorized".to_string(), - InferSeedVcError::ServerError => "server error".to_string(), - InferSeedVcError::RateLimited => "rate limited".to_string(), - InferSeedVcError::NotFound => "not found".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for InferSeedVcError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - #[utoipa::path( post, tag = "Voice Conversion", path = "/v1/voice_conversion/seed_vc_inference", responses( (status = 200, description = "Success response", body = InferSeedVcSuccessResponse), - (status = 400, description = "Bad input", body = InferSeedVcError), - (status = 401, description = "Not authorized", body = InferSeedVcError), - (status = 429, description = "Rate limited", body = InferSeedVcError), - (status = 500, description = "Server error", body = InferSeedVcError) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params(("request" = InferSeedVcRequest, description = "Payload for Request")) )] @@ -120,10 +77,10 @@ pub async fn enqueue_infer_seed_vc_handler( http_request: HttpRequest, request: Json, server_state: web::Data> -) -> Result, InferSeedVcError> { +) -> Result, CommonWebError> { if server_state.flags.disable_voice_conversion { - return Err(InferSeedVcError::RateLimited); + return Err(CommonWebError::TooManyRequests); } let mut is_from_api = false; @@ -137,7 +94,7 @@ pub async fn enqueue_infer_seed_vc_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - InferSeedVcError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -148,7 +105,7 @@ pub async fn enqueue_infer_seed_vc_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - InferSeedVcError::ServerError + CommonWebError::from_error(e) })?; if let Some(user_session) = maybe_user_session.as_ref() { @@ -215,7 +172,7 @@ pub async fn enqueue_infer_seed_vc_handler( if let Some(ref user) = maybe_user_session { if user.role.is_banned { warn!("User is not authorized to use Seed-VC because they are banned."); - return Err(InferSeedVcError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -249,7 +206,7 @@ pub async fn enqueue_infer_seed_vc_handler( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(InferSeedVcError::RateLimited); + return Err(CommonWebError::TooManyRequests); } } @@ -283,7 +240,7 @@ pub async fn enqueue_infer_seed_vc_handler( } else if source_media_token.starts_with(MediaFileToken::token_prefix()) { InferenceInputSourceTokenType::MediaFile } else { - return Err(InferSeedVcError::BadInput( + return Err(CommonWebError::BadInputWithSimpleMessage( "input token is not a media_upload or media_file token".to_string())); }; @@ -295,7 +252,7 @@ pub async fn enqueue_infer_seed_vc_handler( } else if reference_media_token.starts_with(MediaFileToken::token_prefix()) { InferenceInputSourceTokenType::MediaFile } else { - return Err(InferSeedVcError::BadInput( + return Err(CommonWebError::BadInputWithSimpleMessage( "reference token is not a media_upload or media_file token".to_string())); }; @@ -337,9 +294,9 @@ pub async fn enqueue_infer_seed_vc_handler( Err(err) => { warn!("New (generic) seed-vc inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(InferSeedVcError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(InferSeedVcError::ServerError); + return Err(CommonWebError::from_error(err)); } }; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_voice_conversion_inference_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_voice_conversion_inference_handler.rs index f9d565dba..0ba6685a7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_voice_conversion_inference_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_conversion/enqueue_voice_conversion_inference_handler.rs @@ -3,13 +3,10 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::web::Json; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpRequest}; use log::{error, info, warn}; use redis::Commands; use sqlx::pool::PoolConnection; @@ -35,8 +32,8 @@ use tokens::tokens::users::UserToken; use tts_common::priority::FAKEYOU_INVESTOR_PRIORITY_LEVEL; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::deprecated_endpoints::investor_demo::demo_cookie::request_has_demo_cookie; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::memory_cache::model_token_to_info_cache::{ModelInfoForInferenceJob, ModelTokenToInfoCache}; use crate::state::server_state::ServerState; @@ -97,54 +94,17 @@ pub struct EnqueueVoiceConversionInferenceSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug, ToSchema)] -pub enum EnqueueVoiceConversionInferenceError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueVoiceConversionInferenceError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueVoiceConversionInferenceError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueVoiceConversionInferenceError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueVoiceConversionInferenceError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueVoiceConversionInferenceError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueVoiceConversionInferenceError::BadInput(reason) => reason.to_string(), - EnqueueVoiceConversionInferenceError::NotAuthorized => "unauthorized".to_string(), - EnqueueVoiceConversionInferenceError::ServerError => "server error".to_string(), - EnqueueVoiceConversionInferenceError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for EnqueueVoiceConversionInferenceError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - #[utoipa::path( post, tag = "Voice Conversion", path = "/v1/voice_conversion/inference", responses( (status = 200, description = "Success", body = EnqueueVoiceConversionInferenceSuccessResponse), - (status = 400, description = "Bad input", body = EnqueueVoiceConversionInferenceError), - (status = 401, description = "Not authorized", body = EnqueueVoiceConversionInferenceError), - (status = 429, description = "Rate limited", body = EnqueueVoiceConversionInferenceError), - (status = 500, description = "Server error", body = EnqueueVoiceConversionInferenceError), + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError), ), params( ("request" = EnqueueVoiceConversionInferenceRequest, description = "Payload for Request"), @@ -154,11 +114,11 @@ pub async fn enqueue_voice_conversion_inference_handler( http_request: HttpRequest, request: Json, server_state: web::Data> -) -> Result, EnqueueVoiceConversionInferenceError> +) -> Result, CommonWebError> { if server_state.flags.disable_voice_conversion { - return Err(EnqueueVoiceConversionInferenceError::RateLimited); + return Err(CommonWebError::TooManyRequests); } let mut maybe_user_token : Option = None; @@ -170,7 +130,7 @@ pub async fn enqueue_voice_conversion_inference_handler( .await .map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_error(err) })?; // ==================== AVT TOKEN ==================== // @@ -185,7 +145,7 @@ pub async fn enqueue_voice_conversion_inference_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_error(e) })?; if let Some(user_session) = maybe_user_session.as_ref() { @@ -239,7 +199,7 @@ pub async fn enqueue_voice_conversion_inference_handler( if let Some(ref user) = maybe_user_session { if user.role.is_banned { - return Err(EnqueueVoiceConversionInferenceError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -257,7 +217,7 @@ pub async fn enqueue_voice_conversion_inference_handler( } if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueVoiceConversionInferenceError::RateLimited); + return Err(CommonWebError::TooManyRequests); } } @@ -282,7 +242,7 @@ pub async fn enqueue_voice_conversion_inference_handler( } else if media_token.starts_with(MediaFileToken::token_prefix()) { InferenceInputSourceTokenType::MediaFile } else { - return Err(EnqueueVoiceConversionInferenceError::BadInput( + return Err(CommonWebError::BadInputWithSimpleMessage( "input token is not a media_upload or media_file token".to_string())); }; @@ -292,7 +252,7 @@ pub async fn enqueue_voice_conversion_inference_handler( .get() .map_err(|e| { error!("redis error: {:?}", e); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_error(e) })?; let redis_count_key = RedisKeys::web_vc_model_usage_count(&model_token); @@ -300,7 +260,7 @@ pub async fn enqueue_voice_conversion_inference_handler( redis.incr::<_, _, ()>(&redis_count_key, 1) .map_err(|e| { warn!("redis error: {:?}", e); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_error(e) })?; let ip_address = get_request_ip(&http_request); @@ -340,7 +300,7 @@ pub async fn enqueue_voice_conversion_inference_handler( _ => { // In theory, this shouldn't catch anything. error!("wrong model type for voice conversion: {:?}", model_inference_info.job_model_type); - return Err(EnqueueVoiceConversionInferenceError::ServerError) + return Err(CommonWebError::server_error_with_message("wrong model type for voice conversion")) } }; @@ -383,9 +343,9 @@ pub async fn enqueue_voice_conversion_inference_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueVoiceConversionInferenceError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueVoiceConversionInferenceError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -395,7 +355,7 @@ pub async fn enqueue_voice_conversion_inference_handler( .await .map_err(|e| { warn!("error publishing event: {:?}", e); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_anyhow_error(e) })?; Ok(Json(EnqueueVoiceConversionInferenceSuccessResponse { @@ -408,12 +368,12 @@ async fn lookup_model_info( model_token: &str, cache: &ModelTokenToInfoCache, mysql_connection: &mut PoolConnection, -) -> Result { +) -> Result { let maybe_model_inference_info = cache .get_info(model_token) .map_err(|err| { error!("in-memory cache error: {:?}", err); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_anyhow_error(err) })?; if let Some(inference_info) = maybe_model_inference_info { @@ -425,7 +385,7 @@ async fn lookup_model_info( .await .map_err(|err| { error!("model lookup error: {:?}", err); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_anyhow_error(err) })?; let model_info = match result { @@ -449,14 +409,14 @@ async fn lookup_model_info( } _ => { error!("wrong model type for inference: {:?}", model_info); - return Err(EnqueueVoiceConversionInferenceError::BadInput("wrong model type for inference".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("wrong model type for inference".to_string())); } }; cache.insert_one(model_token, &inference_info) .map_err(|err| { error!("cache insertion error: {:?}", err); - EnqueueVoiceConversionInferenceError::ServerError + CommonWebError::from_anyhow_error(err) })?; Ok(inference_info) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs index 1b3d92c5c..f4892bb72 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/inference/enqueue_tts_request.rs @@ -2,11 +2,8 @@ #![forbid(unused_mut)] #![forbid(unused_variables)] -use std::fmt::Debug; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; use log::warn; use serde::Deserialize; @@ -34,7 +31,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::users::UserToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; -use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::state::server_state::ServerState; /// Debug requests can get routed to special "debug-only" workers, which can @@ -58,43 +55,6 @@ pub struct EnqueueTTSRequestSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug,ToSchema)] -pub enum EnqueueTTSRequestError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueTTSRequestError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueTTSRequestError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueTTSRequestError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueTTSRequestError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueTTSRequestError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueTTSRequestError::BadInput(reason) => reason.to_string(), - EnqueueTTSRequestError::NotAuthorized => "unauthorized".to_string(), - EnqueueTTSRequestError::ServerError => "server error".to_string(), - EnqueueTTSRequestError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -impl std::fmt::Display for EnqueueTTSRequestError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - // Implementation for enqueuing a TTS request // Reference enqueue_infer_tts_handler.rs for checks: rate limiting / user sessions // insert generic inference job.rs @@ -105,10 +65,10 @@ impl std::fmt::Display for EnqueueTTSRequestError { path = "/v1/voice_designer/inference/enqueue_tts", responses( (status = 200, description = "Enqueue TTS generically", body = EnqueueTTSRequestSuccessResponse), - (status = 400, description = "Bad input", body = EnqueueTTSRequestError), - (status = 401, description = "Not authorized", body = EnqueueTTSRequestError), - (status = 429, description = "Rate limited", body = EnqueueTTSRequestError), - (status = 500, description = "Server error", body = EnqueueTTSRequestError) + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 429, description = "Rate limited", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError) ), params( ("request" = EnqueueTTSRequest, description = "Payload for TTS Request") @@ -118,13 +78,13 @@ pub async fn enqueue_tts_request( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result { let mut maybe_user_token: Option = None; let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueTTSRequestError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -133,7 +93,7 @@ pub async fn enqueue_tts_request( .maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueTTSRequestError::ServerError + CommonWebError::from_error(e) })?; if let Some(user_session) = maybe_user_session.as_ref() { @@ -161,7 +121,7 @@ pub async fn enqueue_tts_request( if let Some(ref user) = maybe_user_session { if user.role.is_banned { - return Err(EnqueueTTSRequestError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -173,7 +133,7 @@ pub async fn enqueue_tts_request( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueTTSRequestError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // Get up IP address @@ -223,9 +183,9 @@ pub async fn enqueue_tts_request( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueTTSRequestError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueTTSRequestError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -234,7 +194,7 @@ pub async fn enqueue_tts_request( inference_job_token: job_token, }; - let body = serde_json::to_string(&response).map_err(|_e| EnqueueTTSRequestError::ServerError)?; + let body = serde_json::to_string(&response)?; // Error handling 101 rust result type returned like so. Ok(HttpResponse::Ok().content_type("application/json").body(body)) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/upload_zs_sample.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/upload_zs_sample.rs index 94f1db0ad..d0b49b815 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/upload_zs_sample.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voice_dataset_samples/upload_zs_sample.rs @@ -23,6 +23,7 @@ use tokens::tokens::zs_voice_dataset_samples::ZsVoiceDatasetSampleToken; use tokens::tokens::zs_voice_datasets::ZsVoiceDatasetToken; use crate::http_server::deprecated_endpoints::media_uploads::common::drain_multipart_request::{drain_multipart_request, MediaSource}; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; @@ -32,39 +33,7 @@ pub struct UploadSampleResponse { pub sample_token: ZsVoiceDatasetSampleToken, pub media_file_token: MediaFileToken, } - -#[derive(Debug, Serialize)] -pub enum UploadSampleError { - BadInput(String), - NotAuthorized, - MustBeLoggedIn, - ServerError, - RateLimited, -} - -impl ResponseError for UploadSampleError { - fn status_code(&self) -> StatusCode { - match *self { - UploadSampleError::BadInput(_) => StatusCode::BAD_REQUEST, - UploadSampleError::NotAuthorized => StatusCode::UNAUTHORIZED, - UploadSampleError::MustBeLoggedIn => StatusCode::UNAUTHORIZED, - UploadSampleError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - UploadSampleError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - // NB: Not using derive_more::Display since Clion doesn't understand it. -impl std::fmt::Display for UploadSampleError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - static ALLOWED_AUDIO_MIME_TYPES : Lazy> = Lazy::new(|| { HashSet::from([ "audio/aac", @@ -87,14 +56,14 @@ pub async fn upload_zs_sample_handler( http_request: HttpRequest, server_state: web::Data>, mut multipart_payload: Multipart, -) -> Result { +) -> Result { let mut mysql_connection = server_state.mysql_pool .acquire() .await .map_err(|err| { error!("MySql pool error: {:?}", err); - UploadSampleError::ServerError + CommonWebError::from_error(err) })?; // ==================== READ SESSION ==================== // @@ -105,14 +74,14 @@ pub async fn upload_zs_sample_handler( .await .map_err(|e| { error!("Session checker error: {:?}", e); - UploadSampleError::ServerError + CommonWebError::from_error(e) })?; // ==================== BANNED USERS ==================== // if let Some(ref user) = maybe_user_session { if user.is_banned { - return Err(UploadSampleError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -124,11 +93,11 @@ pub async fn upload_zs_sample_handler( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(UploadSampleError::RateLimited); + return Err(CommonWebError::TooManyRequests); } if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request).await { - return Err(UploadSampleError::RateLimited); + return Err(CommonWebError::TooManyRequests); } // ==================== READ MULTIPART REQUEST ==================== // @@ -137,11 +106,11 @@ pub async fn upload_zs_sample_handler( .await .map_err(|e| { // TODO: Error handling could be nicer. - UploadSampleError::BadInput("bad request".to_string()) + CommonWebError::BadInputWithSimpleMessage("bad request".to_string()) })?; let uuid_idempotency_token = upload_sample_request.uuid_idempotency_token - .ok_or(UploadSampleError::BadInput("no uuid".to_string()))?; + .ok_or(CommonWebError::BadInputWithSimpleMessage("no uuid".to_string()))?; let maybe_existing_upload = get_dataset_sample_by_uuid_with_connection(&uuid_idempotency_token, &mut mysql_connection) @@ -150,7 +119,7 @@ pub async fn upload_zs_sample_handler( match maybe_existing_upload { Err(err) => { error!("Error checking for previous upload: {:?}", err); - return Err(UploadSampleError::ServerError); + return Err(CommonWebError::from_anyhow_error(err)); } Ok(Some(previous_record)) => { return success_response( @@ -164,7 +133,7 @@ pub async fn upload_zs_sample_handler( } if let Err(reason) = validate_idempotency_token_format(&uuid_idempotency_token) { - return Err(UploadSampleError::BadInput(reason)); + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); } let creator_set_visibility = maybe_user_session @@ -191,19 +160,19 @@ pub async fn upload_zs_sample_handler( .flatten(); let dataset_token = match upload_sample_request.dataset_token { - None => return Err(UploadSampleError::BadInput("missing dataset token".to_string())), + None => return Err(CommonWebError::BadInputWithSimpleMessage("missing dataset token".to_string())), Some(token) => ZsVoiceDatasetToken::new_from_str(&token), }; let bytes = match upload_sample_request.file_bytes { - None => return Err(UploadSampleError::BadInput("missing file contents".to_string())), + None => return Err(CommonWebError::BadInputWithSimpleMessage("missing file contents".to_string())), Some(bytes) => bytes, }; let hash = sha256_hash_bytes(&bytes) .map_err(|io_error| { error!("Problem hashing bytes: {:?}", io_error); - UploadSampleError::ServerError + CommonWebError::from_anyhow_error(io_error) })?; let file_size_bytes = bytes.len(); @@ -221,7 +190,7 @@ pub async fn upload_zs_sample_handler( .filter(|c| c.is_ascii()) .filter(|c| c.is_alphanumeric() || *c == '/') .collect::(); - return Err(UploadSampleError::BadInput(format!("unpermitted mime type: {}", &filtered_mimetype))); + return Err(CommonWebError::BadInputWithSimpleMessage(format!("unpermitted mime type: {}", &filtered_mimetype))); } // NB: .aiff (audio/aiff) isn't supported by Symphonia: @@ -285,7 +254,7 @@ pub async fn upload_zs_sample_handler( None ).map_err(|e| { warn!("file decoding error: {:?}", e); - UploadSampleError::BadInput("could not decode file".to_string()) + CommonWebError::BadInputWithSimpleMessage("could not decode file".to_string()) })?; maybe_duration_millis = basic_info.duration_millis; @@ -297,7 +266,7 @@ pub async fn upload_zs_sample_handler( Some(m) => m, None => { warn!("Invalid mimetype: {:?}", maybe_mimetype); - return Err(UploadSampleError::BadInput(format!("unknown mimetype: {:?}", maybe_mimetype))) + return Err(CommonWebError::BadInputWithSimpleMessage(format!("unknown mimetype: {:?}", maybe_mimetype))) }, }; @@ -312,7 +281,7 @@ pub async fn upload_zs_sample_handler( Some(m) => m, None => { warn!("Missing mimetype!"); - return Err(UploadSampleError::BadInput("Missing mimetype".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Missing mimetype".to_string())); }, }; @@ -335,7 +304,7 @@ pub async fn upload_zs_sample_handler( .await .map_err(|e| { warn!("Upload media bytes to bucket error: {:?}", e); - UploadSampleError::ServerError + CommonWebError::from_anyhow_error(e) })?; let (dataset_sample_token, media_file_token, _record_id) = insert_dataset_sample_and_media_file(InsertDatasetSampleAndMediaFileArgs { @@ -362,7 +331,7 @@ pub async fn upload_zs_sample_handler( .await .map_err(|err| { warn!("New generic download creation DB error: {:?}", err); - UploadSampleError::ServerError + CommonWebError::from_anyhow_error(err) })?; info!("new media file token: {:?} dataset token: {:?}", media_file_token, dataset_sample_token); @@ -376,7 +345,7 @@ pub async fn upload_zs_sample_handler( fn success_response( sample_token: ZsVoiceDatasetSampleToken, media_file_token: MediaFileToken -) -> Result { +) -> Result { let response = UploadSampleResponse { success: true, @@ -385,7 +354,7 @@ fn success_response( }; let body = serde_json::to_string(&response) - .map_err(|e| UploadSampleError::ServerError)?; + .map_err(CommonWebError::from_error)?; return Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs index 3cd04a476..b26dd52eb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/voice_designer/voices/create_voice.rs @@ -32,6 +32,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken; use tokens::tokens::users::UserToken; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::state::server_state::ServerState; @@ -46,43 +47,6 @@ pub struct EnqueueCreateVoiceRequestSuccessResponse { pub success: bool, pub inference_job_token: InferenceJobToken, } - -#[derive(Debug)] -pub enum EnqueueCreateVoiceRequestError { - BadInput(String), - NotAuthorized, - ServerError, - RateLimited, -} - -impl ResponseError for EnqueueCreateVoiceRequestError { - fn status_code(&self) -> StatusCode { - match *self { - EnqueueCreateVoiceRequestError::BadInput(_) => StatusCode::BAD_REQUEST, - EnqueueCreateVoiceRequestError::NotAuthorized => StatusCode::UNAUTHORIZED, - EnqueueCreateVoiceRequestError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - EnqueueCreateVoiceRequestError::RateLimited => StatusCode::TOO_MANY_REQUESTS, - } - } - - fn error_response(&self) -> HttpResponse { - let error_reason = match self { - EnqueueCreateVoiceRequestError::BadInput(reason) => reason.to_string(), - EnqueueCreateVoiceRequestError::NotAuthorized => "unauthorized".to_string(), - EnqueueCreateVoiceRequestError::ServerError => "server error".to_string(), - EnqueueCreateVoiceRequestError::RateLimited => "rate limited".to_string(), - }; - - to_simple_json_error(&error_reason, self.status_code()) - } -} - -impl std::fmt::Display for EnqueueCreateVoiceRequestError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - /// Debug requests can get routed to special "debug-only" workers, which can /// be used to trial new code, run debugging, etc. const DEBUG_HEADER_NAME: &str = "enable-debug-mode"; @@ -96,7 +60,7 @@ pub async fn create_voice_handler( http_request: HttpRequest, request: web::Json, server_state: web::Data> -) -> Result { +) -> Result { // voice data set token ... zsd_wb08asm0m4a4cz42wwqked6tfg6e e.g // need this for the job @@ -107,7 +71,7 @@ pub async fn create_voice_handler( let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| { warn!("MySql pool error: {:?}", err); - EnqueueCreateVoiceRequestError::ServerError + CommonWebError::from_error(err) })?; // ==================== USER SESSION ==================== // @@ -116,7 +80,7 @@ pub async fn create_voice_handler( .maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await .map_err(|e| { warn!("Session checker error: {:?}", e); - EnqueueCreateVoiceRequestError::ServerError + CommonWebError::from_error(e) })?; if let Some(user_session) = maybe_user_session.as_ref() { @@ -144,7 +108,7 @@ pub async fn create_voice_handler( if let Some(ref user) = maybe_user_session { if user.role.is_banned { - return Err(EnqueueCreateVoiceRequestError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } } @@ -156,7 +120,7 @@ pub async fn create_voice_handler( }; if let Err(_err) = rate_limiter.rate_limit_request(&http_request).await { - return Err(EnqueueCreateVoiceRequestError::RateLimited); + return Err(CommonWebError::TooManyRequests); } info!("Received payload for voice creation."); @@ -208,9 +172,9 @@ pub async fn create_voice_handler( Err(err) => { warn!("New generic inference job creation DB error: {:?}", err); if err.had_duplicate_idempotency_token() { - return Err(EnqueueCreateVoiceRequestError::BadInput("Duplicate idempotency token".to_string())); + return Err(CommonWebError::BadInputWithSimpleMessage("Duplicate idempotency token".to_string())); } - return Err(EnqueueCreateVoiceRequestError::ServerError); + return Err(CommonWebError::from_error(err)); } }; @@ -222,7 +186,7 @@ pub async fn create_voice_handler( let body = serde_json ::to_string(&response) - .map_err(|_e| EnqueueCreateVoiceRequestError::ServerError)?; + ?; // Error handling 101 rust result type returned like so. Ok(HttpResponse::Ok().content_type("application/json").body(body)) }