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
9pub struct MpcClient {
11 config: Config,
12 token_manager: Arc<TokenManager>,
13 http_client: reqwest::Client,
14}
15
16impl MpcClient {
17 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 pub fn config(&self) -> &Config {
53 &self.config
54 }
55
56 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}