mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
categorize seedance credits error (#1285)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use wreq::StatusCode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Seedance2ProSpecificApiError {
|
||||
/// The session cookies are expired or invalid.
|
||||
@@ -8,6 +10,12 @@ pub enum Seedance2ProSpecificApiError {
|
||||
|
||||
/// The video generation request was flagged as a content violation.
|
||||
VideoGenerationViolation(String),
|
||||
|
||||
/// The account does not have enough credits to perform the operation.
|
||||
BillingError {
|
||||
status_code: StatusCode,
|
||||
body: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error for Seedance2ProSpecificApiError {}
|
||||
@@ -17,6 +25,7 @@ impl Display for Seedance2ProSpecificApiError {
|
||||
match self {
|
||||
Self::UnauthorizedSessionExpired => write!(f, "Unauthorized: session cookies expired or invalid."),
|
||||
Self::VideoGenerationViolation(body) => write!(f, "Video generation violation: {}", body),
|
||||
Self::BillingError { status_code, body } => write!(f, "Billing error (status {}): {}", status_code, body),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -5,6 +5,7 @@ use crate::error::seedance2pro_generic_api_error::Seedance2ProGenericApiError;
|
||||
use crate::error::seedance2pro_specific_api_error::Seedance2ProSpecificApiError;
|
||||
use crate::requests::generate_video::request_types::*;
|
||||
use crate::requests::kinovi_host::{KinoviHost, resolve_host};
|
||||
use crate::utils::categorize_seedance2pro_error::categorize_seedance2pro_error;
|
||||
use crate::utils::common_headers::FIREFOX_USER_AGENT;
|
||||
use log::info;
|
||||
use wreq::Client;
|
||||
@@ -308,10 +309,7 @@ pub async fn generate_video(args: GenerateVideoArgs<'_>) -> Result<GenerateVideo
|
||||
info!("Response status: {}, body: {}", status, response_body);
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code: status,
|
||||
body: response_body,
|
||||
}.into());
|
||||
return Err(categorize_seedance2pro_error(status, response_body));
|
||||
}
|
||||
|
||||
let batch_response: Vec<BatchResponseItem> = serde_json::from_str(&response_body)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use wreq::StatusCode;
|
||||
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::error::seedance2pro_generic_api_error::Seedance2ProGenericApiError;
|
||||
use crate::error::seedance2pro_specific_api_error::Seedance2ProSpecificApiError;
|
||||
|
||||
/// Known error message substrings that indicate a billing/credits error.
|
||||
const BILLING_ERROR_MARKERS: &[&str] = &[
|
||||
"credits not enough",
|
||||
];
|
||||
|
||||
/// Categorize a non-success HTTP response into a specific or generic error.
|
||||
///
|
||||
/// Checks the response body for known patterns (e.g. billing errors) and returns
|
||||
/// a specific error variant when possible, falling back to
|
||||
/// `UncategorizedBadResponseWithStatusAndBody` otherwise.
|
||||
pub fn categorize_seedance2pro_error(
|
||||
status_code: StatusCode,
|
||||
body: String,
|
||||
) -> Seedance2ProError {
|
||||
if is_billing_error(&body) {
|
||||
return Seedance2ProSpecificApiError::BillingError {
|
||||
status_code,
|
||||
body,
|
||||
}.into();
|
||||
}
|
||||
|
||||
Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code,
|
||||
body,
|
||||
}.into()
|
||||
}
|
||||
|
||||
/// Check if the response body contains a known billing error marker.
|
||||
fn is_billing_error(body: &str) -> bool {
|
||||
let body_lower = body.to_lowercase();
|
||||
BILLING_ERROR_MARKERS.iter().any(|marker| body_lower.contains(marker))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
|
||||
#[test]
|
||||
fn real_not_enough_credits_response() {
|
||||
let body = std::fs::read_to_string("test_data/responses/not_enough_credits.json")
|
||||
.expect("Failed to read test data file");
|
||||
|
||||
let error = categorize_seedance2pro_error(StatusCode::BAD_REQUEST, body);
|
||||
|
||||
match error {
|
||||
Seedance2ProError::ApiSpecific(
|
||||
Seedance2ProSpecificApiError::BillingError { status_code, body }
|
||||
) => {
|
||||
assert_eq!(status_code, StatusCode::BAD_REQUEST);
|
||||
assert!(body.contains("credits not enough"));
|
||||
}
|
||||
other => panic!("Expected BillingError, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn billing_error_case_insensitive() {
|
||||
let body = r#"{"error":"Credits Not Enough"}"#.to_string();
|
||||
|
||||
let error = categorize_seedance2pro_error(StatusCode::BAD_REQUEST, body);
|
||||
|
||||
match error {
|
||||
Seedance2ProError::ApiSpecific(
|
||||
Seedance2ProSpecificApiError::BillingError { .. }
|
||||
) => {}
|
||||
other => panic!("Expected BillingError, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_error_falls_back_to_uncategorized() {
|
||||
let body = r#"{"error":"something else went wrong"}"#.to_string();
|
||||
|
||||
let error = categorize_seedance2pro_error(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
body.clone(),
|
||||
);
|
||||
|
||||
match error {
|
||||
Seedance2ProError::ApiGeneric(
|
||||
Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code, body: b }
|
||||
) => {
|
||||
assert_eq!(status_code, StatusCode::INTERNAL_SERVER_ERROR);
|
||||
assert_eq!(b, body);
|
||||
}
|
||||
other => panic!("Expected UncategorizedBadResponseWithStatusAndBody, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_body_falls_back_to_uncategorized() {
|
||||
let error = categorize_seedance2pro_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
String::new(),
|
||||
);
|
||||
|
||||
match error {
|
||||
Seedance2ProError::ApiGeneric(
|
||||
Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody { .. }
|
||||
) => {}
|
||||
other => panic!("Expected UncategorizedBadResponseWithStatusAndBody, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod categorize_seedance2pro_error;
|
||||
pub mod common_headers;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"error": {
|
||||
"json": {
|
||||
"message": "credits not enough",
|
||||
"code": -32600,
|
||||
"data": {
|
||||
"code": "BAD_REQUEST",
|
||||
"httpStatus": 400,
|
||||
"path": "workflow.runTask",
|
||||
"zodError": null
|
||||
},
|
||||
"stack": null
|
||||
},
|
||||
"meta": {
|
||||
"values": {
|
||||
"stack": [
|
||||
"undefined"
|
||||
]
|
||||
},
|
||||
"v": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user