/ src / csfloat / client.rs
client.rs
  1  use dotenv::dotenv;
  2  use log::debug;
  3  use reqwest::header::{HeaderMap, HeaderValue};
  4  use std::env;
  5  
  6  use crate::csfloat::error::CSFloatError;
  7  use crate::csfloat::endpoints::listings::ListingsHandler;
  8  
  9  pub const API_BASE_URL: &str = "https://csfloat.com/api/v1";
 10  
 11  /// The main CSFloat API client.
 12  pub struct CSFloatClient {
 13      pub(crate) api_key: String,
 14      pub(crate) http_client: reqwest::Client,
 15  }
 16  
 17  impl CSFloatClient {
 18      /// Creates a new CSFloatClient instance.
 19      /// 
 20      /// Reads `CSFLOAT_API_KEY` from environment variables.
 21      pub fn new() -> Result<Self, CSFloatError> {
 22          dotenv().ok();
 23          let api_key = env::var("CSFLOAT_API_KEY")?;
 24  
 25          if api_key.is_empty() {
 26              return Err(CSFloatError::ApiError(
 27                  "API key cannot be empty".to_string(),
 28              ));
 29          }
 30  
 31          debug!(
 32              "Initializing CSFloat client with API key (first 8 chars): {}...",
 33              api_key.chars().take(8).collect::<String>()
 34          );
 35  
 36          Ok(CSFloatClient {
 37              api_key,
 38              http_client: reqwest::Client::builder()
 39                  .user_agent("CSFloat-API-Client/1.0")
 40                  .build()?,
 41          })
 42      }
 43  
 44      /// Creates the required HTTP headers for an API request.
 45      pub(crate) fn create_headers(&self) -> Result<HeaderMap, CSFloatError> {
 46          let mut headers = HeaderMap::new();
 47          headers.insert("Authorization", HeaderValue::from_str(&self.api_key)?);
 48          headers.insert("Content-Type", HeaderValue::from_static("application/json"));
 49          headers.insert("Accept", HeaderValue::from_static("application/json"));
 50          Ok(headers)
 51      }
 52  
 53      /// Makes a GET request to the CSFloat API.
 54      pub(crate) async fn get<T: serde::de::DeserializeOwned>(
 55          &self,
 56          endpoint: &str,
 57      ) -> Result<T, CSFloatError> {
 58          let url = format!("{}{}", API_BASE_URL, endpoint);
 59          debug!("GET request to URL: {}", url);
 60          
 61          let response = self.http_client
 62              .get(&url)
 63              .headers(self.create_headers()?)
 64              .send()
 65              .await?;
 66  
 67          let status = response.status();
 68          debug!("Response status: {}", status);
 69          
 70          if !status.is_success() {
 71              let error_text = response.text().await?;
 72              debug!("Error response: {}", error_text);
 73              return Err(CSFloatError::ApiError(format!(
 74                  "API request failed with status {}: {}",
 75                  status, error_text
 76              )));
 77          }
 78  
 79          // Debug the response body before trying to deserialize it
 80          let text = response.text().await?;
 81          debug!("Response body: {}", text);
 82          
 83          match serde_json::from_str::<T>(&text) {
 84              Ok(parsed) => Ok(parsed),
 85              Err(e) => {
 86                  debug!("Failed to deserialize response: {}", e);
 87                  Err(CSFloatError::SerializationError(e))
 88              }
 89          }
 90      }
 91  
 92      /// Makes a POST request to the CSFloat API.
 93      pub(crate) async fn post<T: serde::de::DeserializeOwned, B: serde::Serialize>(
 94          &self,
 95          endpoint: &str,
 96          body: &B,
 97      ) -> Result<T, CSFloatError> {
 98          let url = format!("{}{}", API_BASE_URL, endpoint);
 99          
100          let response = self.http_client
101              .post(&url)
102              .headers(self.create_headers()?)
103              .json(body)
104              .send()
105              .await?;
106  
107          let status = response.status();
108          
109          if !status.is_success() {
110              let error_text = response.text().await?;
111              return Err(CSFloatError::ApiError(format!(
112                  "API request failed with status {}: {}",
113                  status,
114                  error_text
115              )));
116          }
117  
118          // Debug the response body before trying to deserialize it
119          let text = response.text().await?;
120          debug!("Response body: {}", text);
121          
122          match serde_json::from_str::<T>(&text) {
123              Ok(parsed) => Ok(parsed),
124              Err(e) => {
125                  debug!("Failed to deserialize response: {}", e);
126                  Err(CSFloatError::SerializationError(e))
127              }
128          }
129      }
130  
131      /// Provides access to listings-related API endpoints.
132      pub fn listings(&self) -> ListingsHandler<'_> {
133          ListingsHandler::new(self)
134      }
135  }