1use crate::blocking::BlockingClient;
2use graph_core::identity::{ClientApplication, ForceTokenRefresh};
3use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, USER_AGENT};
4use reqwest::redirect::Policy;
5use reqwest::tls::Version;
6use reqwest::Proxy;
7use reqwest::{Request, Response};
8use std::env::VarError;
9use std::ffi::OsStr;
10use std::fmt::{Debug, Formatter};
11use std::time::Duration;
12use tower::limit::ConcurrencyLimitLayer;
13use tower::retry::RetryLayer;
14use tower::util::BoxCloneService;
15use tower::ServiceExt;
16
17fn user_agent_header_from_env() -> Option<HeaderValue> {
18 let header = std::option_env!("GRAPH_CLIENT_USER_AGENT")?;
19 HeaderValue::from_str(header).ok()
20}
21
22#[derive(Default, Clone)]
23struct ServiceLayersConfiguration {
24 concurrency_limit: Option<usize>,
25 retry: Option<usize>,
26 wait_for_retry_after_headers: Option<()>,
27}
28
29#[derive(Clone)]
30struct ClientConfiguration {
31 client_application: Option<Box<dyn ClientApplication>>,
32 headers: HeaderMap,
33 referer: bool,
34 timeout: Option<Duration>,
35 connect_timeout: Option<Duration>,
36 connection_verbose: bool,
37 https_only: bool,
38 min_tls_version: Version,
41 service_layers_configuration: ServiceLayersConfiguration,
42 proxy: Option<Proxy>,
43}
44
45impl ClientConfiguration {
46 pub fn new() -> ClientConfiguration {
47 let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
48 headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
49
50 if let Some(user_agent) = user_agent_header_from_env() {
51 headers.insert(USER_AGENT, user_agent);
52 }
53
54 ClientConfiguration {
55 client_application: None,
56 headers,
57 referer: true,
58 timeout: None,
59 connect_timeout: None,
60 connection_verbose: false,
61 https_only: true,
62 min_tls_version: Version::TLS_1_2,
63 service_layers_configuration: ServiceLayersConfiguration::default(),
64 proxy: None,
65 }
66 }
67}
68
69impl PartialEq for ClientConfiguration {
70 fn eq(&self, other: &Self) -> bool {
71 self.headers == other.headers
72 && self.referer == other.referer
73 && self.connect_timeout == other.connect_timeout
74 && self.connection_verbose == other.connection_verbose
75 && self.https_only == other.https_only
76 && self.min_tls_version == other.min_tls_version
77 }
78}
79
80impl Debug for ClientConfiguration {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("ClientConfiguration")
83 .field("headers", &self.headers)
84 .field("referer", &self.referer)
85 .field("timeout", &self.timeout)
86 .field("connect_timeout", &self.connect_timeout)
87 .field("https_only", &self.https_only)
88 .field("min_tls_version", &self.min_tls_version)
89 .field("proxy", &self.proxy)
90 .finish()
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
95pub struct GraphClientConfiguration {
96 config: ClientConfiguration,
97}
98
99impl GraphClientConfiguration {
100 pub fn new() -> GraphClientConfiguration {
101 GraphClientConfiguration {
102 config: ClientConfiguration::new(),
103 }
104 }
105
106 pub fn access_token<AT: ToString>(mut self, access_token: AT) -> GraphClientConfiguration {
107 self.config.client_application = Some(Box::new(access_token.to_string()));
108 self
109 }
110
111 pub fn client_application<CA: ClientApplication + 'static>(mut self, client_app: CA) -> Self {
112 self.config.client_application = Some(Box::new(client_app));
113 self
114 }
115
116 pub fn default_headers(mut self, headers: HeaderMap) -> GraphClientConfiguration {
117 for (key, value) in headers.iter() {
118 self.config.headers.insert(key, value.clone());
119 }
120 self
121 }
122
123 pub fn referer(mut self, enable: bool) -> GraphClientConfiguration {
127 self.config.referer = enable;
128 self
129 }
130
131 pub fn timeout(mut self, timeout: Duration) -> GraphClientConfiguration {
138 self.config.timeout = Some(timeout);
139 self
140 }
141
142 pub fn connect_timeout(mut self, timeout: Duration) -> GraphClientConfiguration {
151 self.config.connect_timeout = Some(timeout);
152 self
153 }
154
155 pub fn connection_verbose(mut self, verbose: bool) -> GraphClientConfiguration {
162 self.config.connection_verbose = verbose;
163 self
164 }
165
166 pub fn user_agent(mut self, value: HeaderValue) -> GraphClientConfiguration {
167 self.config.headers.insert(USER_AGENT, value);
168 self
169 }
170
171 pub fn min_tls_version(mut self, version: Version) -> GraphClientConfiguration {
174 self.config.min_tls_version = version;
175 self
176 }
177
178 pub fn proxy(mut self, proxy: Proxy) -> GraphClientConfiguration {
182 self.config.proxy = Some(proxy);
183 self
184 }
185
186 #[cfg(feature = "test-util")]
187 pub fn https_only(mut self, https_only: bool) -> GraphClientConfiguration {
188 self.config.https_only = https_only;
189 self
190 }
191
192 pub fn retry(mut self, retry: Option<usize>) -> GraphClientConfiguration {
200 self.config.service_layers_configuration.retry = retry;
201 self
202 }
203
204 pub fn wait_for_retry_after_headers(mut self, retry: bool) -> GraphClientConfiguration {
218 self.config
219 .service_layers_configuration
220 .wait_for_retry_after_headers = match retry {
221 true => Some(()),
222 false => None,
223 };
224 self
225 }
226
227 pub fn concurrency_limit(
234 mut self,
235 concurrency_limit: Option<usize>,
236 ) -> GraphClientConfiguration {
237 self.config.service_layers_configuration.concurrency_limit = concurrency_limit;
238 self
239 }
240
241 pub(crate) fn build_tower_service(
242 &self,
243 client: &reqwest::Client,
244 ) -> BoxCloneService<Request, Response, Box<dyn std::error::Error + Send + Sync>> {
245 tower::ServiceBuilder::new()
246 .option_layer(
247 self.config
248 .service_layers_configuration
249 .retry
250 .map(|num| RetryLayer::new(crate::tower_services::Attempts(num))),
251 )
252 .option_layer(
253 self.config
254 .service_layers_configuration
255 .wait_for_retry_after_headers
256 .map(|_| RetryLayer::new(crate::tower_services::WaitFor())),
257 )
258 .option_layer(
259 self.config
260 .service_layers_configuration
261 .concurrency_limit
262 .map(ConcurrencyLimitLayer::new),
263 )
264 .service(client.clone())
265 .boxed_clone()
266 }
267
268 fn build_http_client(&self) -> reqwest::Client {
269 let headers = self.config.headers.clone();
270 let mut builder = reqwest::ClientBuilder::new()
271 .referer(self.config.referer)
272 .connection_verbose(self.config.connection_verbose)
273 .https_only(self.config.https_only)
274 .min_tls_version(self.config.min_tls_version)
275 .redirect(Policy::limited(2))
276 .default_headers(headers);
277
278 if let Some(timeout) = self.config.timeout {
279 builder = builder.timeout(timeout);
280 }
281
282 if let Some(connect_timeout) = self.config.connect_timeout {
283 builder = builder.connect_timeout(connect_timeout);
284 }
285
286 if let Some(proxy) = &self.config.proxy {
287 builder = builder.proxy(proxy.clone());
288 }
289
290 builder.build().unwrap()
291 }
292
293 fn build_blocking_http_client(&self) -> reqwest::blocking::Client {
294 let headers = self.config.headers.clone();
295 let mut builder = reqwest::blocking::ClientBuilder::new()
296 .referer(self.config.referer)
297 .connection_verbose(self.config.connection_verbose)
298 .https_only(self.config.https_only)
299 .min_tls_version(self.config.min_tls_version)
300 .redirect(Policy::limited(2))
301 .default_headers(headers);
302
303 if let Some(timeout) = self.config.timeout {
304 builder = builder.timeout(timeout);
305 }
306
307 if let Some(connect_timeout) = self.config.connect_timeout {
308 builder = builder.connect_timeout(connect_timeout);
309 }
310
311 if let Some(proxy) = &self.config.proxy {
312 builder = builder.proxy(proxy.clone());
313 }
314
315 builder.build().unwrap()
316 }
317
318 pub(crate) fn build(self) -> Client {
319 let config = self.clone();
320 let headers = self.config.headers.clone();
321 let client = self.build_http_client();
322
323 if let Some(client_application) = self.config.client_application {
324 Client {
325 client_application,
326 inner: client,
327 headers,
328 builder: config,
329 }
330 } else {
331 Client {
332 client_application: Box::<String>::default(),
333 inner: client,
334 headers,
335 builder: config,
336 }
337 }
338 }
339
340 pub(crate) fn build_blocking(self) -> BlockingClient {
341 let headers = self.config.headers.clone();
342 let client = self.build_blocking_http_client();
343
344 if let Some(client_application) = self.config.client_application {
345 BlockingClient {
346 client_application,
347 inner: client,
348 headers,
349 }
350 } else {
351 BlockingClient {
352 client_application: Box::<String>::default(),
353 inner: client,
354 headers,
355 }
356 }
357 }
358
359 pub(crate) fn build_minimal_async_client(self) -> MinimalAsyncClient {
360 let config = self.clone();
361 let client = self.build_http_client();
362 let service = self.build_tower_service(&client);
363 MinimalAsyncClient {
364 inner: client,
365 builder: config,
366 service,
367 }
368 }
369
370 pub(crate) fn build_minimal_blocking_client(self) -> MinimalBlockingClient {
371 let config = self.clone();
372 let client = self.build_blocking();
373 MinimalBlockingClient {
374 inner: client.inner,
375 builder: config,
376 }
377 }
378}
379
380impl Default for GraphClientConfiguration {
381 fn default() -> Self {
382 GraphClientConfiguration::new()
383 }
384}
385
386#[derive(Clone)]
387pub struct Client {
388 pub(crate) client_application: Box<dyn ClientApplication>,
389 pub(crate) inner: reqwest::Client,
390 pub(crate) headers: HeaderMap,
391 pub(crate) builder: GraphClientConfiguration,
392}
393
394impl Client {
395 pub fn new<CA: ClientApplication + 'static>(client_app: CA) -> Self {
396 GraphClientConfiguration::new()
397 .client_application(client_app)
398 .build()
399 }
400
401 pub fn from_access_token<T: AsRef<str>>(access_token: T) -> Self {
402 GraphClientConfiguration::new()
403 .access_token(access_token.as_ref())
404 .build()
405 }
406
407 pub fn new_env<K: AsRef<OsStr>>(env_var: K) -> Result<Client, VarError> {
410 Ok(GraphClientConfiguration::new()
411 .access_token(std::env::var(env_var)?)
412 .build())
413 }
414
415 pub fn builder() -> GraphClientConfiguration {
416 GraphClientConfiguration::new()
417 }
418
419 pub fn headers(&self) -> &HeaderMap {
420 &self.headers
421 }
422
423 pub fn with_force_token_refresh(&mut self, force_token_refresh: ForceTokenRefresh) {
424 self.client_application
425 .with_force_token_refresh(force_token_refresh);
426 }
427}
428
429impl Default for Client {
430 fn default() -> Self {
431 GraphClientConfiguration::new().build()
432 }
433}
434
435impl Debug for Client {
436 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
437 f.debug_struct("Client")
438 .field("inner", &self.inner)
439 .field("headers", &self.headers)
440 .field("builder", &self.builder)
441 .finish()
442 }
443}
444
445impl From<GraphClientConfiguration> for Client {
446 fn from(value: GraphClientConfiguration) -> Self {
447 value.build()
448 }
449}
450
451#[derive(Clone)]
452pub struct MinimalAsyncClient {
453 pub inner: reqwest::Client,
454 pub builder: GraphClientConfiguration,
455 pub service: BoxCloneService<Request, Response, Box<dyn std::error::Error + Send + Sync>>,
456}
457
458impl From<GraphClientConfiguration> for MinimalAsyncClient {
463 fn from(value: GraphClientConfiguration) -> Self {
464 value.build_minimal_async_client()
465 }
466}
467
468impl Default for MinimalAsyncClient {
469 fn default() -> Self {
470 GraphClientConfiguration::new().build_minimal_async_client()
471 }
472}
473
474#[derive(Clone)]
475pub struct MinimalBlockingClient {
476 pub inner: reqwest::blocking::Client,
477 pub builder: GraphClientConfiguration,
478}
479
480impl From<GraphClientConfiguration> for MinimalBlockingClient {
481 fn from(value: GraphClientConfiguration) -> Self {
482 value.build_minimal_blocking_client()
483 }
484}
485
486impl Default for MinimalBlockingClient {
487 fn default() -> Self {
488 GraphClientConfiguration::new().build_minimal_blocking_client()
489 }
490}
491
492#[cfg(test)]
493mod test {
494 use super::*;
495
496 #[test]
497 fn compile_time_user_agent_header() {
498 let client = GraphClientConfiguration::new()
499 .access_token("access_token")
500 .build();
501
502 assert!(client.builder.config.headers.contains_key(USER_AGENT));
503 }
504
505 #[test]
506 fn update_user_agent_header() {
507 let client = GraphClientConfiguration::new()
508 .access_token("access_token")
509 .user_agent(HeaderValue::from_static("user_agent"))
510 .build();
511
512 assert!(client.builder.config.headers.contains_key(USER_AGENT));
513 let user_agent_header = client.builder.config.headers.get(USER_AGENT).unwrap();
514 assert_eq!("user_agent", user_agent_header.to_str().unwrap());
515 }
516}