Compare commits

...
Author SHA1 Message Date
Brandon Thomas a0266118b7 cleanup error types 2026-05-25 21:35:50 -04:00
Brandon Thomas 02978dca1e wip migration 2026-05-25 21:08:04 -04:00
Brandon Thomas a1a119276a test 2026-05-25 20:58:13 -04:00
Brandon Thomas 86a2d1dbc9 429 variant 2026-05-25 20:53:17 -04:00
22 changed files with 329 additions and 1058 deletions
@@ -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,
@@ -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");
@@ -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<Arc<ServerState>>,
mut multipart_payload: Multipart,
) -> Result<HttpResponse, CreateSceneError> {
) -> Result<HttpResponse, CommonWebError> {
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")
@@ -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<EnqueueImageGenRequest>,
server_state: Data<Arc<ServerState>>
) -> Result<HttpResponse, EnqueueImageGenRequestError> {
) -> Result<HttpResponse, CommonWebError> {
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<EnqueueImageGenRequest>,
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(())
@@ -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<EnqueueFaceFusionWorkflowRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<EnqueueFaceFusionWorkflowSuccessResponse>, EnqueueFaceFusionWorkflowError>
) -> Result<Json<EnqueueFaceFusionWorkflowSuccessResponse>, 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));
}
};
@@ -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<EnqueueLivePortraitWorkflowRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<Json<EnqueueLivePortraitWorkflowSuccessResponse>, EnqueueLivePortraitWorkflowError>
server_state: web::Data<Arc<ServerState>>) -> Result<Json<EnqueueLivePortraitWorkflowSuccessResponse>, 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));
}
};
@@ -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<EnqueueComfyRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, EnqueueComfyError>
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CommonWebError>
{
let mut maybe_user_token : Option<UserToken> = 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")
@@ -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<EnqueueVideoStyleTransferRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, EnqueueVideoStyleTransferError>
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CommonWebError>
{
// ==================== 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")
@@ -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<EnqueueWorkFlowRequest>,
server_state: Data<Arc<ServerState>>
) -> Result<HttpResponse, EnqueueWorkFlowRequestError> {
) -> Result<HttpResponse, CommonWebError> {
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<UserToken> = 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))
@@ -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<RedeemBetaKeyRequest>,
server_state: web::Data<Arc<ServerState>>,
) -> Result<HttpResponse, RedeemBetaKeyError>
) -> Result<HttpResponse, CommonWebError>
{
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<serde_json::Error>` 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(())
@@ -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<EnqueueGptSovitsModelDownloadRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<Json<EnqueueGptSovitsModelDownloadSuccessResponse>, EnqueueGptSovitsModelDownloadError>
server_state: web::Data<Arc<ServerState>>) -> Result<Json<EnqueueGptSovitsModelDownloadSuccessResponse>, 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));
}
};
@@ -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<EnqueueStudioGen2Request>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<EnqueueStudioGen2Response>, EnqueueStudioGen2Error> {
) -> Result<Json<EnqueueStudioGen2Response>, 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));
}
};
@@ -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<bool>,
pub maybe_custom_m_factor: Option<f64>,
}
#[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<EditTtsModelPathInfo>,
request: Json<EditTtsModelRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, EditTtsModelError>
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CommonWebError>
{
// 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))
}
}
}
@@ -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<String>,
}
#[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<EditTtsResultPathInfo>,
request: web::Json<EditTtsResultRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, EditTtsResultError>
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CommonWebError>
{
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())
@@ -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<UploadTtsModelRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, UploadTtsModelError> {
) -> Result<HttpResponse, CommonWebError> {
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")
@@ -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<u64>,
}
#[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<GetTtsModelUseCountPathInfo>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, GetTtsModelUseCountError>
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CommonWebError>
{
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<u64> = 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")
@@ -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<EditProfilePathInfo>,
@@ -98,27 +65,27 @@ pub async fn edit_profile_handler(
mysql_pool: web::Data<MySqlPool>,
redis_ttl_cache: web::Data<RedisTtlCache>,
session_checker: web::Data<SessionChecker>,
) -> Result<HttpResponse, EditProfileError>
) -> Result<HttpResponse, CommonWebError>
{
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")
@@ -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<InferSeedVcRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<InferSeedVcSuccessResponse>, InferSeedVcError> {
) -> Result<Json<InferSeedVcSuccessResponse>, 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));
}
};
@@ -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<EnqueueVoiceConversionInferenceRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<EnqueueVoiceConversionInferenceSuccessResponse>, EnqueueVoiceConversionInferenceError>
) -> Result<Json<EnqueueVoiceConversionInferenceSuccessResponse>, CommonWebError>
{
if server_state.flags.disable_voice_conversion {
return Err(EnqueueVoiceConversionInferenceError::RateLimited);
return Err(CommonWebError::TooManyRequests);
}
let mut maybe_user_token : Option<UserToken> = 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<MySql>,
) -> Result<ModelInfoForInferenceJob, EnqueueVoiceConversionInferenceError> {
) -> Result<ModelInfoForInferenceJob, CommonWebError> {
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)
@@ -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<EnqueueTTSRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, EnqueueTTSRequestError> {
) -> Result<HttpResponse, CommonWebError> {
let mut maybe_user_token: Option<UserToken> = 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))
@@ -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<HashSet<&'static str>> = Lazy::new(|| {
HashSet::from([
"audio/aac",
@@ -87,14 +56,14 @@ pub async fn upload_zs_sample_handler(
http_request: HttpRequest,
server_state: web::Data<Arc<ServerState>>,
mut multipart_payload: Multipart,
) -> Result<HttpResponse, UploadSampleError> {
) -> Result<HttpResponse, CommonWebError> {
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::<String>();
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<HttpResponse, UploadSampleError> {
) -> Result<HttpResponse, CommonWebError> {
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")
@@ -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<EnqueueCreateVoiceRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, EnqueueCreateVoiceRequestError> {
) -> Result<HttpResponse, CommonWebError> {
// 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))
}