use crate::models::ErrorResponse;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, DocarooError>;
#[derive(Error, Debug)]
pub enum DocarooError {
#[error("HTTP request failed: {0}")]
RequestFailed(#[from] reqwest::Error),
#[error("API error: {message} (code: {code})")]
ApiError {
code: String,
message: String,
request_id: Option<String>,
},
#[error("Invalid request: {0}")]
InvalidRequest(String),
#[error("Rate limit exceeded. Retry after {retry_after} seconds")]
RateLimitExceeded {
retry_after: u64,
},
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Failed to parse response: {0}")]
ParseError(String),
#[error("Invalid URL: {0}")]
UrlError(#[from] url::ParseError),
}
impl DocarooError {
pub fn from_error_response(response: ErrorResponse) -> Self {
match response.error.as_str() {
"rate_limit_exceeded" => {
let retry_after = response
.details
.as_ref()
.and_then(|d| d.get("retryAfter"))
.and_then(|v| v.as_u64())
.unwrap_or(60);
Self::RateLimitExceeded { retry_after }
}
"unauthorized" => Self::AuthenticationFailed(response.message),
_ => Self::ApiError {
code: response.error,
message: response.message,
request_id: response.request_id,
},
}
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
Self::RequestFailed(_) | Self::RateLimitExceeded { .. }
)
}
pub fn request_id(&self) -> Option<&str> {
match self {
Self::ApiError { request_id, .. } => request_id.as_deref(),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
#[test]
fn test_error_from_response() {
let error_response = ErrorResponse {
error: "bad_request".to_string(),
message: "Invalid NPI format".to_string(),
details: None,
request_id: Some("req_123".to_string()),
timestamp: Some(Utc::now()),
};
let error = DocarooError::from_error_response(error_response);
match error {
DocarooError::ApiError {
code,
message,
request_id,
} => {
assert_eq!(code, "bad_request");
assert_eq!(message, "Invalid NPI format");
assert_eq!(request_id, Some("req_123".to_string()));
}
_ => panic!("Expected ApiError"),
}
}
#[test]
fn test_rate_limit_error() {
let error_response = ErrorResponse {
error: "rate_limit_exceeded".to_string(),
message: "Too many requests".to_string(),
details: Some(serde_json::json!({ "retryAfter": 120 })),
request_id: None,
timestamp: None,
};
let error = DocarooError::from_error_response(error_response);
match error {
DocarooError::RateLimitExceeded { retry_after } => {
assert_eq!(retry_after, 120);
}
_ => panic!("Expected RateLimitExceeded"),
}
}
#[test]
fn test_is_retryable() {
let rate_limit_error = DocarooError::RateLimitExceeded { retry_after: 60 };
assert!(rate_limit_error.is_retryable());
let api_error = DocarooError::ApiError {
code: "bad_request".to_string(),
message: "Invalid request".to_string(),
request_id: None,
};
assert!(!api_error.is_retryable());
}
}