Add consistent error handling to text_embeddings and count_tokens

- Check HTTP status before parsing response body, matching the
  pattern used by generate_content and predict_image
- Unwrap TextEmbeddingResponse enum, returning TextEmbeddingResponseOk
- Extract CountTokensResponseResult struct and add into_result(),
  returning the unwrapped result instead of the raw enum
- All endpoints now consistently return the success type directly
  and surface API errors as GeminiError or GenericApiError
This commit is contained in:
2026-01-30 20:32:40 +00:00
parent eb38c65ac5
commit a8fbe658bb
2 changed files with 65 additions and 12 deletions

View File

@@ -98,7 +98,7 @@ impl GeminiClient {
&self,
request: &TextEmbeddingRequest,
model: &str,
) -> GeminiResult<TextEmbeddingResponse> {
) -> GeminiResult<TextEmbeddingResponseOk> {
let endpoint_url =
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:predict");
let resp = self
@@ -108,16 +108,37 @@ impl GeminiClient {
.json(&request)
.send()
.await?;
let status = resp.status();
let txt_json = resp.text().await?;
tracing::debug!("text_embeddings response: {:?}", txt_json);
Ok(serde_json::from_str::<TextEmbeddingResponse>(&txt_json)?)
if !status.is_success() {
if let Ok(gemini_error) =
serde_json::from_str::<crate::types::GeminiApiError>(&txt_json)
{
return Err(GeminiError::GeminiError(gemini_error));
}
return Err(GeminiError::GenericApiError {
status: status.as_u16(),
body: txt_json,
});
}
match serde_json::from_str::<TextEmbeddingResponse>(&txt_json) {
Ok(response) => Ok(response.into_result()?),
Err(e) => {
error!(response = txt_json, error = ?e, "Failed to parse response");
Err(e.into())
}
}
}
pub async fn count_tokens(
&self,
request: &CountTokensRequest,
model: &str,
) -> GeminiResult<CountTokensResponse> {
) -> GeminiResult<CountTokensResponseResult> {
let endpoint_url =
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:countTokens");
let resp = self
@@ -128,9 +149,29 @@ impl GeminiClient {
.send()
.await?;
let status = resp.status();
let txt_json = resp.text().await?;
tracing::debug!("count_tokens response: {:?}", txt_json);
Ok(serde_json::from_str(&txt_json)?)
if !status.is_success() {
if let Ok(gemini_error) =
serde_json::from_str::<crate::types::GeminiApiError>(&txt_json)
{
return Err(GeminiError::GeminiError(gemini_error));
}
return Err(GeminiError::GenericApiError {
status: status.as_u16(),
body: txt_json,
});
}
match serde_json::from_str::<CountTokensResponse>(&txt_json) {
Ok(response) => Ok(response.into_result()?),
Err(e) => {
error!(response = txt_json, error = ?e, "Failed to parse response");
Err(e.into())
}
}
}
pub async fn predict_image(