report 400s instead of 500s on duplicate idempotency tokens

This commit is contained in:
Brandon Thomas committed 2024-08-09 01:48:31 -04:00
1 parent 61033712c8
commit d701f0a437
21 files changed
+125 -9

No files matched your search

@@ -0,0 +1,47 @@
#[derive(Debug)]
pub enum DatabaseQueryError {
/// A duplicate idempotency token error occurred.
/// This should be surfaced as a 400 to the user.
IdempotencyDuplicateKeyError,
/// An uncategorized error occurred.
/// This will likely result in a 500 for the user.
SqlxError(sqlx::Error),
/// An uncategorized non-database error occurred.
/// This will likely result in a 500 for the user.
AnyhowError(anyhow::Error),
}
impl From<anyhow::Error> for DatabaseQueryError {
fn from(err: anyhow::Error) -> Self {
DatabaseQueryError::AnyhowError(err)
}
}
impl From<sqlx::Error> for DatabaseQueryError {
fn from(err: sqlx::Error) -> Self {
DatabaseQueryError::SqlxError(err)
}
}
impl DatabaseQueryError {
/// Whether we should surface this failure as a 400 to the user.
/// This could be any field (for now we only have the idempotency token).
pub fn is_400_error(&self) -> bool {
match self {
DatabaseQueryError::IdempotencyDuplicateKeyError => true,
_ => false,
}
}
/// Whether we should surface this failure as a 400 to the user.
/// Specifically, if we know it was the idempotency token.
pub fn had_duplicate_idempotency_token(&self) -> bool {
match self {
DatabaseQueryError::IdempotencyDuplicateKeyError => true,
_ => false,
}
}
}
@@ -0,0 +1 @@
pub mod database_query_error;
+1
View File
@@ -7,6 +7,7 @@ pub mod common_inputs;
pub mod complex_models;
pub mod composite_keys;
pub mod config;
pub mod errors;
pub mod helpers;
pub mod mediators;
pub mod payloads;
@@ -1,4 +1,5 @@
use anyhow::anyhow;
use sqlx::error::DatabaseError;
use sqlx::MySqlPool;
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
@@ -6,12 +7,12 @@ use enums::by_table::generic_inference_jobs::inference_input_source_token_type::
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
use enums::common::visibility::Visibility;
use errors::AnyhowResult;
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
use crate::errors::database_query_error::DatabaseQueryError;
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
pub struct InsertGenericInferenceArgs<'a> {
@@ -30,7 +31,7 @@ pub struct InsertGenericInferenceArgs<'a> {
pub maybe_input_source_token_type: Option<InferenceInputSourceTokenType>,
// For jobs that perform "downloads", this is the URL to download.
// NB: Some jobs aren't using this field yet and will pack the URL inside of
// NB: Some jobs aren't using this field yet and will pack the URL inside
// the "GenericInferenceArgs" field. The goal is to migrate them to this
// top-level field eventually.
pub maybe_download_url: Option<&'a str>,
@@ -58,7 +59,9 @@ pub struct InsertGenericInferenceArgs<'a> {
pub mysql_pool: &'a MySqlPool,
}
pub async fn insert_generic_inference_job(args: InsertGenericInferenceArgs<'_>) -> AnyhowResult<(InferenceJobToken, u64)> {
pub async fn insert_generic_inference_job(args: InsertGenericInferenceArgs<'_>)
-> Result<(InferenceJobToken, u64), DatabaseQueryError>
{
let job_token = InferenceJobToken::generate();
let serialized_args_payload = serde_json::ser::to_string(&args.maybe_inference_args)
@@ -157,7 +160,19 @@ SET
res.last_insert_id()
},
Err(err) => {
return Err(anyhow!("error inserting new generic inference job: {:?}", err));
if let Some(db_err) = err.as_database_error() {
// NB: SQLSTATE[23000]: Integrity constraint violation
// NB: MySQL Error Code 1062: Duplicate key insertion (this is harder to access)
let is_integrity_violation = db_err.code().as_deref() == Some("23000");
let is_duplicate_key = db_err.message().contains("Duplicate entry");
let is_idempotency_error = db_err.message().contains("uuid_idempotency_token");
if is_integrity_violation && is_duplicate_key && is_idempotency_error {
return Err(DatabaseQueryError::IdempotencyDuplicateKeyError);
}
}
return Err(DatabaseQueryError::SqlxError(err));
}
};
@@ -326,6 +326,9 @@ pub async fn enqueue_face_animation_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(EnqueueFaceAnimationError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(EnqueueFaceAnimationError::ServerError);
}
};
@@ -229,6 +229,9 @@ pub async fn enqueue_rerender_animation_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(EnqueueRerenderAnimationError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(EnqueueRerenderAnimationError::ServerError);
}
};
@@ -220,6 +220,9 @@ pub async fn enqueue_fbx_to_gltf_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(EnqueueFbxToGltfRequestError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(EnqueueFbxToGltfRequestError::ServerError);
}
};
@@ -278,6 +278,9 @@ pub async fn enqueue_render_engine_scene_to_video_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(EnqueueBvhToWorkflowRequestError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(EnqueueBvhToWorkflowRequestError::ServerError);
}
};
@@ -440,6 +440,9 @@ pub async fn enqueue_image_generation_request(
Ok((job_token, _id)) => job_token,
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(EnqueueImageGenRequestError::ServerError);
}
};
@@ -233,6 +233,9 @@ pub async fn enqueue_mocapnet_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(EnqueueMocapnetError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(EnqueueMocapnetError::ServerError);
}
};
@@ -206,6 +206,9 @@ pub async fn enqueue_gptsovits_model_download_handler(
Ok((job_token, _id)) => job_token,
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(EnqueueGptSovitsModelDownloadError::ServerError);
}
};
@@ -448,6 +448,9 @@ pub async fn enqueue_infer_tts_handler(
Ok((inference_job_token, _id)) => inference_job_token,
Err(err) => {
warn!("New (generic) tts inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(InferTtsError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(InferTtsError::ServerError);
}
};
@@ -388,6 +388,9 @@ pub async fn enqueue_voice_conversion_inference_handler(
Ok((job_token, _id)) => job_token,
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(EnqueueVoiceConversionInferenceError::ServerError);
}
};
@@ -20,14 +20,14 @@ use enums::common::visibility::Visibility;
use http_server_common::request::get_request_header_optional::get_request_header_optional;
use http_server_common::request::get_request_ip::get_request_ip;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{
GenericInferenceArgs,
InferenceCategoryAbbreviated,
PolymorphicInferenceArgs,
GenericInferenceArgs,
InferenceCategoryAbbreviated,
PolymorphicInferenceArgs,
};
use mysql_queries::payloads::generic_inference_args::tts_payload::TTSArgs;
use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{
insert_generic_inference_job,
InsertGenericInferenceArgs,
insert_generic_inference_job,
InsertGenericInferenceArgs,
};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::users::UserToken;
@@ -220,6 +220,9 @@ pub async fn enqueue_tts_request(
Ok((job_token, _id)) => job_token,
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(EnqueueTTSRequestError::ServerError);
}
};
@@ -206,6 +206,9 @@ pub async fn create_voice_handler(
Ok((job_token, _id)) => job_token,
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(EnqueueCreateVoiceRequestError::ServerError);
}
};
@@ -30,6 +30,7 @@ use tokens::tokens::media_files::MediaFileToken;
use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session;
use crate::configs::plans::plan_category::PlanCategory;
use crate::http_server::endpoints::workflows::enqueue::vst_common::vst_error::VstError;
use crate::http_server::headers::get_routing_tag_header::get_routing_tag_header;
use crate::http_server::headers::has_debug_header::has_debug_header;
use crate::http_server::requests::get_request_domain_branding::{DomainBranding, get_request_domain_branding};
@@ -284,6 +285,9 @@ pub async fn enqueue_live_portrait_workflow_handler(
Ok((job_token, _id)) => job_token,
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(EnqueueLivePortraitWorkflowError::ServerError);
}
};
@@ -333,6 +333,9 @@ pub async fn enqueue_studio_workflow_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(VstError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(VstError::ServerError);
}
};
@@ -348,6 +348,9 @@ pub async fn enqueue_video_style_transfer_workflow_handler(
Ok((job_token, _id)) => job_token,
Err(err) => {
warn!("New generic inference job creation DB error: {:?}", err);
if err.had_duplicate_idempotency_token() {
return Err(VstError::BadInput("Duplicate idempotency token".to_string()));
}
return Err(VstError::ServerError);
}
};
@@ -324,6 +324,9 @@ pub async fn enqueue_comfy_ui_handler(
Ok((job_token, _id)) => job_token,
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(EnqueueComfyError::ServerError);
}
};
@@ -477,6 +477,9 @@ pub async fn enqueue_video_style_transfer_handler(
Ok((job_token, _id)) => job_token,
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(EnqueueVideoStyleTransferError::ServerError);
}
};
@@ -299,6 +299,9 @@ pub async fn enqueue_workflow_upload_request(
Ok((job_token, _id)) => job_token,
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(EnqueueWorkFlowRequestError::ServerError);
}
};