1use std::time::Duration;
2
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5
6use crate::client_common::{
7 cbr_endpoint_methods, configure_reqwest_builder, endpoint, normalize_base_url,
8};
9use crate::error::{CbrError, parse_json_body};
10use crate::models::{
11 CategoryNewResponse, DataExResponse, DataNewResponse, DataResponse, Dataset,
12 DatasetDescription, DatasetsExResponse, MeasuresResponse, Publication, YearRange,
13};
14use crate::query::{
15 DataExQuery, DataNewQuery, DataQuery, dataset_id_query, publication_id_query, years_ex_query,
16 years_query,
17};
18use crate::types::{DatasetId, MeasureId, PublicationId};
19
20pub use crate::client_common::DEFAULT_BASE_URL;
21
22const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
23
24macro_rules! impl_async_endpoint_method {
25 (
26 $doc:literal,
27 $name:ident,
28 ($($arg_name:ident : $arg_ty:ty),* $(,)?),
29 $ret:ty,
30 $path:literal,
31 no_query
32 ) => {
33 #[doc = $doc]
34 #[inline]
35 pub async fn $name(&self $(, $arg_name: $arg_ty)*) -> Result<$ret, CbrError> {
36 self.request_json($path).await
37 }
38 };
39 (
40 $doc:literal,
41 $name:ident,
42 ($($arg_name:ident : $arg_ty:ty),* $(,)?),
43 $ret:ty,
44 $path:literal,
45 query($query:expr)
46 ) => {
47 #[doc = $doc]
48 #[inline]
49 pub async fn $name(&self $(, $arg_name: $arg_ty)*) -> Result<$ret, CbrError> {
50 self.request_json_with_query($path, &$query).await
51 }
52 };
53}
54
55#[derive(Debug, Clone)]
57pub struct CbrClientBuilder {
58 pub(crate) base_url: String,
59 pub(crate) timeout: Duration,
60 pub(crate) user_agent: Option<String>,
61 pub(crate) proxy_url: Option<String>,
62 pub(crate) use_system_proxy: bool,
63}
64
65impl CbrClientBuilder {
66 #[must_use]
68 #[inline]
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 #[must_use]
77 #[inline]
78 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
79 self.base_url = normalize_base_url(base_url.into());
80 self
81 }
82
83 #[must_use]
85 #[inline]
86 pub fn timeout(mut self, timeout: Duration) -> Self {
87 self.timeout = timeout;
88 self
89 }
90
91 #[must_use]
93 #[inline]
94 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
95 self.user_agent = Some(user_agent.into());
96 self
97 }
98
99 #[must_use]
105 #[inline]
106 pub fn proxy(mut self, proxy_url: impl Into<String>) -> Self {
107 self.proxy_url = Some(proxy_url.into());
108 self
109 }
110
111 #[must_use]
116 #[inline]
117 pub fn use_system_proxy(mut self, enabled: bool) -> Self {
118 self.use_system_proxy = enabled;
119 self
120 }
121
122 pub fn build(self) -> Result<CbrClient, CbrError> {
124 let builder = configure_reqwest_builder!(
126 reqwest::Client::builder(),
127 timeout = self.timeout,
128 use_system_proxy = self.use_system_proxy,
129 proxy_url = self.proxy_url.as_deref(),
130 user_agent = self.user_agent.as_deref()
131 );
132
133 let http = builder.build().map_err(CbrError::build)?;
134 Ok(CbrClient {
135 base_url: normalize_base_url(&self.base_url),
136 http,
137 })
138 }
139
140 #[cfg(feature = "blocking")]
144 #[inline]
145 pub fn build_blocking(self) -> Result<crate::blocking::BlockingCbrClient, CbrError> {
146 crate::blocking::BlockingCbrClient::from_builder(self)
147 }
148}
149
150impl Default for CbrClientBuilder {
151 fn default() -> Self {
152 Self {
153 base_url: DEFAULT_BASE_URL.to_owned(),
154 timeout: DEFAULT_TIMEOUT,
155 user_agent: None,
156 proxy_url: None,
157 use_system_proxy: false,
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct CbrClient {
165 base_url: String,
166 http: reqwest::Client,
167}
168
169impl CbrClient {
170 #[inline]
172 pub fn new() -> Result<Self, CbrError> {
173 Self::builder().build()
174 }
175
176 #[must_use]
178 #[inline]
179 pub fn builder() -> CbrClientBuilder {
180 CbrClientBuilder::new()
181 }
182
183 #[must_use]
185 #[inline]
186 pub fn base_url(&self) -> &str {
187 &self.base_url
188 }
189
190 #[inline]
194 pub async fn request_json<T>(&self, path: &str) -> Result<T, CbrError>
195 where
196 T: DeserializeOwned,
197 {
198 self.get_json(path).await
199 }
200
201 #[inline]
205 pub async fn request_json_with_query<T, Q>(&self, path: &str, query: &Q) -> Result<T, CbrError>
206 where
207 T: DeserializeOwned,
208 Q: Serialize + ?Sized,
209 {
210 self.get_json_with_query(path, query).await
211 }
212
213 cbr_endpoint_methods!(impl_async_endpoint_method);
214
215 async fn get_json<T>(&self, path: &str) -> Result<T, CbrError>
216 where
217 T: DeserializeOwned,
218 {
219 let response = self
220 .http
221 .get(endpoint(&self.base_url, path))
222 .send()
223 .await
224 .map_err(CbrError::transport)?;
225 let status = response.status();
226 let body = response.bytes().await.map_err(CbrError::transport)?;
227 parse_json_body(status, body.as_ref())
228 }
229
230 async fn get_json_with_query<T, Q>(&self, path: &str, query: &Q) -> Result<T, CbrError>
231 where
232 T: DeserializeOwned,
233 Q: Serialize + ?Sized,
234 {
235 let response = self
236 .http
237 .get(endpoint(&self.base_url, path))
238 .query(query)
239 .send()
240 .await
241 .map_err(CbrError::transport)?;
242 let status = response.status();
243 let body = response.bytes().await.map_err(CbrError::transport)?;
244 parse_json_body(status, body.as_ref())
245 }
246}