feat: enhance HTTP error handling with detailed status code processing
parent
772280da20
commit
babbcb8e03
18
src/error.rs
18
src/error.rs
|
@ -28,6 +28,24 @@ pub enum ValidatorError {
|
|||
|
||||
#[error("Invalid Google API key format: {0}")]
|
||||
KeyFormatInvalid(String),
|
||||
|
||||
#[error("HTTP 400 Bad Request: {body}")]
|
||||
HttpBadRequest { body: String },
|
||||
|
||||
#[error("HTTP 401 Unauthorized: {body}")]
|
||||
HttpUnauthorized { body: String },
|
||||
|
||||
#[error("HTTP 403 Forbidden: {body}")]
|
||||
HttpForbidden { body: String },
|
||||
|
||||
#[error("HTTP 429 Too Many Requests: {body}")]
|
||||
HttpTooManyRequests { body: String },
|
||||
|
||||
#[error("HTTP {status} Client Error: {body}")]
|
||||
HttpClientError { status: u16, body: String },
|
||||
|
||||
#[error("HTTP {status} Server Error: {body}")]
|
||||
HttpServerError { status: u16, body: String },
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for ValidatorError {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use backon::{ExponentialBuilder, Retryable};
|
||||
use reqwest::{Client, StatusCode};
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
use tokio::time::Duration;
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
use crate::error::ValidatorError;
|
||||
use crate::types::GeminiKey;
|
||||
|
||||
pub async fn send_request(
|
||||
|
@ -13,7 +14,7 @@ pub async fn send_request(
|
|||
key: GeminiKey,
|
||||
payload: &Value,
|
||||
max_retries: usize,
|
||||
) -> Result<reqwest::Response, reqwest::Error> {
|
||||
) -> Result<(), ValidatorError> {
|
||||
let retry_policy = ExponentialBuilder::default()
|
||||
.with_max_times(max_retries)
|
||||
.with_min_delay(Duration::from_secs(1))
|
||||
|
@ -27,14 +28,49 @@ pub async fn send_request(
|
|||
.json(payload)
|
||||
.send()
|
||||
.await?;
|
||||
debug!("Response for key {}: {:?}", key.as_ref(), response.status());
|
||||
response.error_for_status()
|
||||
|
||||
let status = response.status();
|
||||
|
||||
if status.is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
let body = response.text().await.map_err(ValidatorError::from)?;
|
||||
debug!(
|
||||
"Response for key {}: status={:?}, body={}",
|
||||
key.as_ref(),
|
||||
status,
|
||||
body
|
||||
);
|
||||
|
||||
let status_code = status.as_u16();
|
||||
match status_code {
|
||||
400 => Err(ValidatorError::HttpBadRequest { body }),
|
||||
401 => Err(ValidatorError::HttpUnauthorized { body }),
|
||||
403 => Err(ValidatorError::HttpForbidden { body }),
|
||||
429 => Err(ValidatorError::HttpTooManyRequests { body }),
|
||||
400..=499 => Err(ValidatorError::HttpClientError {
|
||||
status: status_code,
|
||||
body,
|
||||
}),
|
||||
500..=599 => Err(ValidatorError::HttpServerError {
|
||||
status: status_code,
|
||||
body,
|
||||
}),
|
||||
_ => {
|
||||
// For other status codes, treat as client error
|
||||
Err(ValidatorError::HttpClientError {
|
||||
status: status_code,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.retry(&retry_policy)
|
||||
.when(|e: &reqwest::Error| {
|
||||
.when(|error: &ValidatorError| {
|
||||
!matches!(
|
||||
e.status(),
|
||||
Some(StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED)
|
||||
error,
|
||||
ValidatorError::HttpUnauthorized { .. } | ValidatorError::HttpForbidden { .. }
|
||||
)
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use reqwest::{Client, IntoUrl, StatusCode};
|
||||
use reqwest::{Client, IntoUrl};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::{CACHE_CONTENT_TEST_BODY, GENERATE_CONTENT_TEST_BODY};
|
||||
|
@ -28,10 +28,11 @@ pub async fn test_generate_content_api(
|
|||
info!("SUCCESS - {}... - Valid key found", &api_key.as_ref()[..10]);
|
||||
Ok(api_key)
|
||||
}
|
||||
Err(e) => match e.status() {
|
||||
Some(
|
||||
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN | StatusCode::TOO_MANY_REQUESTS,
|
||||
) => {
|
||||
Err(e) => match &e {
|
||||
ValidatorError::HttpBadRequest { .. }
|
||||
| ValidatorError::HttpUnauthorized { .. }
|
||||
| ValidatorError::HttpForbidden { .. }
|
||||
| ValidatorError::HttpTooManyRequests { .. } => {
|
||||
warn!(
|
||||
"INVALID - {}... - {}",
|
||||
&api_key.as_ref()[..10],
|
||||
|
@ -40,9 +41,8 @@ pub async fn test_generate_content_api(
|
|||
Err(ValidatorError::KeyInvalid)
|
||||
}
|
||||
_ => {
|
||||
let req_error = ValidatorError::from(e);
|
||||
error!("ERROR- {}... - {}", &api_key.as_ref()[..10], req_error);
|
||||
Err(req_error)
|
||||
error!("ERROR- {}... - {}", &api_key.as_ref()[..10], e);
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -72,10 +72,11 @@ pub async fn test_cache_content_api(
|
|||
);
|
||||
Ok(api_key)
|
||||
}
|
||||
Err(e) => match e.status() {
|
||||
Some(
|
||||
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN | StatusCode::TOO_MANY_REQUESTS,
|
||||
) => {
|
||||
Err(e) => match &e {
|
||||
ValidatorError::HttpBadRequest { .. }
|
||||
| ValidatorError::HttpUnauthorized { .. }
|
||||
| ValidatorError::HttpForbidden { .. }
|
||||
| ValidatorError::HttpTooManyRequests { .. } => {
|
||||
warn!(
|
||||
"CACHE INVALID - {}... - {}",
|
||||
&api_key.as_ref()[..10],
|
||||
|
@ -84,13 +85,8 @@ pub async fn test_cache_content_api(
|
|||
Err(ValidatorError::KeyInvalid)
|
||||
}
|
||||
_ => {
|
||||
let req_error = ValidatorError::from(e);
|
||||
error!(
|
||||
"CACHE ERROR - {}... - {}",
|
||||
&api_key.as_ref()[..10],
|
||||
req_error
|
||||
);
|
||||
Err(req_error)
|
||||
error!("CACHE ERROR - {}... - {}", &api_key.as_ref()[..10], e);
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ pub struct ContentPart {
|
|||
pub role: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ThinkingConfig {
|
||||
#[serde(rename = "thinkingBudget")]
|
||||
|
@ -66,9 +65,9 @@ pub static GENERATE_CONTENT_TEST_BODY: LazyLock<Value> = LazyLock::new(|| {
|
|||
// LazyLock for the cached content test body used in cache API validation
|
||||
pub static CACHE_CONTENT_TEST_BODY: LazyLock<Value> = LazyLock::new(|| {
|
||||
// Generate random text content to meet the minimum 1024 tokens requirement for cache API
|
||||
let long_text = "You are an expert at analyzing transcripts.".repeat(50);
|
||||
let long_text = "You are an expert at analyzing transcripts.".repeat(150);
|
||||
let cache_request = GeminiRequest {
|
||||
model: Some("models/gemini-2.5-flash-lite".to_string()),
|
||||
model: Some("models/gemini-2.5-flash".to_string()),
|
||||
contents: vec![ContentPart {
|
||||
parts: vec![TextPart { text: long_text }],
|
||||
role: Some("user".to_string()),
|
||||
|
|
Loading…
Reference in New Issue