1use serde::{Deserialize, Serialize};
2use std::env::VarError;
3use std::fmt;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ApiError {
7 pub description: String,
8}
9
10impl fmt::Display for ApiError {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 write!(f, "API Error: {}", self.description)
13 }
14}
15
16impl std::error::Error for ApiError {}
17
18#[derive(Debug)]
19pub struct OtlpError {
20 pub message: String,
21}
22
23impl fmt::Display for OtlpError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "{}", self.message)
26 }
27}
28
29impl std::error::Error for OtlpError {}
30
31impl From<String> for OtlpError {
32 fn from(message: String) -> Self {
33 OtlpError { message }
34 }
35}
36
37impl From<Box<dyn std::error::Error>> for OtlpError {
38 fn from(err: Box<dyn std::error::Error>) -> Self {
39 OtlpError {
40 message: err.to_string(),
41 }
42 }
43}
44
45#[derive(Debug)]
46pub enum BotError {
47 Api(ApiError),
49 Network(reqwest::Error),
51 #[cfg(feature = "grpc")]
53 Grpc(tonic::transport::Error),
54 Serialization(serde_json::Error),
56 Url(url::ParseError),
58 Io(std::io::Error),
60 #[cfg(feature = "templates")]
62 Template(tera::Error),
63 Config(String),
65 Validation(String),
67 UrlParams(serde_url_params::Error),
69 System(String),
71 Environment(std::env::VarError),
73 Otlp(OtlpError),
75}
76
77impl fmt::Display for BotError {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 BotError::Api(e) => write!(f, "API Error: {e}"),
81 BotError::Network(e) => write!(f, "Network Error: {e}"),
82 #[cfg(feature = "grpc")]
83 BotError::Grpc(e) => write!(f, "gRPC Error: {e}"),
84 BotError::Serialization(e) => write!(f, "Serialization Error: {e}"),
85 BotError::Url(e) => write!(f, "URL Error: {e}"),
86 BotError::Io(e) => write!(f, "IO Error: {e}"),
87 #[cfg(feature = "templates")]
88 BotError::Template(e) => write!(f, "Template Error: {e}"),
89 BotError::Config(e) => write!(f, "Config Error: {e}"),
90 BotError::Validation(e) => write!(f, "Validation Error: {e}"),
91 BotError::UrlParams(e) => write!(f, "URL Parameters Error: {e}"),
92 BotError::System(e) => write!(f, "System Error: {e}"),
93 BotError::Environment(e) => write!(f, "Environment Error: {e}"),
94 BotError::Otlp(e) => write!(f, "Otlp Error: {e}"),
95 }
96 }
97}
98
99impl std::error::Error for BotError {
100 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
101 match self {
102 BotError::Api(e) => Some(e),
103 BotError::Network(e) => Some(e),
104 #[cfg(feature = "grpc")]
105 BotError::Grpc(e) => Some(e),
106 BotError::Serialization(e) => Some(e),
107 BotError::Url(e) => Some(e),
108 BotError::Io(e) => Some(e),
109 #[cfg(feature = "templates")]
110 BotError::Template(e) => Some(e),
111 BotError::Config(_) => None,
112 BotError::Validation(_) => None,
113 BotError::UrlParams(e) => Some(e),
114 BotError::System(_) => None,
115 BotError::Environment(e) => Some(e),
116 BotError::Otlp(e) => Some(e),
117 }
118 }
119}
120
121impl From<reqwest::Error> for BotError {
122 fn from(err: reqwest::Error) -> Self {
123 BotError::Network(err)
124 }
125}
126
127impl From<serde_json::Error> for BotError {
128 fn from(err: serde_json::Error) -> Self {
129 BotError::Serialization(err)
130 }
131}
132
133impl From<url::ParseError> for BotError {
134 fn from(err: url::ParseError) -> Self {
135 BotError::Url(err)
136 }
137}
138
139impl From<std::io::Error> for BotError {
140 fn from(err: std::io::Error) -> Self {
141 BotError::Io(err)
142 }
143}
144
145#[cfg(feature = "templates")]
146impl From<tera::Error> for BotError {
147 fn from(err: tera::Error) -> Self {
148 BotError::Template(err)
149 }
150}
151
152impl From<serde_url_params::Error> for BotError {
153 fn from(err: serde_url_params::Error) -> Self {
154 BotError::UrlParams(err)
155 }
156}
157#[cfg(feature = "grpc")]
158impl From<tonic::transport::Error> for BotError {
159 fn from(err: tonic::transport::Error) -> Self {
160 BotError::Grpc(err)
161 }
162}
163
164impl From<toml::de::Error> for BotError {
165 fn from(err: toml::de::Error) -> Self {
166 BotError::Config(err.to_string())
167 }
168}
169
170impl From<VarError> for BotError {
171 fn from(err: VarError) -> Self {
172 BotError::Environment(err)
173 }
174}
175
176impl From<ApiError> for BotError {
177 fn from(err: ApiError) -> Self {
178 BotError::Api(err)
179 }
180}
181
182pub type Result<T> = std::result::Result<T, BotError>;
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use serde_json;
188 use url::ParseError;
189
190 #[test]
191 fn test_api_error_display() {
192 let err = ApiError {
193 description: "fail".to_string(),
194 };
195 assert_eq!(format!("{err}"), "API Error: fail");
196 }
197
198 #[test]
199 fn test_otlp_error_display_and_from() {
200 let err = OtlpError {
201 message: "otlp fail".to_string(),
202 };
203 assert_eq!(format!("{err}"), "otlp fail");
204 let from_str: OtlpError = "msg".to_string().into();
205 assert_eq!(from_str.message, "msg");
206 let boxed: OtlpError =
207 Box::<dyn std::error::Error>::from(std::io::Error::other("err")).into();
208 assert!(boxed.message.contains("err"));
209 }
210
211 #[test]
212 fn test_bot_error_display_and_from() {
213 let api = ApiError {
214 description: "api".to_string(),
215 };
216 let err = BotError::Api(api.clone());
217 assert!(format!("{err}").contains("API Error"));
218 let ser = BotError::Serialization(serde_json::Error::io(std::io::Error::other("ser")));
219 assert!(format!("{ser}").contains("Serialization Error"));
220 let url = BotError::Url(ParseError::EmptyHost);
221 assert!(format!("{url}").contains("URL Error"));
222 let ioerr = BotError::Io(std::io::Error::other("io"));
223 assert!(format!("{ioerr}").contains("IO Error"));
224 let conf = BotError::Config("conf".to_string());
225 assert!(format!("{conf}").contains("Config Error"));
226 let val = BotError::Validation("val".to_string());
227 assert!(format!("{val}").contains("Validation Error"));
228 let sys = BotError::System("sys".to_string());
229 assert!(format!("{sys}").contains("System Error"));
230 let otlp = BotError::Otlp(OtlpError {
231 message: "otlp".to_string(),
232 });
233 assert!(format!("{otlp}").contains("Otlp Error"));
234 }
235
236 #[test]
237 fn test_bot_error_from_impls() {
238 let _b: BotError = serde_json::Error::io(std::io::Error::other("ser")).into();
239 let _b: BotError = url::ParseError::EmptyHost.into();
240 let _b: BotError = std::io::Error::other("io").into();
241 let _b: BotError = serde_url_params::Error::unsupported("params").into();
242 let _b: BotError = toml::from_str::<i32>("not toml").unwrap_err().into();
243 let _b: BotError = std::env::VarError::NotPresent.into();
244 let _b: BotError = ApiError {
245 description: "api".to_string(),
246 }
247 .into();
248 }
249
250 #[test]
251 fn test_api_error_serialization() {
252 let error = ApiError {
253 description: "Test error message".to_string(),
254 };
255
256 let serialized = serde_json::to_string(&error).unwrap();
258 assert!(serialized.contains("Test error message"));
259
260 let deserialized: ApiError = serde_json::from_str(&serialized).unwrap();
262 assert_eq!(deserialized.description, "Test error message");
263 }
264
265 #[test]
266 fn test_api_error_clone() {
267 let error = ApiError {
268 description: "Cloneable error".to_string(),
269 };
270 let cloned = error.clone();
271 assert_eq!(error.description, cloned.description);
272 }
273
274 #[test]
275 fn test_api_error_as_std_error() {
276 let error = ApiError {
277 description: "Standard error test".to_string(),
278 };
279
280 let std_err: &dyn std::error::Error = &error;
282 assert!(std_err.source().is_none());
283 }
284
285 #[test]
286 fn test_otlp_error_from_box_error() {
287 let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
288 let boxed: Box<dyn std::error::Error> = Box::new(io_error);
289 let otlp_error: OtlpError = boxed.into();
290
291 assert!(otlp_error.message.contains("Access denied"));
292 }
293
294 #[test]
295 fn test_otlp_error_as_std_error() {
296 let error = OtlpError {
297 message: "OTLP connection failed".to_string(),
298 };
299
300 let std_err: &dyn std::error::Error = &error;
302 assert!(std_err.source().is_none());
303 }
304
305 #[tokio::test]
306 async fn test_bot_error_source_chain() {
307 use std::error::Error;
308
309 let api_err = BotError::Api(ApiError {
311 description: "API failed".to_string(),
312 });
313 assert!(api_err.source().is_some());
314
315 let network_err = BotError::Network(
317 reqwest::ClientBuilder::new()
318 .build()
319 .unwrap()
320 .get("http://invalid-url")
321 .send()
322 .await
323 .unwrap_err(),
324 );
325 assert!(network_err.source().is_some());
326
327 let ser_err = BotError::Serialization(serde_json::Error::io(std::io::Error::other(
329 "serialization",
330 )));
331 assert!(ser_err.source().is_some());
332
333 let url_err = BotError::Url(url::ParseError::EmptyHost);
335 assert!(url_err.source().is_some());
336
337 let io_err = BotError::Io(std::io::Error::new(
339 std::io::ErrorKind::NotFound,
340 "file not found",
341 ));
342 assert!(io_err.source().is_some());
343
344 let params_err = BotError::UrlParams(serde_url_params::Error::unsupported("test"));
346 assert!(params_err.source().is_some());
347
348 let otlp_err = BotError::Otlp(OtlpError {
350 message: "OTLP failed".to_string(),
351 });
352 assert!(otlp_err.source().is_some());
353
354 let config_err = BotError::Config("Config error".to_string());
356 assert!(config_err.source().is_none());
357
358 let validation_err = BotError::Validation("Validation error".to_string());
359 assert!(validation_err.source().is_none());
360
361 let system_err = BotError::System("System error".to_string());
362 assert!(system_err.source().is_none());
363 }
364
365 #[test]
366 fn test_bot_error_debug_format() {
367 let errors = vec![
368 BotError::Api(ApiError {
369 description: "Debug API error".to_string(),
370 }),
371 BotError::Config("Debug config error".to_string()),
372 BotError::Validation("Debug validation error".to_string()),
373 BotError::System("Debug system error".to_string()),
374 BotError::Otlp(OtlpError {
375 message: "Debug OTLP error".to_string(),
376 }),
377 ];
378
379 for error in errors {
380 let debug_str = error.to_string();
381 assert!(!debug_str.is_empty());
382 assert!(debug_str.contains("Error"));
383 }
384 }
385
386 #[test]
387 fn test_error_conversion_chain() {
388 let var_error = std::env::VarError::NotPresent;
390 let bot_error: BotError = var_error.into();
391 match bot_error {
392 BotError::Environment(msg) => assert!(msg.to_string().contains("not found")),
393 _ => panic!("Expected Config error"),
394 }
395
396 let toml_error = toml::from_str::<i32>("invalid toml").unwrap_err();
398 let bot_error: BotError = toml_error.into();
399 match bot_error {
400 BotError::Config(_) => {} _ => panic!("Expected Config error"),
402 }
403 }
404
405 #[tokio::test]
406 async fn test_all_error_types_display() {
407 let test_cases = vec![
408 (
409 BotError::Api(ApiError {
410 description: "API test".to_string(),
411 }),
412 "API Error",
413 ),
414 (
415 BotError::Network(
416 reqwest::ClientBuilder::new()
417 .build()
418 .unwrap()
419 .get("http://test")
420 .send()
421 .await
422 .unwrap_err(),
423 ),
424 "Network Error",
425 ),
426 (
427 BotError::Serialization(serde_json::Error::io(std::io::Error::other("test"))),
428 "Serialization Error",
429 ),
430 (BotError::Url(url::ParseError::EmptyHost), "URL Error"),
431 (BotError::Io(std::io::Error::other("test")), "IO Error"),
432 (BotError::Config("test".to_string()), "Config Error"),
433 (BotError::Validation("test".to_string()), "Validation Error"),
434 (
435 BotError::UrlParams(serde_url_params::Error::unsupported("test")),
436 "URL Parameters Error",
437 ),
438 (BotError::System("test".to_string()), "System Error"),
439 (
440 BotError::Otlp(OtlpError {
441 message: "test".to_string(),
442 }),
443 "Otlp Error",
444 ),
445 ];
446
447 for (error, expected_prefix) in test_cases {
448 let display_str = format!("{error}");
449 assert!(
450 display_str.contains(expected_prefix),
451 "Error '{display_str}' should contain '{expected_prefix}'"
452 );
453 }
454 }
455
456 #[cfg(feature = "templates")]
457 #[test]
458 fn test_template_error_conversion() {
459 use tera::Tera;
460
461 let tera = Tera::new("templates/*").unwrap_or_default();
462 let template_error = tera
463 .render("nonexistent", &tera::Context::new())
464 .unwrap_err();
465 let bot_error: BotError = template_error.into();
466
467 match bot_error {
468 BotError::Template(_) => {} _ => panic!("Expected Template error"),
470 }
471
472 let display_str = format!("{bot_error}");
473 assert!(display_str.contains("Template Error"));
474 }
475
476 #[test]
477 fn test_result_type_alias() {
478 fn test_function() -> Result<String> {
480 Ok("success".to_string())
481 }
482
483 fn test_error_function() -> Result<String> {
484 Err(BotError::Validation("test error".to_string()))
485 }
486
487 assert!(test_function().is_ok());
488 assert_eq!(test_function().unwrap(), "success");
489
490 assert!(test_error_function().is_err());
491 match test_error_function().unwrap_err() {
492 BotError::Validation(msg) => assert_eq!(msg, "test error"),
493 _ => panic!("Expected Validation error"),
494 }
495 }
496
497 #[test]
498 fn test_error_message_content() {
499 let api_error = ApiError {
501 description: "Specific API failure".to_string(),
502 };
503 assert!(format!("{api_error}").contains("Specific API failure"));
504
505 let otlp_error = OtlpError {
506 message: "OTLP connection timeout".to_string(),
507 };
508 assert!(format!("{otlp_error}").contains("OTLP connection timeout"));
509
510 let config_error = BotError::Config("Missing required field".to_string());
511 assert!(format!("{config_error}").contains("Missing required field"));
512 }
513}
514
515#[test]
516fn test_api_error_serialization() {
517 let error = ApiError {
518 description: "Test error message".to_string(),
519 };
520
521 let serialized = serde_json::to_string(&error).unwrap();
523 assert!(serialized.contains("Test error message"));
524
525 let deserialized: ApiError = serde_json::from_str(&serialized).unwrap();
527 assert_eq!(deserialized.description, "Test error message");
528}
529
530#[test]
531fn test_api_error_clone() {
532 let error = ApiError {
533 description: "Cloneable error".to_string(),
534 };
535 let cloned = error.clone();
536 assert_eq!(error.description, cloned.description);
537}
538
539#[test]
540fn test_api_error_as_std_error() {
541 let error = ApiError {
542 description: "Standard error test".to_string(),
543 };
544
545 let std_err: &dyn std::error::Error = &error;
547 assert!(std_err.source().is_none());
548}
549
550#[test]
551fn test_otlp_error_from_box_error() {
552 let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
553 let boxed: Box<dyn std::error::Error> = Box::new(io_error);
554 let otlp_error: OtlpError = boxed.into();
555
556 assert!(otlp_error.message.contains("Access denied"));
557}
558
559#[test]
560fn test_otlp_error_as_std_error() {
561 let error = OtlpError {
562 message: "OTLP connection failed".to_string(),
563 };
564
565 let std_err: &dyn std::error::Error = &error;
567 assert!(std_err.source().is_none());
568}
569
570#[tokio::test]
571async fn test_bot_error_source_chain() {
572 use std::error::Error;
573 let api_err = BotError::Api(ApiError {
575 description: "API failed".to_string(),
576 });
577 assert!(api_err.source().is_some());
578
579 let network_err = BotError::Network(
581 reqwest::ClientBuilder::new()
582 .build()
583 .unwrap()
584 .get("http://invalid-url")
585 .send()
586 .await
587 .unwrap_err(),
588 );
589 assert!(network_err.source().is_some());
590
591 let ser_err = BotError::Serialization(serde_json::Error::io(std::io::Error::other(
593 "serialization",
594 )));
595 assert!(ser_err.source().is_some());
596
597 let url_err = BotError::Url(url::ParseError::EmptyHost);
599 assert!(url_err.source().is_some());
600
601 let io_err = BotError::Io(std::io::Error::new(
603 std::io::ErrorKind::NotFound,
604 "file not found",
605 ));
606 assert!(io_err.source().is_some());
607
608 let params_err = BotError::UrlParams(serde_url_params::Error::unsupported("test"));
610 assert!(params_err.source().is_some());
611
612 let otlp_err = BotError::Otlp(OtlpError {
614 message: "OTLP failed".to_string(),
615 });
616 assert!(otlp_err.source().is_some());
617
618 let config_err = BotError::Config("Config error".to_string());
620 assert!(config_err.source().is_none());
621
622 let validation_err = BotError::Validation("Validation error".to_string());
623 assert!(validation_err.source().is_none());
624
625 let system_err = BotError::System("System error".to_string());
626 assert!(system_err.source().is_none());
627}
628
629#[test]
630fn test_bot_error_debug_format() {
631 let errors = vec![
632 BotError::Api(ApiError {
633 description: "Debug API error".to_string(),
634 }),
635 BotError::Config("Debug config error".to_string()),
636 BotError::Validation("Debug validation error".to_string()),
637 BotError::System("Debug system error".to_string()),
638 BotError::Otlp(OtlpError {
639 message: "Debug OTLP error".to_string(),
640 }),
641 ];
642
643 for error in errors {
644 let debug_str = error.to_string();
645 assert!(!debug_str.is_empty());
646 assert!(debug_str.contains("Error"));
647 }
648}
649
650#[test]
651fn test_error_conversion_chain() {
652 let var_error = std::env::VarError::NotPresent;
654 let bot_error: BotError = var_error.into();
655 match bot_error {
656 BotError::Environment(msg) => assert!(msg.to_string().contains("not found")),
657 _ => panic!("Expected Config error"),
658 }
659
660 let toml_error = toml::from_str::<i32>("invalid toml").unwrap_err();
662 let bot_error: BotError = toml_error.into();
663 match bot_error {
664 BotError::Config(_) => {} _ => panic!("Expected Config error"),
666 }
667}
668
669#[tokio::test]
670async fn test_all_error_types_display() {
671 let test_cases = vec![
672 (
673 BotError::Api(ApiError {
674 description: "API test".to_string(),
675 }),
676 "API Error",
677 ),
678 (
679 BotError::Network(
680 reqwest::ClientBuilder::new()
681 .build()
682 .unwrap()
683 .get("http://test")
684 .send()
685 .await
686 .unwrap_err(),
687 ),
688 "Network Error",
689 ),
690 (
691 BotError::Serialization(serde_json::Error::io(std::io::Error::other("test"))),
692 "Serialization Error",
693 ),
694 (BotError::Url(url::ParseError::EmptyHost), "URL Error"),
695 (BotError::Io(std::io::Error::other("test")), "IO Error"),
696 (BotError::Config("test".to_string()), "Config Error"),
697 (BotError::Validation("test".to_string()), "Validation Error"),
698 (
699 BotError::UrlParams(serde_url_params::Error::unsupported("test")),
700 "URL Parameters Error",
701 ),
702 (BotError::System("test".to_string()), "System Error"),
703 (
704 BotError::Otlp(OtlpError {
705 message: "test".to_string(),
706 }),
707 "Otlp Error",
708 ),
709 ];
710
711 for (error, expected_prefix) in test_cases {
712 let display_str = format!("{error}");
713 assert!(
714 display_str.contains(expected_prefix),
715 "Error '{display_str}' should contain '{expected_prefix}'"
716 );
717 }
718}
719
720#[cfg(feature = "templates")]
721#[test]
722fn test_template_error_conversion() {
723 use tera::Tera;
724
725 let tera = Tera::new("templates/*").unwrap_or_default();
726 let template_error = tera
727 .render("nonexistent", &tera::Context::new())
728 .unwrap_err();
729 let bot_error: BotError = template_error.into();
730
731 match bot_error {
732 BotError::Template(_) => {} _ => panic!("Expected Template error"),
734 }
735
736 let display_str = format!("{bot_error}");
737 assert!(display_str.contains("Template Error"));
738}
739
740#[test]
741fn test_result_type_alias() {
742 fn test_function() -> Result<String> {
744 Ok("success".to_string())
745 }
746
747 fn test_error_function() -> Result<String> {
748 Err(BotError::Validation("test error".to_string()))
749 }
750
751 assert!(test_function().is_ok());
752 assert_eq!(test_function().unwrap(), "success");
753
754 assert!(test_error_function().is_err());
755 match test_error_function().unwrap_err() {
756 BotError::Validation(msg) => assert_eq!(msg, "test error"),
757 _ => panic!("Expected Validation error"),
758 }
759}
760
761#[test]
762fn test_error_message_content() {
763 let api_error = ApiError {
765 description: "Specific API failure".to_string(),
766 };
767 assert!(format!("{api_error}").contains("Specific API failure"));
768
769 let otlp_error = OtlpError {
770 message: "OTLP connection timeout".to_string(),
771 };
772 assert!(format!("{otlp_error}").contains("OTLP connection timeout"));
773
774 let config_error = BotError::Config("Missing required field".to_string());
775 assert!(format!("{config_error}").contains("Missing required field"));
776}