1use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum Context7Error {
11 #[error("No valid API key available after {attempts} attempts")]
13 RetriesExhausted {
14 attempts: u32,
16 },
17
18 #[error("All API keys failed due to authentication errors")]
20 NoValidApiKey,
21
22 #[error("Invalid API response: status {status}")]
24 InvalidResponse {
25 status: u16,
27 },
28
29 #[error("API returned error 400: {message}")]
31 ApiReturned400 {
32 message: String,
34 },
35
36 #[error("Library not found: {library_id}")]
38 LibraryNotFound {
39 library_id: String,
41 },
42
43 #[error("")]
46 KeysOperationFailed,
47}
48
49impl Context7Error {
50 #[must_use]
61 pub fn exit_code(&self) -> i32 {
62 match self {
63 Self::RetriesExhausted { .. } => 69,
64 Self::NoValidApiKey => 77,
65 Self::InvalidResponse { .. } => 74,
66 Self::ApiReturned400 { .. } => 65,
67 Self::LibraryNotFound { .. } => 66,
68 Self::KeysOperationFailed => 1,
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_error_no_api_keys_display() {
79 let err = Context7Error::NoValidApiKey;
80 let message = err.to_string();
81 assert!(
82 !message.is_empty(),
83 "NoValidApiKey must have non-empty message"
84 );
85 assert!(
86 message.to_lowercase().contains("key")
87 || message.to_lowercase().contains("api")
88 || message.to_lowercase().contains("auth"),
89 "Message must mention key/api/auth, got: {message}"
90 );
91 }
92
93 #[test]
94 fn test_error_retries_exhausted_contains_attempts_count() {
95 let err = Context7Error::RetriesExhausted { attempts: 3 };
96 let message = err.to_string();
97 assert!(
98 message.contains('3'),
99 "Message must contain number of attempts (3), got: {message}"
100 );
101 }
102
103 #[test]
104 fn test_error_invalid_response_contains_status() {
105 let err = Context7Error::InvalidResponse { status: 500 };
106 let message = err.to_string();
107 assert!(
108 message.contains("500"),
109 "Message must contain status code, got: {message}"
110 );
111 }
112
113 #[test]
114 fn test_error_api_400_contains_error_text() {
115 let err = Context7Error::ApiReturned400 {
116 message: "Invalid parameter".to_string(),
117 };
118 let message = err.to_string();
119 assert!(
120 message.contains("Invalid parameter"),
121 "Message must contain error text, got: {message}"
122 );
123 }
124
125 #[test]
126 fn test_result_alias_propagates_context7_error() {
127 fn fail() -> Result<(), Context7Error> {
128 Err(Context7Error::NoValidApiKey)
129 }
130 let result: Result<(), Context7Error> = fail();
131 assert!(result.is_err(), "Result must be Err");
132 let err = result.unwrap_err();
133 assert!(matches!(err, Context7Error::NoValidApiKey));
134 }
135}