Skip to main content

docaroo_rs/
client.rs

1//! Main client for interacting with the Docaroo API
2
3use crate::{
4    error::{DocarooError, Result},
5    models::ErrorResponse,
6    pricing::PricingClient,
7    procedures::ProceduresClient,
8};
9use bon::Builder;
10use reqwest::{Client, Response, StatusCode};
11use std::sync::Arc;
12use url::Url;
13
14/// Configuration for the Docaroo client
15#[derive(Debug, Clone, Builder)]
16pub struct DocarooConfig {
17    /// API key for authentication
18    #[builder(into)]
19    pub api_key: String,
20    
21    /// Base URL for the API (defaults to production)
22    #[builder(into, default = crate::API_BASE_URL.to_string())]
23    pub base_url: String,
24    
25    /// HTTP client to use (defaults to new client)
26    pub http_client: Option<Client>,
27}
28
29/// Main client for interacting with the Docaroo API
30#[derive(Debug, Clone)]
31pub struct DocarooClient {
32    config: Arc<DocarooConfig>,
33    http_client: Client,
34}
35
36impl DocarooClient {
37    /// Create a new Docaroo client with the given API key
38    pub fn new(api_key: impl Into<String>) -> Self {
39        Self::with_config(
40            DocarooConfig::builder()
41                .api_key(api_key)
42                .build()
43        )
44    }
45
46    /// Create a new Docaroo client with custom configuration
47    pub fn with_config(config: DocarooConfig) -> Self {
48        let http_client = config.http_client.clone().unwrap_or_else(|| {
49            Client::builder()
50                .timeout(std::time::Duration::from_secs(30))
51                .build()
52                .expect("Failed to create HTTP client")
53        });
54
55        Self {
56            config: Arc::new(config),
57            http_client,
58        }
59    }
60
61    /// Get the API key
62    pub fn api_key(&self) -> &str {
63        &self.config.api_key
64    }
65
66    /// Get the base URL
67    pub fn base_url(&self) -> &str {
68        &self.config.base_url
69    }
70
71    /// Get the HTTP client
72    pub(crate) fn http_client(&self) -> &Client {
73        &self.http_client
74    }
75
76    /// Build a URL for an API endpoint
77    pub(crate) fn build_url(&self, endpoint: &str) -> Result<Url> {
78        let base = Url::parse(&self.config.base_url)?;
79        let mut url = base.join(endpoint)?;
80        
81        // Add API key as query parameter
82        url.query_pairs_mut()
83            .append_pair("key", &self.config.api_key);
84        
85        Ok(url)
86    }
87
88    /// Handle API response and convert errors
89    pub(crate) async fn handle_response<T>(response: Response) -> Result<T>
90    where
91        T: serde::de::DeserializeOwned,
92    {
93        let status = response.status();
94        
95        if status.is_success() {
96            response
97                .json::<T>()
98                .await
99                .map_err(|e| DocarooError::ParseError(e.to_string()))
100        } else {
101            // Try to parse error response
102            let error_response = response
103                .json::<ErrorResponse>()
104                .await
105                .unwrap_or_else(|_| ErrorResponse {
106                    error: status.as_str().to_string(),
107                    message: format!("HTTP {} error", status.as_u16()),
108                    details: None,
109                    request_id: None,
110                    timestamp: None,
111                });
112
113            // Map status codes to specific errors
114            match status {
115                StatusCode::UNAUTHORIZED => {
116                    Err(DocarooError::AuthenticationFailed(error_response.message))
117                }
118                StatusCode::BAD_REQUEST => {
119                    Err(DocarooError::InvalidRequest(error_response.message))
120                }
121                StatusCode::TOO_MANY_REQUESTS => {
122                    Err(DocarooError::from_error_response(error_response))
123                }
124                _ => Err(DocarooError::from_error_response(error_response)),
125            }
126        }
127    }
128
129    /// Create a pricing client for in-network rates operations
130    pub fn pricing(&self) -> PricingClient {
131        PricingClient::new(self.clone())
132    }
133
134    /// Create a procedures client for likelihood operations
135    pub fn procedures(&self) -> ProceduresClient {
136        ProceduresClient::new(self.clone())
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_client_creation() {
146        let client = DocarooClient::new("test-api-key");
147        assert_eq!(client.api_key(), "test-api-key");
148        assert_eq!(client.base_url(), crate::API_BASE_URL);
149    }
150
151    #[test]
152    fn test_client_with_config() {
153        let config = DocarooConfig::builder()
154            .api_key("custom-key")
155            .base_url("https://custom.api.com")
156            .build();
157        
158        let client = DocarooClient::with_config(config);
159        assert_eq!(client.api_key(), "custom-key");
160        assert_eq!(client.base_url(), "https://custom.api.com");
161    }
162
163    #[test]
164    fn test_build_url() {
165        let client = DocarooClient::new("test-key");
166        let url = client.build_url("/pricing/in-network").unwrap();
167        
168        assert_eq!(url.path(), "/pricing/in-network");
169        assert_eq!(
170            url.query_pairs().find(|(k, _)| k == "key").map(|(_, v)| v.into_owned()),
171            Some("test-key".to_string())
172        );
173    }
174}