cufinder_rust/
client.rs

1use crate::error::{CufinderError, Result};
2use reqwest::Client as ReqwestClient;
3use serde::Serialize;
4use std::time::Duration;
5
6/// Configuration for the CUFinder client
7#[derive(Debug, Clone)]
8pub struct ClientConfig {
9    pub api_key: String,
10    pub base_url: String,
11    pub timeout: Duration,
12    pub max_retries: u32,
13}
14
15impl Default for ClientConfig {
16    fn default() -> Self {
17        Self {
18            api_key: String::new(),
19            base_url: "https://api.cufinder.io/v2".to_string(),
20            timeout: Duration::from_secs(30),
21            max_retries: 3,
22        }
23    }
24}
25
26/// HTTP client for CUFinder API
27#[derive(Debug, Clone)]
28pub struct Client {
29    config: ClientConfig,
30    http_client: ReqwestClient,
31}
32
33impl Client {
34    /// Create a new client with the given configuration
35    pub fn new(config: ClientConfig) -> Result<Self> {
36        let http_client = ReqwestClient::builder()
37            .timeout(config.timeout)
38            .build()
39            .map_err(CufinderError::HttpError)?;
40
41        Ok(Self {
42            config,
43            http_client,
44        })
45    }
46
47    /// Create a new client with just an API key
48    pub fn with_api_key(api_key: String) -> Result<Self> {
49        Self::new(ClientConfig {
50            api_key,
51            ..Default::default()
52        })
53    }
54
55    /// Send a POST request to the API
56    pub async fn post<T>(&self, endpoint: &str, data: &T) -> Result<serde_json::Value>
57    where
58        T: Serialize,
59    {
60        let url = format!("{}{}", self.config.base_url, endpoint);
61        
62        // Convert data to form-encoded format
63        let form_data = serde_urlencoded::to_string(data)
64            .map_err(|e| CufinderError::ValidationError(format!("Failed to encode form data: {}", e)))?;
65        
66        let response = self
67            .http_client
68            .post(&url)
69            .header("x-api-key", &self.config.api_key)
70            .header("Content-Type", "application/x-www-form-urlencoded")
71            .header("User-Agent", "cufinder-rust/1.0.0")
72            .body(form_data)
73            .send()
74            .await
75            .map_err(CufinderError::HttpError)?;
76
77        let status = response.status();
78        
79        if !status.is_success() {
80            let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
81            
82            return match status.as_u16() {
83                401 => Err(CufinderError::AuthenticationError(error_text)),
84                429 => Err(CufinderError::RateLimitError(error_text)),
85                402 => Err(CufinderError::CreditLimitError(error_text)),
86                _ => Err(CufinderError::ApiError {
87                    status: status.as_u16(),
88                    message: error_text,
89                }),
90            };
91        }
92
93        let mut json_response: serde_json::Value = response
94            .json()
95            .await
96            .map_err(CufinderError::HttpError)?;
97
98        // Check if the response has a "data" wrapper and extract it
99        if let Some(data_wrapper) = json_response.get("data") {
100            if let Some(meta_data) = json_response.get("meta_data") {
101                // Create a new object with data content plus meta_data
102                let mut data_obj = data_wrapper.clone();
103                if let serde_json::Value::Object(ref mut map) = data_obj {
104                    map.insert("meta_data".to_string(), meta_data.clone());
105                }
106                json_response = data_obj;
107            } else {
108                json_response = data_wrapper.clone();
109            }
110        }
111
112        Ok(json_response)
113    }
114
115    /// Get the underlying HTTP client for advanced usage
116    pub fn http_client(&self) -> &ReqwestClient {
117        &self.http_client
118    }
119
120    /// Get the client configuration
121    pub fn config(&self) -> &ClientConfig {
122        &self.config
123    }
124}