1use thiserror::Error;
6
7#[derive(Error, Debug)]
12pub enum SerperError {
13 #[error("HTTP request failed: {0}")]
17 Request(#[from] reqwest::Error),
18
19 #[error("JSON parsing failed: {0}")]
24 Json(#[from] serde_json::Error),
25
26 #[error("API error: {message}")]
30 Api {
31 message: String,
33 },
34
35 #[error("Invalid API key")]
39 InvalidApiKey,
40
41 #[error("Configuration error: {message}")]
45 Config {
46 message: String,
48 },
49
50 #[error("Validation error: {message}")]
54 Validation {
55 message: String,
57 },
58}
59
60impl SerperError {
61 pub fn api_error(message: impl Into<String>) -> Self {
63 Self::Api {
64 message: message.into(),
65 }
66 }
67
68 pub fn config_error(message: impl Into<String>) -> Self {
70 Self::Config {
71 message: message.into(),
72 }
73 }
74
75 pub fn validation_error(message: impl Into<String>) -> Self {
77 Self::Validation {
78 message: message.into(),
79 }
80 }
81
82 pub fn is_auth_error(&self) -> bool {
84 matches!(self, SerperError::InvalidApiKey)
85 }
86
87 pub fn is_network_error(&self) -> bool {
89 matches!(self, SerperError::Request(_))
90 }
91
92 pub fn is_parse_error(&self) -> bool {
94 matches!(self, SerperError::Json(_))
95 }
96
97 pub fn is_api_error(&self) -> bool {
99 matches!(self, SerperError::Api { .. })
100 }
101}
102
103pub type Result<T> = std::result::Result<T, SerperError>;
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_invalid_api_key_error_display() {
112 let error = SerperError::InvalidApiKey;
113 assert_eq!(error.to_string(), "Invalid API key");
114 assert!(error.is_auth_error());
115 }
116
117 #[test]
118 fn test_api_error_display() {
119 let error = SerperError::Api {
120 message: "Rate limit exceeded".to_string(),
121 };
122 assert_eq!(error.to_string(), "API error: Rate limit exceeded");
123 assert!(error.is_api_error());
124 }
125
126 #[test]
127 fn test_config_error() {
128 let error = SerperError::config_error("Invalid timeout");
129 match error {
130 SerperError::Config { message } => {
131 assert_eq!(message, "Invalid timeout");
132 }
133 _ => panic!("Expected Config error"),
134 }
135 }
136
137 #[test]
138 fn test_validation_error() {
139 let error = SerperError::validation_error("Empty query string");
140 match error {
141 SerperError::Validation { message } => {
142 assert_eq!(message, "Empty query string");
143 }
144 _ => panic!("Expected Validation error"),
145 }
146 }
147
148 #[test]
149 fn test_json_error_conversion() {
150 let json_error = serde_json::from_str::<i32>("invalid json");
151 match json_error {
152 Err(e) => {
153 let serper_error: SerperError = e.into();
154 assert!(serper_error.is_parse_error());
155 }
156 Ok(_) => panic!("Expected error"),
157 }
158 }
159
160 #[test]
161 fn test_error_variants() {
162 let api_key_error = SerperError::InvalidApiKey;
163 let api_error = SerperError::Api {
164 message: "test".to_string(),
165 };
166
167 match api_key_error {
169 SerperError::InvalidApiKey => {}
170 _ => panic!("Expected InvalidApiKey variant"),
171 }
172
173 match api_error {
174 SerperError::Api { message } => {
175 assert_eq!(message, "test");
176 }
177 _ => panic!("Expected Api variant"),
178 }
179 }
180
181 #[test]
182 #[allow(clippy::unnecessary_literal_unwrap)]
183 fn test_result_type_alias() {
184 let success_result: Result<i32> = Ok(42);
185 assert_eq!(success_result.unwrap(), 42);
186
187 let error_result: Result<i32> = Err(SerperError::InvalidApiKey);
188 assert!(error_result.is_err());
189 }
190
191 #[test]
192 fn test_error_classification() {
193 let auth_error = SerperError::InvalidApiKey;
194 let parse_error = SerperError::Json(
195 serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err(),
196 );
197 let api_error = SerperError::api_error("Not found");
198
199 assert!(auth_error.is_auth_error());
200 assert!(parse_error.is_parse_error());
201 assert!(api_error.is_api_error());
202 }
203}