From 6308f8ecbbef15798109dcec6705cdf48840407d Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 19 Feb 2026 13:55:59 +0700 Subject: [PATCH] Seedance Pro client --- crates/api_clients/seedance2pro/Cargo.toml | 31 +++++++++++ .../api_clients/seedance2pro/src/creds/mod.rs | 2 + .../src/creds/seedance2pro_cookies.rs | 24 +++++++++ .../src/creds/seedance2pro_session.rs | 19 +++++++ .../api_clients/seedance2pro/src/error/mod.rs | 4 ++ .../src/error/seedance2pro_client_error.rs | 22 ++++++++ .../src/error/seedance2pro_error.rs | 54 +++++++++++++++++++ .../error/seedance2pro_generic_api_error.rs | 50 +++++++++++++++++ .../error/seedance2pro_specific_api_error.rs | 18 +++++++ crates/api_clients/seedance2pro/src/lib.rs | 3 ++ 10 files changed, 227 insertions(+) create mode 100644 crates/api_clients/seedance2pro/Cargo.toml create mode 100644 crates/api_clients/seedance2pro/src/creds/mod.rs create mode 100644 crates/api_clients/seedance2pro/src/creds/seedance2pro_cookies.rs create mode 100644 crates/api_clients/seedance2pro/src/creds/seedance2pro_session.rs create mode 100644 crates/api_clients/seedance2pro/src/error/mod.rs create mode 100644 crates/api_clients/seedance2pro/src/error/seedance2pro_client_error.rs create mode 100644 crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs create mode 100644 crates/api_clients/seedance2pro/src/error/seedance2pro_generic_api_error.rs create mode 100644 crates/api_clients/seedance2pro/src/error/seedance2pro_specific_api_error.rs create mode 100644 crates/api_clients/seedance2pro/src/lib.rs diff --git a/crates/api_clients/seedance2pro/Cargo.toml b/crates/api_clients/seedance2pro/Cargo.toml new file mode 100644 index 0000000000..9b9e87a501 --- /dev/null +++ b/crates/api_clients/seedance2pro/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "seedance2pro" +edition = "2024" +version = "0.0.1" +publish = false + +[package.metadata.cargo-udeps.ignore] +normal = ["workspace-build-acceleration"] + +[lib] +name = "seedance2pro" +path = "src/lib.rs" + +[dependencies] +# Workspace hack (faster compile times) +workspace-build-acceleration.workspace = true + +# Internal +errors.workspace = true + +# External +cloudflare_errors.workspace = true +log.workspace = true +serde.workspace = true +serde_json.workspace = true +wreq.workspace = true +wreq-util.workspace = true +url.workspace = true + +[dev-dependencies] +testing = { path = "../../lib/testing" } diff --git a/crates/api_clients/seedance2pro/src/creds/mod.rs b/crates/api_clients/seedance2pro/src/creds/mod.rs new file mode 100644 index 0000000000..bd0abf8580 --- /dev/null +++ b/crates/api_clients/seedance2pro/src/creds/mod.rs @@ -0,0 +1,2 @@ +pub mod seedance2pro_cookies; +pub mod seedance2pro_session; diff --git a/crates/api_clients/seedance2pro/src/creds/seedance2pro_cookies.rs b/crates/api_clients/seedance2pro/src/creds/seedance2pro_cookies.rs new file mode 100644 index 0000000000..005456ae5b --- /dev/null +++ b/crates/api_clients/seedance2pro/src/creds/seedance2pro_cookies.rs @@ -0,0 +1,24 @@ +/// Cookies are the credential required to interact with the Seedance2 Pro API. + +#[derive(Clone)] +pub struct Seedance2ProCookies { + cookies: String, +} + +impl Seedance2ProCookies { + pub fn new(cookies: String) -> Self { + Seedance2ProCookies { cookies } + } + + pub fn as_str(&self) -> &str { + &self.cookies + } + + pub fn as_bytes(&self) -> &[u8] { + self.cookies.as_bytes() + } + + pub fn to_string(&self) -> String { + self.cookies.clone() + } +} diff --git a/crates/api_clients/seedance2pro/src/creds/seedance2pro_session.rs b/crates/api_clients/seedance2pro/src/creds/seedance2pro_session.rs new file mode 100644 index 0000000000..f418414dec --- /dev/null +++ b/crates/api_clients/seedance2pro/src/creds/seedance2pro_session.rs @@ -0,0 +1,19 @@ +use crate::creds::seedance2pro_cookies::Seedance2ProCookies; + +/// Holds the full session data needed to make authenticated requests to Seedance2 Pro. +#[derive(Clone)] +pub struct Seedance2ProSession { + pub cookies: Seedance2ProCookies, +} + +impl Seedance2ProSession { + pub fn new(cookies: Seedance2ProCookies) -> Self { + Seedance2ProSession { cookies } + } + + pub fn from_cookies_string(cookies: String) -> Self { + Seedance2ProSession { + cookies: Seedance2ProCookies::new(cookies), + } + } +} diff --git a/crates/api_clients/seedance2pro/src/error/mod.rs b/crates/api_clients/seedance2pro/src/error/mod.rs new file mode 100644 index 0000000000..95d4dcf3c8 --- /dev/null +++ b/crates/api_clients/seedance2pro/src/error/mod.rs @@ -0,0 +1,4 @@ +pub mod seedance2pro_client_error; +pub mod seedance2pro_error; +pub mod seedance2pro_generic_api_error; +pub mod seedance2pro_specific_api_error; diff --git a/crates/api_clients/seedance2pro/src/error/seedance2pro_client_error.rs b/crates/api_clients/seedance2pro/src/error/seedance2pro_client_error.rs new file mode 100644 index 0000000000..edaca8ed2f --- /dev/null +++ b/crates/api_clients/seedance2pro/src/error/seedance2pro_client_error.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum Seedance2ProClientError { + /// No cookies are present in the session. + NoCookiesPresent, + + /// An error was encountered in building the Wreq client. + WreqClientError(wreq::Error), +} + +impl Error for Seedance2ProClientError {} + +impl Display for Seedance2ProClientError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::NoCookiesPresent => write!(f, "No cookies present in the session."), + Self::WreqClientError(err) => write!(f, "Wreq client error (during client creation): {}", err), + } + } +} diff --git a/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs b/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs new file mode 100644 index 0000000000..5d876b5e34 --- /dev/null +++ b/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs @@ -0,0 +1,54 @@ +use crate::error::seedance2pro_client_error::Seedance2ProClientError; +use crate::error::seedance2pro_generic_api_error::Seedance2ProGenericApiError; +use crate::error::seedance2pro_specific_api_error::Seedance2ProSpecificApiError; +use cloudflare_errors::cloudflare_error::CloudflareError; +use std::error::Error; + +#[derive(Debug)] +pub enum Seedance2ProError { + Client(Seedance2ProClientError), + ApiSpecific(Seedance2ProSpecificApiError), + ApiGeneric(Seedance2ProGenericApiError), +} + +impl Seedance2ProError { + pub fn is_having_downtime_issues(&self) -> bool { + match self { + Self::ApiGeneric(Seedance2ProGenericApiError::Http502ErrorBadGateway(_)) => true, + Self::ApiGeneric(Seedance2ProGenericApiError::CloudflareError(CloudflareError::BadGateway502)) => true, + Self::ApiGeneric(Seedance2ProGenericApiError::CloudflareError(CloudflareError::GatewayTimeout504)) => true, + Self::ApiGeneric(Seedance2ProGenericApiError::CloudflareError(CloudflareError::TimeoutOccurred524)) => true, + _ => false, + } + } +} + +impl Error for Seedance2ProError {} + +impl std::fmt::Display for Seedance2ProError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Client(e) => write!(f, "Seedance2ProClientError: {:?}", e), + Self::ApiSpecific(e) => write!(f, "Seedance2ProSpecificApiError: {:?}", e), + Self::ApiGeneric(e) => write!(f, "Seedance2ProGenericApiError: {:?}", e), + } + } +} + +impl From for Seedance2ProError { + fn from(error: Seedance2ProClientError) -> Self { + Self::Client(error) + } +} + +impl From for Seedance2ProError { + fn from(error: Seedance2ProSpecificApiError) -> Self { + Self::ApiSpecific(error) + } +} + +impl From for Seedance2ProError { + fn from(error: Seedance2ProGenericApiError) -> Self { + Self::ApiGeneric(error) + } +} diff --git a/crates/api_clients/seedance2pro/src/error/seedance2pro_generic_api_error.rs b/crates/api_clients/seedance2pro/src/error/seedance2pro_generic_api_error.rs new file mode 100644 index 0000000000..6f0db74ced --- /dev/null +++ b/crates/api_clients/seedance2pro/src/error/seedance2pro_generic_api_error.rs @@ -0,0 +1,50 @@ +use cloudflare_errors::cloudflare_error::CloudflareError; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use wreq::StatusCode; + +#[derive(Debug)] +pub enum Seedance2ProGenericApiError { + /// Specific Cloudflare errors. + CloudflareError(CloudflareError), + + /// HTTP 502 Bad Gateway. + /// We preserve the message for debugging. + Http502ErrorBadGateway(String), + + /// serde_json::Error, likely from JSON deserialization schema mismatch. + /// Includes the original body. + SerdeResponseParseErrorWithBody(serde_json::Error, String), + + /// serde_json::Error on a non-200 (error) response. + /// Includes the original body. + SerdeParseErrorWithBodyOnNon200(serde_json::Error, String), + + /// An uncategorized bad HTTP response. + UncategorizedBadResponse(String), + + /// An uncategorized bad HTTP response with status code and body. + UncategorizedBadResponseWithStatusAndBody { + status_code: StatusCode, + body: String, + }, + + /// An uncaught error from the HTTP client. + WreqError(wreq::Error), +} + +impl Error for Seedance2ProGenericApiError {} + +impl Display for Seedance2ProGenericApiError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::CloudflareError(err) => write!(f, "Cloudflare error: {}", err), + Self::Http502ErrorBadGateway(msg) => write!(f, "HTTP 502 Bad Gateway: {}", msg), + Self::SerdeResponseParseErrorWithBody(err, body) => write!(f, "Failed to parse response body: {:?}. Body: {}", err, body), + Self::SerdeParseErrorWithBodyOnNon200(err, body) => write!(f, "Failed to parse non-200 response body: {:?}. Body: {}", err, body), + Self::UncategorizedBadResponse(msg) => write!(f, "Uncategorized bad response: {}", msg), + Self::UncategorizedBadResponseWithStatusAndBody { status_code, body } => write!(f, "Uncategorized bad response: status code {}, body: {}", status_code, body), + Self::WreqError(err) => write!(f, "Wreq client error: {}", err), + } + } +} diff --git a/crates/api_clients/seedance2pro/src/error/seedance2pro_specific_api_error.rs b/crates/api_clients/seedance2pro/src/error/seedance2pro_specific_api_error.rs new file mode 100644 index 0000000000..199d2d0640 --- /dev/null +++ b/crates/api_clients/seedance2pro/src/error/seedance2pro_specific_api_error.rs @@ -0,0 +1,18 @@ +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum Seedance2ProSpecificApiError { + /// The session cookies are expired or invalid. + UnauthorizedSessionExpired, +} + +impl Error for Seedance2ProSpecificApiError {} + +impl Display for Seedance2ProSpecificApiError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnauthorizedSessionExpired => write!(f, "Unauthorized: session cookies expired or invalid."), + } + } +} diff --git a/crates/api_clients/seedance2pro/src/lib.rs b/crates/api_clients/seedance2pro/src/lib.rs new file mode 100644 index 0000000000..a4c246132a --- /dev/null +++ b/crates/api_clients/seedance2pro/src/lib.rs @@ -0,0 +1,3 @@ +// User API +pub mod creds; +pub mod error;