use thiserror::Error;
#[derive(Error, Debug)]
pub enum SerperError {
#[error("HTTP request failed: {0}")]
Request(#[from] reqwest::Error),
#[error("JSON parsing failed: {0}")]
Json(#[from] serde_json::Error),
#[error("API error: {message}")]
Api {
message: String,
},
#[error("Invalid API key")]
InvalidApiKey,
#[error("Configuration error: {message}")]
Config {
message: String,
},
#[error("Validation error: {message}")]
Validation {
message: String,
},
}
impl SerperError {
pub fn api_error(message: impl Into<String>) -> Self {
Self::Api {
message: message.into(),
}
}
pub fn config_error(message: impl Into<String>) -> Self {
Self::Config {
message: message.into(),
}
}
pub fn validation_error(message: impl Into<String>) -> Self {
Self::Validation {
message: message.into(),
}
}
pub fn is_auth_error(&self) -> bool {
matches!(self, SerperError::InvalidApiKey)
}
pub fn is_network_error(&self) -> bool {
matches!(self, SerperError::Request(_))
}
pub fn is_parse_error(&self) -> bool {
matches!(self, SerperError::Json(_))
}
pub fn is_api_error(&self) -> bool {
matches!(self, SerperError::Api { .. })
}
}
pub type Result<T> = std::result::Result<T, SerperError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_api_key_error_display() {
let error = SerperError::InvalidApiKey;
assert_eq!(error.to_string(), "Invalid API key");
assert!(error.is_auth_error());
}
#[test]
fn test_api_error_display() {
let error = SerperError::Api {
message: "Rate limit exceeded".to_string(),
};
assert_eq!(error.to_string(), "API error: Rate limit exceeded");
assert!(error.is_api_error());
}
#[test]
fn test_config_error() {
let error = SerperError::config_error("Invalid timeout");
match error {
SerperError::Config { message } => {
assert_eq!(message, "Invalid timeout");
}
_ => panic!("Expected Config error"),
}
}
#[test]
fn test_validation_error() {
let error = SerperError::validation_error("Empty query string");
match error {
SerperError::Validation { message } => {
assert_eq!(message, "Empty query string");
}
_ => panic!("Expected Validation error"),
}
}
#[test]
fn test_json_error_conversion() {
let json_error = serde_json::from_str::<i32>("invalid json");
match json_error {
Err(e) => {
let serper_error: SerperError = e.into();
assert!(serper_error.is_parse_error());
}
Ok(_) => panic!("Expected error"),
}
}
#[test]
fn test_error_variants() {
let api_key_error = SerperError::InvalidApiKey;
let api_error = SerperError::Api {
message: "test".to_string(),
};
match api_key_error {
SerperError::InvalidApiKey => {}
_ => panic!("Expected InvalidApiKey variant"),
}
match api_error {
SerperError::Api { message } => {
assert_eq!(message, "test");
}
_ => panic!("Expected Api variant"),
}
}
#[test]
#[allow(clippy::unnecessary_literal_unwrap)]
fn test_result_type_alias() {
let success_result: Result<i32> = Ok(42);
assert_eq!(success_result.unwrap(), 42);
let error_result: Result<i32> = Err(SerperError::InvalidApiKey);
assert!(error_result.is_err());
}
#[test]
fn test_error_classification() {
let auth_error = SerperError::InvalidApiKey;
let parse_error = SerperError::Json(
serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err(),
);
let api_error = SerperError::api_error("Not found");
assert!(auth_error.is_auth_error());
assert!(parse_error.is_parse_error());
assert!(api_error.is_api_error());
}
}