Skip to main content

paratro_sdk/
client.rs

1use std::sync::Arc;
2
3use serde::{de::DeserializeOwned, Serialize};
4
5use crate::config::Config;
6use crate::error::{Error, ErrorBody};
7use crate::token::TokenManager;
8
9/// The main MPC SDK client.
10pub struct MpcClient {
11    config: Config,
12    token_manager: Arc<TokenManager>,
13    http_client: reqwest::Client,
14}
15
16impl MpcClient {
17    /// Creates a new MPC SDK client.
18    pub fn new(
19        api_key: impl Into<String>,
20        api_secret: impl Into<String>,
21        config: Config,
22    ) -> Result<Self, Error> {
23        let api_key = api_key.into();
24        let api_secret = api_secret.into();
25
26        if api_key.is_empty() {
27            return Err(Error::InvalidConfig("apiKey is required".to_string()));
28        }
29        if api_secret.is_empty() {
30            return Err(Error::InvalidConfig("apiSecret is required".to_string()));
31        }
32
33        let token_manager = Arc::new(TokenManager::new(
34            api_key,
35            api_secret,
36            config.base_url.clone(),
37        ));
38
39        let http_client = reqwest::Client::builder()
40            .timeout(std::time::Duration::from_secs(30))
41            .build()
42            .map_err(Error::Http)?;
43
44        Ok(Self {
45            config,
46            token_manager,
47            http_client,
48        })
49    }
50
51    /// Returns the client configuration.
52    pub fn config(&self) -> &Config {
53        &self.config
54    }
55
56    /// Logs out from the API, invalidating the current token.
57    pub async fn logout(&self) -> Result<(), Error> {
58        self.token_manager.logout().await
59    }
60
61    pub(crate) async fn post<B: Serialize, R: DeserializeOwned>(
62        &self,
63        path: &str,
64        body: &B,
65    ) -> Result<R, Error> {
66        let token = self.token_manager.get_token().await?;
67        let url = format!("{}{}", self.config.base_url, path);
68
69        let resp = self
70            .http_client
71            .post(&url)
72            .header("Content-Type", "application/json")
73            .header("Authorization", format!("Bearer {token}"))
74            .json(body)
75            .send()
76            .await
77            .map_err(Error::Http)?;
78
79        self.handle_response(resp).await
80    }
81
82    pub(crate) async fn get<R: DeserializeOwned>(&self, path: &str) -> Result<R, Error> {
83        let token = self.token_manager.get_token().await?;
84        let url = format!("{}{}", self.config.base_url, path);
85
86        let resp = self
87            .http_client
88            .get(&url)
89            .header("Authorization", format!("Bearer {token}"))
90            .send()
91            .await
92            .map_err(Error::Http)?;
93
94        self.handle_response(resp).await
95    }
96
97    pub(crate) async fn get_with_query<R: DeserializeOwned>(
98        &self,
99        path: &str,
100        params: &[(String, String)],
101    ) -> Result<R, Error> {
102        let token = self.token_manager.get_token().await?;
103        let url = format!("{}{}", self.config.base_url, path);
104
105        let resp = self
106            .http_client
107            .get(&url)
108            .header("Authorization", format!("Bearer {token}"))
109            .query(params)
110            .send()
111            .await
112            .map_err(Error::Http)?;
113
114        self.handle_response(resp).await
115    }
116
117    async fn handle_response<R: DeserializeOwned>(
118        &self,
119        resp: reqwest::Response,
120    ) -> Result<R, Error> {
121        let status = resp.status().as_u16();
122
123        if status >= 400 {
124            let body: ErrorBody = resp.json().await.unwrap_or(ErrorBody {
125                code: "unknown".to_string(),
126                error_type: "unknown".to_string(),
127                message: "failed to decode error response".to_string(),
128            });
129            return Err(Error::Api { status, body });
130        }
131
132        resp.json().await.map_err(Error::Http)
133    }
134}