rusty_feeder/common/
rest_utils.rs

1//! REST API utility functions for exchange integrations
2
3use reqwest::Client;
4use rusty_common::error::CommonError;
5use serde::de::DeserializeOwned;
6use smartstring::alias::String;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9/// Configuration for REST API connections
10#[derive(Debug, Clone)]
11pub struct RestConfig {
12    /// Base URL for the REST API endpoint
13    pub base_url: String,
14    /// Request timeout in seconds
15    pub timeout_secs: u64,
16    /// Maximum number of retry attempts for failed requests
17    pub max_retries: u32,
18}
19
20impl Default for RestConfig {
21    fn default() -> Self {
22        Self {
23            base_url: String::new(),
24            timeout_secs: 30,
25            max_retries: 3,
26        }
27    }
28}
29
30/// Create a new HTTP client with default settings
31pub fn create_client(timeout_secs: u64) -> Result<Client, CommonError> {
32    Client::builder()
33        .timeout(Duration::from_secs(timeout_secs))
34        .build()
35        .map_err(|e| CommonError::Network(String::from(e.to_string())))
36}
37
38/// Get current timestamp in milliseconds
39#[must_use]
40pub fn current_timestamp_ms() -> u64 {
41    SystemTime::now()
42        .duration_since(UNIX_EPOCH)
43        .expect("Time went backwards")
44        .as_millis() as u64
45}
46
47/// Make a GET request and deserialize the response
48pub async fn get_request<T: DeserializeOwned>(
49    client: &Client,
50    url: &str,
51) -> Result<T, CommonError> {
52    let response = client
53        .get(url)
54        .send()
55        .await
56        .map_err(|e| CommonError::Network(String::from(e.to_string())))?;
57
58    if !response.status().is_success() {
59        let status = response.status();
60        let error_text = response.text().await.unwrap_or_default();
61        let mut error_msg = String::new();
62        use std::fmt::Write;
63        write!(error_msg, "HTTP error: {status} - {error_text}")
64            .expect("write to String should not fail");
65        return Err(CommonError::Api(error_msg));
66    }
67
68    let bytes = response
69        .bytes()
70        .await
71        .map_err(|e| CommonError::Network(String::from(e.to_string())))?;
72    let mut bytes_vec = bytes.to_vec();
73    simd_json::from_slice::<T>(&mut bytes_vec)
74        .map_err(|e| CommonError::Parse(String::from(e.to_string())))
75}
76
77/// Make a POST request with JSON body
78pub async fn post_request<T: DeserializeOwned, B: serde::Serialize>(
79    client: &Client,
80    url: &str,
81    body: &B,
82) -> Result<T, CommonError> {
83    // Serialize body to JSON string with simd-json
84    let json_body =
85        simd_json::to_string(body).map_err(|e| CommonError::Parse(String::from(e.to_string())))?;
86
87    let response = client
88        .post(url)
89        .header("Content-Type", "application/json")
90        .body(json_body)
91        .send()
92        .await
93        .map_err(|e| CommonError::Network(String::from(e.to_string())))?;
94
95    if !response.status().is_success() {
96        let status = response.status();
97        let error_text = response.text().await.unwrap_or_default();
98        let mut error_msg = String::new();
99        use std::fmt::Write;
100        write!(error_msg, "HTTP error: {status} - {error_text}")
101            .expect("write to String should not fail");
102        return Err(CommonError::Api(error_msg));
103    }
104
105    let bytes = response
106        .bytes()
107        .await
108        .map_err(|e| CommonError::Network(String::from(e.to_string())))?;
109    let mut bytes_vec = bytes.to_vec();
110    simd_json::from_slice::<T>(&mut bytes_vec)
111        .map_err(|e| CommonError::Parse(String::from(e.to_string())))
112}
113
114/// Parse a JSON response from text
115pub fn parse_json<T: DeserializeOwned>(text: &str) -> Result<T, CommonError> {
116    rusty_common::json::parse(text)
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_timestamp() {
125        let ts1 = current_timestamp_ms();
126        std::thread::sleep(Duration::from_millis(10));
127        let ts2 = current_timestamp_ms();
128        assert!(ts2 > ts1);
129    }
130
131    #[test]
132    fn test_rest_config_default() {
133        let config = RestConfig::default();
134        assert_eq!(config.timeout_secs, 30);
135        assert_eq!(config.max_retries, 3);
136    }
137}