mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-07 10:31:46 +00:00
report 400s instead of 500s on duplicate idempotency tokens
This commit is contained in:
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;
|
||||
@@ -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;
|
||||
|
||||
+19
-4
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+8
-5
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user