1use crate::error::{NubisError, Result};
2use crate::types::NubisConfig;
3use reqwest::{Client, Response};
4use std::time::Duration;
5
6pub struct NubisClient {
8 client: Client,
9 base_url: String,
10}
11
12impl NubisClient {
13 pub fn new(api_key: String) -> Self {
15 Self::with_config(NubisConfig {
16 api_key,
17 ..Default::default()
18 })
19 }
20
21 pub fn with_config(config: NubisConfig) -> Self {
23 let client_builder = Client::builder()
24 .timeout(config.timeout.unwrap_or(Duration::from_secs(30)))
25 .default_headers({
26 let mut headers = reqwest::header::HeaderMap::new();
27 headers.insert(
28 reqwest::header::CONTENT_TYPE,
29 "application/json".parse().unwrap(),
30 );
31 headers.insert(
32 reqwest::header::AUTHORIZATION,
33 format!("Bearer {}", config.api_key).parse().unwrap(),
34 );
35 headers
36 });
37
38 let client = client_builder.build().expect("Failed to create HTTP client");
39
40 Self {
41 client,
42 base_url: config.base_url,
43 }
44 }
45
46 pub(crate) async fn get<T>(&self, path: &str) -> Result<T>
48 where
49 T: serde::de::DeserializeOwned,
50 {
51 let url = format!("{}{}", self.base_url, path);
52 let response = self.client.get(&url).send().await?;
53 self.handle_response(response).await
54 }
55
56 pub(crate) async fn get_with_params<T>(&self, path: &str, params: &[(&str, &str)]) -> Result<T>
58 where
59 T: serde::de::DeserializeOwned,
60 {
61 let url = format!("{}{}", self.base_url, path);
62 let mut request = self.client.get(&url);
63 for (key, value) in params {
64 request = request.query(&[(key, value)]);
65 }
66 let response = request.send().await?;
67 self.handle_response(response).await
68 }
69
70 pub(crate) async fn post<T, B>(&self, path: &str, body: &B) -> Result<T>
72 where
73 T: serde::de::DeserializeOwned,
74 B: serde::Serialize,
75 {
76 let url = format!("{}{}", self.base_url, path);
77 let response = self.client.post(&url).json(body).send().await?;
78 self.handle_response(response).await
79 }
80
81 pub(crate) async fn put<T, B>(&self, path: &str, body: &B) -> Result<T>
83 where
84 T: serde::de::DeserializeOwned,
85 B: serde::Serialize,
86 {
87 let url = format!("{}{}", self.base_url, path);
88 let response = self.client.put(&url).json(body).send().await?;
89 self.handle_response(response).await
90 }
91
92 pub(crate) async fn delete<T>(&self, path: &str) -> Result<T>
94 where
95 T: serde::de::DeserializeOwned,
96 {
97 let url = format!("{}{}", self.base_url, path);
98 let response = self.client.delete(&url).send().await?;
99 self.handle_response(response).await
100 }
101
102 async fn handle_response<T>(&self, response: Response) -> Result<T>
104 where
105 T: serde::de::DeserializeOwned,
106 {
107 let status = response.status();
108
109 if status.is_success() {
110 let text = response.text().await?;
111 if text.is_empty() {
112 serde_json::from_str("{}").map_err(NubisError::Serialization)
114 } else {
115 serde_json::from_str(&text).map_err(NubisError::Serialization)
116 }
117 } else {
118 let error_text = response.text().await.unwrap_or_default();
119 let error_message = if let Ok(api_error) = serde_json::from_str::<crate::types::ApiErrorResponse>(&error_text) {
120 api_error.error.message
121 } else {
122 format!("HTTP {}: {}", status, error_text)
123 };
124
125 Err(NubisError::Api {
126 status: status.as_u16(),
127 message: error_message,
128 })
129 }
130 }
131}