update sora error type - much more semantic

This commit is contained in:
Brandon Thomas committed 2025-10-01 22:31:20 -04:00
1 parent a96db56d8f
commit 2d3ec8b4e5
38 files changed
+498 -317

No files matched your search

@@ -1,5 +1,9 @@
use crate::creds::sora_cookies::SoraCookies;
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::sora_error::SoraError;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::creds::sora_sentinel::SoraSentinel;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
pub struct SoraCredentialBuilder {
cookies: Option<String>,
@@ -16,29 +20,41 @@ impl SoraCredentialBuilder {
}
}
pub fn cookies(mut self, cookies: &str) -> Self {
// Consuming methods
pub fn with_cookies(mut self, cookies: &str) -> Self {
self.cookies = Some(cookies.to_string());
self
}
pub fn jwt_bearer_token(mut self, token: &str) -> Self {
pub fn with_jwt_bearer_token(mut self, token: &str) -> Self {
self.jwt_bearer_token = Some(token.to_string());
self
}
pub fn sora_sentinel(mut self, sentinel: &str) -> Self {
pub fn with_sora_sentinel(mut self, sentinel: &str) -> Self {
self.sora_sentinel = Some(sentinel.to_string());
self
}
// Mutable methods
pub fn set_cookies(&mut self, cookies: &str) {
self.cookies = Some(cookies.to_string());
}
pub fn set_jwt_bearer_token(&mut self, token: &str) {
self.jwt_bearer_token = Some(token.to_string());
}
pub fn set_sora_sentinel(&mut self, sentinel: &str) {
self.sora_sentinel = Some(sentinel.to_string());
}
pub fn build(self) -> Result<SoraCredentialSet, SoraError> {
use crate::creds::sora_cookies::SoraCookies;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::creds::sora_sentinel::SoraSentinel;
let cookies = match self.cookies {
Some(c) => SoraCookies::new(c),
None => return Err(SoraError::SoraCredentialBuilderError("no cookies provided".to_string())),
None => return Err(SoraClientError::SoraCredentialBuilderError("no cookies provided").into()),
};
let jwt_bearer_token = match self.jwt_bearer_token {
@@ -39,12 +39,12 @@ impl SoraCredentialSet {
pub fn to_builder(&self) -> SoraCredentialBuilder {
let mut builder = SoraCredentialBuilder::new()
.cookies(self.cookies.as_str());
.with_cookies(self.cookies.as_str());
if let Some(bearer) = &self.jwt_bearer_token {
builder = builder.jwt_bearer_token(bearer.as_str());
builder = builder.with_jwt_bearer_token(bearer.as_str());
}
if let Some(sentinel) = &self.sora_sentinel {
builder = builder.sora_sentinel(sentinel.as_str());
builder = builder.with_sora_sentinel(sentinel.as_str());
}
builder
}
@@ -1,5 +1,5 @@
use crate::error::sora_error::SoraError;
use crate::utils::lightweight_sora_jwt_parse::{lightweight_sora_jwt_parse, SoraJwtClaims};
use errors::AnyhowResult;
/// Sora bearer tokens are JWT tokens that can be minted with a valid cookie/session.
#[derive(Clone)]
@@ -9,7 +9,7 @@ pub struct SoraJwtBearerToken {
}
impl SoraJwtBearerToken {
pub fn new(token: String) -> AnyhowResult<Self> {
pub fn new(token: String) -> Result<Self, SoraError> {
let jwt_claims = lightweight_sora_jwt_parse(&token)?;
Ok(SoraJwtBearerToken {
token,
@@ -0,0 +1,4 @@
pub mod sora_client_error;
pub mod sora_error;
pub mod sora_generic_api_error;
pub mod sora_specific_api_error;
@@ -0,0 +1,49 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub enum SoraClientError {
/// An error reading the file for upload.
FileForUploadReadError(std::io::Error),
/// The file path provided for upload is invalid.
FileForUploadHasInvalidPath,
/// Something is wrong with the JWT bearer token.
/// This error originates on our end as we try to parse the JWT.
LocalJwtClaimsParseError(String),
/// There was an error constructing the form-multipart request.
MultipartFormError(wreq::Error),
/// We haven't received a bearer token yet
/// This is our own internal application state error, not something Sora returns.
/// We know our client can't make the request, so we preemptively fail it.
NoBearerTokenAvailable,
/// Issue with using the SoraCredentialBuilder.
SoraCredentialBuilderError(&'static str),
/// Error parsing a request URL.
UrlParseError(url::ParseError),
/// An error was encountered in building the Wreq client
WreqClientError(wreq::Error),
}
impl Error for SoraClientError {}
impl Display for SoraClientError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::FileForUploadReadError(err) => write!(f, "Error reading file for upload: {}", err),
Self::FileForUploadHasInvalidPath => write!(f, "The file path provided for upload is invalid."),
Self::LocalJwtClaimsParseError(msg) => write!(f, "Local JWT claims parse error: {}", msg),
Self::MultipartFormError(err) => write!(f, "Multipart form error: {}", err),
Self::NoBearerTokenAvailable => write!(f, "No bearer token available. The client needs a bearer token to make the request."),
Self::SoraCredentialBuilderError(msg) => write!(f, "Sora Credential Builder error: {}", msg),
Self::UrlParseError(err) => write!(f, "URL parse error: {}", err),
Self::WreqClientError(err) => write!(f, "Wreq client error (during client creation): {}", err),
}
}
}
@@ -0,0 +1,54 @@
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
use cloudflare_errors::cloudflare_error::CloudflareError;
use std::error::Error;
#[derive(Debug)]
pub enum SoraError {
Client(SoraClientError),
ApiSpecific(SoraSpecificApiError),
ApiGeneric(SoraGenericApiError),
}
impl SoraError {
pub fn is_sora_having_downtime_issues(&self) -> bool {
match self {
Self::ApiGeneric(SoraGenericApiError::Http502ErrorBadGateway(_)) => true,
Self::ApiGeneric(SoraGenericApiError::CloudflareError(CloudflareError::BadGateway502)) => true,
Self::ApiGeneric(SoraGenericApiError::CloudflareError(CloudflareError::GatewayTimeout504)) => true,
Self::ApiGeneric(SoraGenericApiError::CloudflareError(CloudflareError::TimeoutOccurred524)) => true,
_ => false,
}
}
}
impl Error for SoraError {}
impl std::fmt::Display for SoraError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Client(e) => write!(f, "SoraClientError: {:?}", e),
Self::ApiSpecific(e) => write!(f, "SoraSpecificApiError: {:?}", e),
Self::ApiGeneric(e) => write!(f, "SoraGenericApiError: {:?}", e),
}
}
}
impl From<SoraClientError> for SoraError {
fn from(error: SoraClientError) -> Self {
Self::Client(error)
}
}
impl From<SoraSpecificApiError> for SoraError {
fn from(error: SoraSpecificApiError) -> Self {
Self::ApiSpecific(error)
}
}
impl From<SoraGenericApiError> for SoraError {
fn from(error: SoraGenericApiError) -> Self {
Self::ApiGeneric(error)
}
}
@@ -0,0 +1,48 @@
use cloudflare_errors::cloudflare_error::CloudflareError;
use std::error::Error;
use std::fmt::{Display, Formatter};
use wreq::StatusCode;
#[derive(Debug)]
pub enum SoraGenericApiError {
/// Specific Cloudflare errors.
CloudflareError(CloudflareError),
/// HTTP 502 Bad Gateway
/// Sora is having issues.
/// Typically served from Cloudflare
/// 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),
/// Another error occurred.
/// TODO: Try to get rid of this.
UncategorizedBadResponse(String),
/// An uncategorized bad HTTP response from Sora.
UncategorizedBadResponseWithStatusAndBody {
status_code: StatusCode,
body: String,
},
/// An uncaught error from the API client.
WreqError(wreq::Error),
}
impl Error for SoraGenericApiError {}
impl Display for SoraGenericApiError {
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::UncategorizedBadResponseWithStatusAndBody { status_code, body } => write!(f, "Uncategorized bad response: status code {}, body: {}", status_code, body),
Self::UncategorizedBadResponse(msg) => write!(f, "Uncategorized bad response status: {}", msg),
Self::WreqError(err) => write!(f, "Wreq client error: {}", err),
}
}
}
@@ -0,0 +1,44 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub enum SoraSpecificApiError {
/// Failed to generate a bearer token despite the `200 OK` response.
/// This has been generated by sending old/expired session cookies or empty cookies.
FailedToGenerateBearerToken,
/// Sora requires users to set up an account username before they can generate images.
/// This requires the user to set up that username.
SoraUsernameNotYetCreated,
/// We're sending too many tasks to Sora.
/// Sora is telling us to cool it.
TooManyConcurrentTasks,
// TODO: Try to make concrete error types for these distinct cases.
/// Unauthorized, cookie and/or bearer token expired. We'll need to ask for a refreshed login.
///
/// Example message, e.g. from the upload endpoint:
/// {
/// "error": {
/// "message": "Your authentication token has expired. Please try signing in again.",
/// "type": "invalid_request_error",
/// "param": null,
/// "code": "token_expired"
/// }
/// }
UnauthorizedCookieOrBearerExpired,
}
impl Error for SoraSpecificApiError {}
impl Display for SoraSpecificApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::FailedToGenerateBearerToken => write!(f, "Failed to generate bearer token. Probably invalid or expired session cookies."),
Self::SoraUsernameNotYetCreated => write!(f, "Sora username not yet created. Please create a username in the Sora app or website."),
Self::TooManyConcurrentTasks => write!(f, "Too many concurrent tasks. Please wait."),
Self::UnauthorizedCookieOrBearerExpired => write!(f, "Unauthorized: cookie and/or bearer token expired"),
}
}
}
@@ -1,5 +1,5 @@
pub mod creds;
pub mod error;
pub mod recipes;
pub mod requests;
pub mod sora_error;
pub mod utils;
@@ -1,12 +1,14 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::creds::sora_sentinel::SoraSentinel;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
use crate::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
use crate::requests::image_gen::{image_gen_http_request, SoraImageGenError};
use crate::requests::sentinel_refresh::generate::token::generate_token;
use crate::sora_error::SoraError;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{info, warn};
@@ -47,17 +49,17 @@ pub async fn image_remix_with_session_auto_renew(request: ImageRemixAutoRenewReq
match err {
// We'll fail these requests...
SoraImageGenError::TooManyConcurrentTasks(_) => {
return Err(SoraError::TooManyConcurrentTasks);
return Err(SoraSpecificApiError::TooManyConcurrentTasks.into());
}
SoraImageGenError::UsernameRequired(_err) => {
return Err(SoraSpecificApiError::SoraUsernameNotYetCreated.into())
}
SoraImageGenError::GenericError(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("image remix failed with GenericError: {:?}", err)))
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("image remix failed with GenericError: {:?}", err)).into())
}
SoraImageGenError::NetworkError(err) => {
// TODO: The underlying type should be a reqwest::Error.
return Err(SoraError::OtherBadStatus(anyhow!("network error: {:?}", err)))
}
SoraImageGenError::UsernameRequired(_err) => {
return Err(SoraError::SoraUsernameNotYetCreated)
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("network error: {:?}", err)).into())
}
// We'll retry these requests...
@@ -85,12 +87,12 @@ pub async fn image_remix_with_session_auto_renew(request: ImageRemixAutoRenewReq
let response = generate_bearer_with_cookie(cookies).await;
match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new JWT bearer token: {:?}", err)))
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("failed to generate new JWT bearer token: {:?}", err)).into())
}
Ok(bearer) => {
match SoraJwtBearerToken::new(bearer) {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("Failed to parse new JWT bearer token: {:?}", err)));
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("Failed to parse new JWT bearer token: {:?}", err)).into());
}
Ok(bearer) => new_bearer = Some(bearer),
}
@@ -105,7 +107,7 @@ pub async fn image_remix_with_session_auto_renew(request: ImageRemixAutoRenewReq
let response = generate_token().await;
match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new sentinel: {:?}", err)))
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("failed to generate new sentinel: {:?}", err)).into())
}
Ok(sentinel) => new_sentinel = Some(SoraSentinel::new(sentinel)),
}
@@ -135,12 +137,12 @@ pub async fn image_remix_with_session_auto_renew(request: ImageRemixAutoRenewReq
match result {
Ok(response) => Ok((response, Some(new_creds))),
Err(err) => match err {
image_gen_http_request::SoraImageGenError::TooManyConcurrentTasks(err) => {
Err(SoraError::TooManyConcurrentTasks)
SoraImageGenError::TooManyConcurrentTasks(err) => {
Err(SoraSpecificApiError::TooManyConcurrentTasks.into())
}
_ => {
warn!("Image remix failed again: {:?}", err);
Err(SoraError::OtherBadStatus(anyhow!("image remix failed again: {:?}", err)))
Err(SoraGenericApiError::UncategorizedBadResponse(format!("image remix failed again: {:?}", err)).into())
}
}
}
@@ -9,11 +9,11 @@ use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, Sor
use crate::requests::sentinel_refresh::generate::token::generate_token;
use crate::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
use crate::sora_error::SoraError;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{error, info, warn};
use std::time::Duration;
use crate::error::sora_error::SoraError;
pub struct ImageUploadFromBytesAutoRenewRequest<'a> {
pub file_bytes: Vec<u8>,
@@ -1,11 +1,12 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::error::sora_error::SoraError;
use crate::recipes::maybe_refresh_credentials_on_sora_error::maybe_refresh_credentials_on_sora_error;
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
use crate::sora_error::SoraError;
use log::{info, warn};
use std::path::Path;
use std::time::Duration;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
pub struct ImageUploadFromFileAutoRenewRequest<'a, P: AsRef<Path>> {
pub file_path: P,
@@ -31,7 +32,7 @@ pub async fn image_upload_from_file_with_session_auto_renew<P: AsRef<Path>>(
let err = match result {
Ok(response) => return Ok((response, None)),
// We can't retry some errors.
Err(err @ SoraError::SoraUsernameNotYetCreated) => return Err(err),
Err(err @ SoraError::ApiSpecific(SoraSpecificApiError::SoraUsernameNotYetCreated)) => return Err(err),
// Retry all other errors.
Err(err) => err,
};
@@ -1,11 +1,11 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::error::sora_error::SoraError;
use crate::recipes::image_upload_from_file_with_session_auto_renew::ImageUploadFromFileAutoRenewRequest;
use crate::recipes::maybe_refresh_credentials_on_sora_error::maybe_refresh_credentials_on_sora_error;
use crate::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use crate::requests::image_gen::image_gen_status::{get_image_gen_status, StatusRequest, TaskId, TaskResponse, VideoGenStatusResponse};
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
use crate::requests::upload::upload_media_http_request::SoraMediaUploadResponse;
use crate::sora_error::SoraError;
use anyhow::Error;
use log::{info, warn};
use std::path::Path;
@@ -1,7 +1,9 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
use crate::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
use crate::sora_error::SoraError;
use anyhow::anyhow;
use log::{error, info, warn};
@@ -10,15 +12,15 @@ use log::{error, info, warn};
pub async fn maybe_refresh_credentials_on_sora_error(creds: &SoraCredentialSet, error: SoraError) -> Result<SoraCredentialSet, SoraError> {
match error {
SoraError::NoBearerTokenAvailable => {
warn!("Previous request failed due to missing bearer token.");
SoraError::Client(SoraClientError::NoBearerTokenAvailable) => {
error!("Previous request failed due to missing bearer token.");
}
SoraError::UnauthorizedCookieOrBearerExpired => {
warn!("Previous request failed due to invalid bearer token.");
SoraError::ApiSpecific(SoraSpecificApiError::UnauthorizedCookieOrBearerExpired) => {
error!("Previous request failed due to invalid bearer token.");
}
_ => {
error!("Previous request failed with error: {:?}", error);
return Err(SoraError::OtherBadStatus(anyhow!("previous request failed with error: {:?}", error)))
error!("Previous request failed due to the following error: {:?}", error);
return Err(error)
}
}
@@ -29,12 +31,14 @@ pub async fn maybe_refresh_credentials_on_sora_error(creds: &SoraCredentialSet,
let new_bearer = match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new JWT bearer token: {:?}", err)))
error!("Error generating new JWT bearer token: {}", err);
return Err(err);
}
Ok(bearer) => {
match SoraJwtBearerToken::new(bearer) {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to parse new JWT bearer token: {:?}", err)));
error!("Error parsing new JWT bearer token: {}", err);
return Err(err);
}
Ok(bearer) => bearer,
}
@@ -7,7 +7,7 @@ use chrono::{DateTime, TimeDelta, Utc};
use errors::AnyhowResult;
use log::info;
use std::ops::Sub;
use crate::sora_error::SoraError;
use crate::error::sora_error::SoraError;
const EXPIRATION_DEADLINE : TimeDelta = TimeDelta::hours(12);
@@ -1,16 +1,18 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::creds::sora_sentinel::SoraSentinel;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
use crate::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
use crate::requests::image_gen::sora_image_gen_simple::{sora_image_gen_simple, SoraImageGenSimpleRequest};
use crate::requests::image_gen::{image_gen_http_request, SoraImageGenError};
use crate::requests::sentinel_refresh::generate::token::generate_token;
use crate::sora_error::SoraError;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{info, warn};
use log::{error, info, warn};
use std::time::Duration;
pub struct SimpleImageGenAutoRenewRequest<'a> {
@@ -46,17 +48,17 @@ pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAut
match err {
// We'll fail these requests...
SoraImageGenError::TooManyConcurrentTasks(_) => {
return Err(SoraError::TooManyConcurrentTasks);
return Err(SoraSpecificApiError::TooManyConcurrentTasks.into());
}
SoraImageGenError::UsernameRequired(_err) => {
return Err(SoraSpecificApiError::SoraUsernameNotYetCreated.into())
}
SoraImageGenError::GenericError(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("image gen failed with GenericError: {:?}", err)))
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("image gen failed with GenericError: {:?}", err)).into())
}
SoraImageGenError::NetworkError(err) => {
// TODO: The underlying type should be a reqwest::Error.
return Err(SoraError::OtherBadStatus(anyhow!("network error: {:?}", err)))
}
SoraImageGenError::UsernameRequired(_err) => {
return Err(SoraError::SoraUsernameNotYetCreated)
return Err(SoraGenericApiError::UncategorizedBadResponse(format!("network error (todo: this should be a reqwest error): {:?}", err)).into())
}
// We'll retry these requests...
@@ -84,12 +86,14 @@ pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAut
let response = generate_bearer_with_cookie(cookies).await;
match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new JWT bearer token: {:?}", err)))
error!("Error asking to generate JWT: {:?}", err);
return Err(err);
}
Ok(bearer) => {
match SoraJwtBearerToken::new(bearer) {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("Failed to parse new JWT bearer token: {:?}", err)));
error!("Failed to parse new JWT: {:?}", err);
return Err(err);
}
Ok(bearer) => new_bearer = Some(bearer),
}
@@ -104,7 +108,8 @@ pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAut
let response = generate_token().await;
match response {
Err(err) => {
return Err(SoraError::OtherBadStatus(anyhow!("failed to generate new sentinel: {:?}", err)))
error!("failed to generate new sentinel: {:?}", err);
return Err(err)
}
Ok(sentinel) => new_sentinel = Some(SoraSentinel::new(sentinel)),
}
@@ -134,11 +139,11 @@ pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAut
Ok(response) => Ok((response, Some(new_creds))),
Err(err) => match err {
image_gen_http_request::SoraImageGenError::TooManyConcurrentTasks(err) => {
Err(SoraError::TooManyConcurrentTasks)
Err(SoraSpecificApiError::TooManyConcurrentTasks.into())
}
_ => {
warn!("Image gen failed again: {:?}", err);
Err(SoraError::OtherBadStatus(anyhow!("image gen failed again: {:?}", err)))
Err(SoraGenericApiError::UncategorizedBadResponse(format!("image gen failed again: {:?}", err)).into())
}
}
}
@@ -1,5 +1,8 @@
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
use crate::requests::image_gen::SoraImageGenError;
use crate::sora_error::SoraError;
use crate::utils::classify_general_http_error::classify_general_http_error;
use crate::utils::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
use errors::AnyhowResult;
@@ -34,7 +37,11 @@ pub struct SoraUser {
pub async fn generate_bearer_with_cookie(cookie: &str) -> Result<String, SoraError> {
let client = Client::builder()
.gzip(true)
.build()?;
.build()
.map_err(|err| {
error!("Error building the client: {:?}", err);
SoraClientError::WreqClientError(err)
})?;
info!("Generating new JWT bearer token from session cookies... (cookie payload length: {})", cookie.len());
@@ -45,14 +52,18 @@ pub async fn generate_bearer_with_cookie(cookie: &str) -> Result<String, SoraErr
.header("Accept-Language", "en-US,en;q=0.5")
.header("Cookie", cookie)
.send()
.await?;
.await
.map_err(|err| {
error!("Error sending request: {:?}", err);
SoraGenericApiError::WreqError(err)
})?;
let status = response.status();
let response_body = &response.text().await
.map_err(|err| {
error!("Error reading body while attempting to generate bearer token: {:?}", err);
SoraError::WreqError(err)
SoraGenericApiError::WreqError(err)
})?;
if !status.is_success() {
@@ -66,10 +77,14 @@ pub async fn generate_bearer_with_cookie(cookie: &str) -> Result<String, SoraErr
if response_body == "null" {
error!("Failed to generate bearer token. Response was the string `null`.");
return Err(SoraError::FailedToGenerateBearer)
return Err(SoraSpecificApiError::FailedToGenerateBearerToken.into())
}
let auth_response: SoraAuthResponse = serde_json::from_str(&response_body)?;
let auth_response: SoraAuthResponse = serde_json::from_str(&response_body)
.map_err(|err| {
error!("Error parsing body while attempting to generate bearer token: {:?}", err);
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string())
})?;
Ok(auth_response.access_token)
}
@@ -1,6 +1,8 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
use crate::sora_error::SoraError;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::utils::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
use anyhow::anyhow;
use errors::AnyhowResult;
@@ -207,15 +209,17 @@ pub async fn get_image_gen_status(status_request: &StatusRequest, credentials: &
let bearer_header = match credentials.jwt_bearer_token.as_ref() {
Some(bearer) => bearer.to_authorization_header_value(),
None => {
warn!("No JWT bearer for getting image gen status!");
return Err(SoraError::NoBearerTokenAvailable)
warn!("No JWT bearer token in client - cannot fetch image gen status!");
return Err(SoraClientError::NoBearerTokenAvailable.into());
},
};
let client = Client::new();
let mut url = Url::parse(SORA_STATUS_URL)
.map_err(|err| anyhow!("error parsing URL: {:?}", err))?;
.map_err(|err| {
SoraClientError::UrlParseError(err)
})?;
// Add query parameters
if let Some(limit) = status_request.limit {
@@ -232,13 +236,26 @@ pub async fn get_image_gen_status(status_request: &StatusRequest, credentials: &
.header("Authorization", bearer_header)
.header("Content-Type", "application/json");
let http_request = http_request.build()?;
let http_request = http_request.build()
.map_err(|err| {
SoraClientError::WreqClientError(err)
})?;
let response = client.execute(http_request).await?;
let response = client.execute(http_request)
.await
.map_err(|err| {
error!("Client failed to fetch sora image gen status: {:?}", err);
SoraGenericApiError::WreqError(err)
})?;
let status = response.status();
let response_body = &response.text().await?;
let response_body = &response.text()
.await
.map_err(|err| {
error!("Client failed to read sora image gen status: {:?}", err);
SoraGenericApiError::WreqError(err)
})?;
debug!("response_body: {}", response_body);
@@ -254,7 +271,8 @@ pub async fn get_image_gen_status(status_request: &StatusRequest, credentials: &
Ok(response) => Ok(response),
Err(err) => {
warn!("Failed to parse status response: {:?}", err);
Err(SoraError::JsonErrorWithBody(err, response_body.to_string()))
Err(SoraGenericApiError::SerdeResponseParseErrorWithBody(
err, response_body.to_string()).into())
}
}
}
@@ -316,9 +334,9 @@ mod tests {
let bearer = bearer.trim().to_string();
let creds = SoraCredentialBuilder::new()
.cookies(&cookie)
.jwt_bearer_token(&bearer)
.sora_sentinel(&sentinel)
.with_cookies(&cookie)
.with_jwt_bearer_token(&bearer)
.with_sora_sentinel(&sentinel)
.build()?;
// Get the task status for a specific task
@@ -70,9 +70,9 @@ mod tests {
let bearer = bearer.trim().to_string();
let creds = SoraCredentialBuilder::new()
.cookies(&cookie)
.jwt_bearer_token(&bearer)
.sora_sentinel(&sentinel)
.with_cookies(&cookie)
.with_jwt_bearer_token(&bearer)
.with_sora_sentinel(&sentinel)
.build()?;
let response = sora_image_gen_remix(SoraImageGenRemixRequest {
@@ -54,9 +54,9 @@ mod tests {
let bearer = bearer.trim().to_string();
let creds = SoraCredentialBuilder::new()
.cookies(&cookie)
.jwt_bearer_token(&bearer)
.sora_sentinel(&sentinel)
.with_cookies(&cookie)
.with_jwt_bearer_token(&bearer)
.with_sora_sentinel(&sentinel)
.build()?;
let response = sora_image_gen_simple(SoraImageGenSimpleRequest {
@@ -1,5 +1,8 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::sora_error::SoraError;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use log::error;
use wreq::Client;
const USER_AGENT : &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";
@@ -12,12 +15,10 @@ pub async fn sora_job_status(
) -> Result<(), SoraError> {
let url = format!("https://sora.com/backend/notif?limit=100&before={}", task_id);
let cookie = credentials.cookies.to_string();
// TODO(bt,2025-04-23): We're using a Sora payload error in place of application state error. Surface this differently.
let authorization_header = credentials.jwt_bearer_token.as_ref()
.ok_or(SoraError::NoBearerTokenAvailable)?
.ok_or(SoraClientError::NoBearerTokenAvailable)?
.to_authorization_header_value();
let client = Client::new();
@@ -28,9 +29,18 @@ pub async fn sora_job_status(
.header("User-Agent", USER_AGENT)
.header("Cookie", &cookie);
let response = request.send().await?;
let response = request.send()
.await
.map_err(|err| {
error!("sora fetch job status error: {}", err);
SoraGenericApiError::WreqError(err)
})?;
let json_response = &response.text().await?;
let json_response = &response.text().await
.map_err(|err| {
error!("sora read job status error: {}", err);
SoraGenericApiError::WreqError(err)
})?;
//let response = serde_json::from_str(json_response)?;
@@ -58,9 +68,9 @@ mod tests {
let bearer = bearer.trim().to_string();
let creds = SoraCredentialBuilder::new()
.cookies(&cookie)
.jwt_bearer_token(&bearer)
.sora_sentinel(&sentinel)
.with_cookies(&cookie)
.with_jwt_bearer_token(&bearer)
.with_sora_sentinel(&sentinel)
.build()?;
let task_id = "task_01jqwwrkvgeqp8jsf5mqk1jceh";
@@ -1,6 +1,9 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::requests::image_gen::image_gen_status::VideoGenStatusResponse;
use crate::requests::upload::upload_media_http_request::SoraMediaUploadResponse;
use crate::sora_error::SoraError;
use crate::utils::classify_general_http_error::classify_general_http_error;
use log::{error, info};
use once_cell::sync::Lazy;
@@ -39,13 +42,17 @@ pub async fn list_media(credentials: &SoraCredentialSet) -> Result<ListMediaResp
let auth_header = credentials.jwt_bearer_token
.as_ref()
.map(|bearer| bearer.to_authorization_header_value())
.ok_or_else(|| SoraError::NoBearerTokenAvailable)?;
.ok_or_else(|| SoraClientError::NoBearerTokenAvailable)?;
let cookie = credentials.cookies.to_string();
let client = Client::builder()
.gzip(true)
.build()?;
.build()
.map_err(|err| {
error!("Failed to build HTTP client: {}", err);
SoraGenericApiError::WreqError(err)
})?;
let response = client.get(SORA_MEDIA_LIST_URL)
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0")
@@ -55,7 +62,11 @@ pub async fn list_media(credentials: &SoraCredentialSet) -> Result<ListMediaResp
.header("Cookie", &cookie)
.header("Authorization", &auth_header)
.send()
.await?;
.await
.map_err(|err| {
error!("Failed to fetch media list: {}", err);
SoraGenericApiError::WreqError(err)
})?;
if !response.status().is_success() {
error!("Failed to fetch media list: {}", response.status());
@@ -65,9 +76,19 @@ pub async fn list_media(credentials: &SoraCredentialSet) -> Result<ListMediaResp
info!("Successfully generated bearer token.");
// Parse response
let typed_response = response.json::<ListMediaResponse>().await?;
Ok(typed_response)
let text_body = &response.text().await
.map_err(|err| {
error!("sora error reading media list text body: {}", err);
SoraGenericApiError::WreqError(err)
})?;
let response = serde_json::from_str::<ListMediaResponse>(text_body)
.map_err(|err| {
error!("Failed to parse media list response: {}", err);
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, text_body.to_string())
})?;
Ok(response)
}
@@ -1,5 +1,7 @@
use super::request::GenerateSentinelRefreshRequest;
use crate::sora_error::SoraError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::requests::list_media::list_media::ListMediaResponse;
use crate::utils::classify_general_http_error::classify_general_http_error;
use errors::AnyhowResult;
use idempotency::uuid::generate_random_uuid;
@@ -49,11 +51,15 @@ pub async fn generate_token() -> Result<String, SoraError> {
let request = SentinelRequest::new(base64_request);
let client = Client::new();
let response = client.post(SORA_IMAGE_GEN_URL)
.header("User-Agent", USER_AGENT)
.header("Content-Type", "application/json")
.json(&request)
.send()
.await?;
.header("User-Agent", USER_AGENT)
.header("Content-Type", "application/json")
.json(&request)
.send()
.await
.map_err(|err| {
error!("Client failed to generate sentinel token: {:?}", err);
SoraGenericApiError::WreqError(err)
})?;
if !response.status().is_success() {
error!("Failed to generate sentinel: {}", response.status());
@@ -61,8 +67,19 @@ pub async fn generate_token() -> Result<String, SoraError> {
return Err(error);
}
let response_json: SentinelResponse = response.json().await?;
Ok(response_json.token)
let text_body = &response.text().await
.map_err(|err| {
error!("sora error reading sentinel token text body: {}", err);
SoraGenericApiError::WreqError(err)
})?;
let response = serde_json::from_str::<SentinelResponse>(&text_body)
.map_err(|err| {
error!("Failed to parse media list response: {}", err);
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, text_body.to_string())
})?;
Ok(response.token)
}
@@ -1,6 +1,6 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::error::sora_error::SoraError;
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
use crate::sora_error::SoraError;
use std::path::PathBuf;
use std::time::Duration;
@@ -1,6 +1,8 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
use crate::sora_error::SoraError;
use log::error;
use std::path::Path;
use std::time::Duration;
use tokio::fs::File;
@@ -15,17 +17,29 @@ pub async fn sora_media_upload_from_file<P: AsRef<Path>>(
creds: &SoraCredentialSet,
maybe_timeout: Option<Duration>,
) -> Result<SoraMediaUploadResponse, SoraError> {
let mut file = File::open(&file_path).await?;
let mut file = File::open(&file_path).await
.map_err(|err| {
error!("Failed to open file for upload: {}", err);
SoraClientError::FileForUploadReadError(err)
})?;
let mut buffer = Vec::with_capacity(INITIAL_BUFFER_SIZE);
file.read_to_end(&mut buffer).await?;
file.read_to_end(&mut buffer).await
.map_err(|err| {
error!("Failed to read file for upload: {}", err);
SoraClientError::FileForUploadReadError(err)
})?;
let filename = file_path.as_ref().file_name()
.ok_or_else(|| anyhow::anyhow!("Invalid file path"))?
.ok_or_else(|| SoraClientError::FileForUploadHasInvalidPath)?
.to_string_lossy()
.to_string();
let maybe_ext = file_path.as_ref().extension().and_then(|e| e.to_str());
// TODO: Read file magic bytes first, then fall back to this.
let mime_type = match file_path.as_ref().extension().and_then(|e| e.to_str()) {
let mime_type = match maybe_ext {
Some("jpg") | Some("jpeg") => "image/jpeg",
Some("png") => "image/png",
// Some("webp") => "image/webp",
@@ -59,8 +73,8 @@ mod tests {
let image_path = test_file_path("test_data/image/juno.jpg")?; // media_01jqyqgqpwf40tkcapq5bmaz5d
let creds = SoraCredentialBuilder::new()
.cookies(&cookie)
.jwt_bearer_token(&bearer)
.with_cookies(&cookie)
.with_jwt_bearer_token(&bearer)
.build()?;
let response = sora_media_upload_from_file(
@@ -1,9 +1,13 @@
use crate::creds::sora_credential_set::SoraCredentialSet;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::requests::bearer::generate_bearer_with_cookie::SoraAuthResponse;
use crate::requests::image_gen::image_gen_status::USER_AGENT;
use crate::sora_error::SoraError;
use crate::utils::classify_general_http_error::classify_general_http_error;
use log::info;
use log::{error, info};
use serde::Deserialize;
use std::io::empty;
use std::time::Duration;
use wreq::multipart::{Form, Part};
use wreq::Client;
@@ -74,16 +78,17 @@ pub (crate) async fn upload_media_http_request(
) -> Result<SoraMediaUploadResponse, SoraError> {
// Create multipart form
let part = Part::bytes(file_bytes) // NB: Reqwest needs to own the bytes.
.file_name(filename) // NB: Reqwest needs to own the bytes
.mime_str(mime_type)?;
let part = Part::bytes(file_bytes) // NB: Reqwest needs to own the bytes.
.file_name(filename) // NB: Reqwest needs to own the bytes
.mime_str(mime_type)
.map_err(|e| SoraClientError::MultipartFormError(e))?;
let form = Form::new().part("file", part);
let cookie = credentials.cookies.to_string();
let auth_header = credentials.jwt_bearer_token
.as_ref()
.ok_or(SoraError::NoBearerTokenAvailable)?
.ok_or(SoraClientError::NoBearerTokenAvailable)?
.to_authorization_header_value();
// Make API request
@@ -98,7 +103,11 @@ pub (crate) async fn upload_media_http_request(
request_builder = request_builder.timeout(timeout);
}
let response = request_builder.send().await?;
let response = request_builder.send()
.await
.map_err(|err| {
SoraGenericApiError::WreqError(err)
})?;
// Check response status
if !response.status().is_success() {
@@ -107,7 +116,17 @@ pub (crate) async fn upload_media_http_request(
return Err(error);
}
// Parse response
let upload_response = response.json::<SoraMediaUploadResponse>().await?;
let response_body = &response.text().await
.map_err(|err| {
error!("Error reading response body while attempting file upload: {:?}", err);
SoraGenericApiError::WreqError(err)
})?;
let upload_response : SoraMediaUploadResponse = serde_json::from_str(&response_body)
.map_err(|err| {
error!("Error parsing response body while attempting file upload: {:?}", err);
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string())
})?;
Ok(upload_response)
}
@@ -1,166 +0,0 @@
use cloudflare_errors::cloudflare_error::CloudflareError;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
/// The single error type this crate should surface to all callers.
#[derive(Debug)]
pub enum SoraError {
/// We haven't received a bearer token yet
/// This is our own internal application state error, not something Sora returns.
NoBearerTokenAvailable,
/// We're sending too many tasks to Sora.
TooManyConcurrentTasks,
// TODO: Try to make concrete error types for these.
/// Unauthorized, cookie and/or bearer token expired. We'll need to ask for a refreshed login.
///
/// Example message, e.g. from the upload endpoint:
/// {
/// "error": {
/// "message": "Your authentication token has expired. Please try signing in again.",
/// "type": "invalid_request_error",
/// "param": null,
/// "code": "token_expired"
/// }
/// }
UnauthorizedCookieOrBearerExpired,
/// Failed to generate a bearer token despite the `200 OK` response.
/// This has been generated by sending old/expired session cookies or empty cookies.
FailedToGenerateBearer,
/// Sora requires users to set up an account username before they can generate images.
/// This requires the user to set up that username.
SoraUsernameNotYetCreated,
/// Something is wrong with the JWT bearer token.
/// This error originates on our end as we try to parse the JWT.
LocalJwtClaimsParseError(String),
/// Sora is having issues.
/// Typically served from Cloudflare
/// We preserve the message for debugging.
BadGateway(String),
/// Specific Cloudflare errors.
CloudflareError(CloudflareError),
/// Another error occurred.
OtherBadStatus(anyhow::Error),
/// Reqwest Error, eg. connection failures.
WreqError(wreq::Error),
/// std::io Error that arises from our end, eg. reading from the filesystem.
IoError(io::Error),
/// anyhow::Error arises from our end
AnyhowError(anyhow::Error),
/// Issue with setting credentials
SoraCredentialBuilderError(String),
/// serde_json::Error, likely from JSON deserialization schema mismatch.
JsonError(serde_json::Error),
/// serde_json::Error, likely from JSON deserialization schema mismatch.
/// Includes the original body.
JsonErrorWithBody(serde_json::Error, String),
}
impl SoraError {
pub fn is_sora_having_downtime_issues(&self) -> bool {
match self {
Self::BadGateway(_) => true,
Self::CloudflareError(CloudflareError::GatewayTimeout504) => true,
Self::CloudflareError(CloudflareError::TimeoutOccurred524) => true,
_ => false,
}
}
}
impl Error for SoraError {}
impl Display for SoraError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoBearerTokenAvailable => {
write!(f, "Unauthorized: no bearer token set")
}
Self::FailedToGenerateBearer => {
write!(f, "Failed to generate bearer. Probably invalid session.")
}
Self::SoraUsernameNotYetCreated => {
write!(f, "Sora username not created. Please set up a username.")
}
Self::LocalJwtClaimsParseError(message) => {
write!(f, "Local JWT claims parse error: {}", message)
}
Self::TooManyConcurrentTasks => {
write!(f, "Too many concurrent tasks. Please wait.")
}
Self::UnauthorizedCookieOrBearerExpired => {
write!(f, "Unauthorized: cookie and/or bearer token expired")
}
Self::BadGateway(message) => {
write!(f, "Bad Gateway; Sora is likely having issues: {:?}", message)
}
Self::CloudflareError(error) => {
write!(f, "Cloudflare error: {}", error)
}
Self::OtherBadStatus(err) => {
write!(f, "Other error: {}", err)
}
Self::WreqError(err) => {
write!(f, "Wreq error: {}", err)
}
Self::IoError(err) => {
write!(f, "IO error: {}", err)
}
Self::AnyhowError(err) => {
write!(f, "Anyhow error: {}", err)
}
Self::SoraCredentialBuilderError(message) => {
write!(f, "Sora credential builder error: {}", message)
}
Self::JsonError(err) => {
write!(f, "serde_json error: {}", err)
}
Self::JsonErrorWithBody(err, message) => {
write!(f, "serde_json error: {:?} body: {:?}", err, message)
}
}
}
}
impl From<wreq::Error> for SoraError {
fn from(err: wreq::Error) -> SoraError {
Self::WreqError(err)
}
}
impl From<io::Error> for SoraError {
fn from(err: io::Error) -> Self {
Self::IoError(err)
}
}
impl From<anyhow::Error> for SoraError {
fn from(err: anyhow::Error) -> Self {
Self::AnyhowError(err)
}
}
impl From<serde_json::Error> for SoraError {
fn from(err: serde_json::Error) -> Self {
Self::JsonError(err)
}
}
impl From<CloudflareError> for SoraError {
fn from(err: CloudflareError) -> Self {
Self::CloudflareError(err)
}
}
@@ -1,4 +1,5 @@
use crate::sora_error::SoraError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::utils::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
/// This assumes the request failed and returned a non-200.
@@ -7,7 +8,7 @@ pub async fn classify_general_http_error(response: wreq::Response) -> SoraError
let status = response.status();
let message = match response.text().await {
Ok(text) => text,
Err(err) => return SoraError::WreqError(err),
Err(err) => return SoraGenericApiError::WreqError(err).into(),
};
classify_general_http_status_code_and_body(status, &message).await
@@ -1,4 +1,6 @@
use crate::sora_error::SoraError;
use crate::error::sora_error::SoraError;
use crate::error::sora_generic_api_error::SoraGenericApiError;
use crate::error::sora_specific_api_error::SoraSpecificApiError;
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
use wreq::StatusCode;
/*
@@ -38,7 +40,7 @@ pub async fn classify_general_http_status_code_and_body(status: StatusCode, resp
|| message.contains("token_expired");
if cookie_expired {
return SoraError::UnauthorizedCookieOrBearerExpired;
return SoraSpecificApiError::UnauthorizedCookieOrBearerExpired.into();
}
let needs_onboarding =
@@ -46,21 +48,24 @@ pub async fn classify_general_http_status_code_and_body(status: StatusCode, resp
|| message.contains("onboarding_required");
if needs_onboarding {
return SoraError::SoraUsernameNotYetCreated;
return SoraSpecificApiError::SoraUsernameNotYetCreated.into();
}
let status_code = status.as_u16();
if let Err(err) = filter_cloudflare_errors(status_code, &response_body) {
return SoraError::CloudflareError(err);
return SoraGenericApiError::CloudflareError(err).into();
}
match status_code {
502 => {
return SoraError::BadGateway(message);
return SoraGenericApiError::Http502ErrorBadGateway(message.to_string()).into();
}
_ => {}, // Fall-through
}
SoraError::OtherBadStatus(anyhow::anyhow!("Upload failed with status {}: {}", status, message))
SoraGenericApiError::UncategorizedBadResponseWithStatusAndBody {
status_code: status,
body: response_body.to_string(),
}.into()
}
@@ -1,7 +1,7 @@
//! Adapted from 'jwt' crate
use crate::sora_error::SoraError;
use anyhow::anyhow;
use crate::error::sora_client_error::SoraClientError;
use crate::error::sora_error::SoraError;
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use base64::Engine;
use chrono::{DateTime, Utc};
@@ -24,11 +24,10 @@ pub fn lightweight_sora_jwt_parse(token: &str) -> Result<SoraJwtClaims, SoraErro
let [_header_str, claims_str, _signature_str] = split_components(token)?;
let claims = decode_base64(claims_str)
.map_err(|err| SoraError::LocalJwtClaimsParseError(
format!("failure to parse claims base64: {:?}", err)))?;
.map_err(|err| as_jwt_error(format!("failure to parse claims base64: {:?}", err)))?;
let claims : serde_json::Map<String, Value> = serde_json::from_str(&claims)
.map_err(|err| SoraError::LocalJwtClaimsParseError(
.map_err(|err| as_jwt_error(
format!("failure to parse claims json: {:?}", err)))?;
let iat = claims.get("iat")
@@ -110,7 +109,11 @@ fn decode_base64(raw: &str) -> AnyhowResult<String> {
}
fn jwt_error(reason: &str) -> SoraError {
SoraError::LocalJwtClaimsParseError(reason.to_string())
SoraError::Client(SoraClientError::LocalJwtClaimsParseError(reason.to_string()))
}
fn as_jwt_error(reason: String) -> SoraError {
SoraError::Client(SoraClientError::LocalJwtClaimsParseError(reason))
}
#[cfg(test)]
@@ -3,7 +3,7 @@ use base64::DecodeError;
use errors::AnyhowError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use midjourney_client::error::midjourney_error::MidjourneyError;
use openai_sora_client::sora_error::SoraError;
use openai_sora_client::error::sora_error::SoraError;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
@@ -32,7 +32,6 @@ use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_rem
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::sora_error::SoraError;
use serde_derive::{Deserialize, Serialize};
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
use std::time::Duration;
@@ -35,7 +35,6 @@ use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_rem
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::sora_error::SoraError;
use serde_derive::{Deserialize, Serialize};
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
use std::time::Duration;
@@ -34,7 +34,6 @@ use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_rem
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::sora_error::SoraError;
use std::time::Duration;
use storyteller_client::endpoints::media_files::get_media_file::get_media_file;
use tauri::AppHandle;
@@ -37,7 +37,6 @@ use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_rem
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::sora_error::SoraError;
use serde_derive::{Deserialize, Serialize};
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
use std::time::Duration;
@@ -14,15 +14,16 @@ use crate::services::sora::state::sora_credential_manager::SoraCredentialManager
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use errors::AnyhowError;
use log::{error, info, warn};
use openai_sora_client::error::sora_error::SoraError;
use openai_sora_client::error::sora_specific_api_error::SoraSpecificApiError;
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::sora_error::SoraError;
use serde_derive::{Deserialize, Serialize};
use std::time::Duration;
use storyteller_client::error::storyteller_error::StorytellerError;
use storyteller_client::endpoints::media_files::get_media_file::get_media_file;
use storyteller_client::error::storyteller_error::StorytellerError;
use tauri::{AppHandle, Manager, State};
use tokens::tokens::media_files::MediaFileToken;
@@ -135,12 +136,12 @@ pub async fn sora_image_remix_command(
let mut error_message = "An error occurred. Please try again. If it continues, please tell our staff about the problem.";
match (err) {
InnerError::SoraError(SoraError::TooManyConcurrentTasks) => {
InnerError::SoraError(SoraError::ApiSpecific(SoraSpecificApiError::TooManyConcurrentTasks)) => {
error_type = SoraImageRemixErrorType::TooManyConcurrentTasks;
status = CommandErrorStatus::ServerError;
error_message = "You have too many Sora image generation tasks running. Please wait a moment.";
},
InnerError::SoraError(SoraError::SoraUsernameNotYetCreated) => {
InnerError::SoraError(SoraError::ApiSpecific(SoraSpecificApiError::SoraUsernameNotYetCreated)) => {
error_type = SoraImageRemixErrorType::SoraUsernameNotYetCreated;
status = CommandErrorStatus::BadRequest;
error_message = "Your Sora username is not yet created. Please visit the Sora.com website to create it first.";
@@ -22,9 +22,9 @@ pub fn get_sora_credentials_from_redis(
match (bearer, cookie, sentinel) {
(Some(b), Some(c), Some(s)) => {
let creds = SoraCredentialBuilder::new()
.jwt_bearer_token(&b)
.cookies(&c)
.sora_sentinel(&s)
.with_jwt_bearer_token(&b)
.with_cookies(&c)
.with_sora_sentinel(&s)
.build()?;
Ok(creds)
}
@@ -27,9 +27,9 @@ pub fn get_sora_credentials_from_request(
info!("Sora credentials were present in HTTP request headers!");
let mut creds = SoraCredentialBuilder::new()
.cookies(cookie)
.jwt_bearer_token(bearer)
.sora_sentinel(sentinel)
.with_cookies(cookie)
.with_jwt_bearer_token(bearer)
.with_sora_sentinel(sentinel)
.build()?;
return Ok(creds);
@@ -44,15 +44,15 @@ pub fn get_sora_credentials_from_request(
let mut builder = credentials.to_builder();
if let Some(bearer) = sora_bearer {
info!("override bearer value");
builder = builder.jwt_bearer_token(&bearer);
builder = builder.with_jwt_bearer_token(&bearer);
}
if let Some(cookie) = sora_cookie {
info!("override cookie value");
builder = builder.cookies(&cookie)
builder = builder.with_cookies(&cookie)
}
if let Some(sentinel) = sora_sentinel {
info!("override sentinel value");
builder = builder.sora_sentinel(&sentinel);
builder = builder.with_sora_sentinel(&sentinel);
}
return Ok(builder.build()?);
},