use std::{error::Error as StdError, fmt};
use crate::{LimitPeriod, Money};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
InvalidApiKey,
InvalidInput(String),
Accounting(String),
Transport(String),
Provider {
status: u16,
code: Option<String>,
message: String,
},
Protocol(String),
SpendingLimitReached {
period: LimitPeriod,
limit: Money,
spent: Money,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidApiKey => f.write_str("the Gemini API key is empty or invalid"),
Self::InvalidInput(message) => write!(f, "invalid Gemini request: {message}"),
Self::Accounting(message) => write!(f, "Gemini accounting failed: {message}"),
Self::Transport(message) => write!(f, "Gemini transport failed: {message}"),
Self::Provider {
status,
code,
message,
} => match code {
Some(code) => write!(f, "Gemini returned HTTP {status} ({code}): {message}"),
None => write!(f, "Gemini returned HTTP {status}: {message}"),
},
Self::Protocol(message) => write!(f, "invalid Gemini response: {message}"),
Self::SpendingLimitReached {
period,
limit,
spent,
} => write!(
f,
"Gemini {period} spending limit reached (spent {spent}, limit {limit})"
),
}
}
}
impl StdError for Error {}
pub(crate) fn transport(error: impl fmt::Display) -> Error {
let message = error.to_string();
Error::Transport(if message.to_ascii_lowercase().contains("timed out") {
"the request timed out".into()
} else {
"the service could not be reached".into()
})
}
pub(crate) fn clean_message(value: &str, limit: usize) -> String {
let value = value
.chars()
.map(|character| {
if character.is_control() {
' '
} else {
character
}
})
.take(limit)
.collect::<String>()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
if value.is_empty() {
"unknown error".into()
} else {
value
}
}