1use std::sync::Arc;
2
3use http::header::USER_AGENT;
4use http::header::{HeaderMap, HeaderValue};
5use reqwest::{Client, ClientBuilder, Proxy};
6
7use crate::auth::Credentials;
8use crate::error::{self, Error, Result};
9use crate::{TargetPlatform, TargetPortal};
10
11use super::{ClientRef, Modio};
12use super::{DEFAULT_AGENT, DEFAULT_HOST, TEST_HOST};
13
14#[must_use]
16pub struct Builder {
17 config: Config,
18}
19
20struct Config {
21 host: Option<String>,
22 credentials: Credentials,
23 builder: Option<ClientBuilder>,
24 headers: HeaderMap,
25 proxies: Vec<Proxy>,
26 #[cfg(feature = "__tls")]
27 tls: TlsBackend,
28 error: Option<Error>,
29}
30
31#[cfg(feature = "__tls")]
32enum TlsBackend {
33 #[cfg(feature = "default-tls")]
34 Default,
35 #[cfg(feature = "rustls-tls")]
36 Rustls,
37}
38
39#[cfg(feature = "__tls")]
40#[allow(clippy::derivable_impls)]
41impl Default for TlsBackend {
42 fn default() -> TlsBackend {
43 #[cfg(feature = "default-tls")]
44 {
45 TlsBackend::Default
46 }
47 #[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))]
48 {
49 TlsBackend::Rustls
50 }
51 }
52}
53
54impl Builder {
55 pub fn new<C: Into<Credentials>>(credentials: C) -> Builder {
59 Builder {
60 config: Config {
61 host: None,
62 credentials: credentials.into(),
63 builder: None,
64 headers: HeaderMap::new(),
65 proxies: Vec::new(),
66 #[cfg(feature = "__tls")]
67 tls: TlsBackend::default(),
68 error: None,
69 },
70 }
71 }
72
73 pub fn build(self) -> Result<Modio> {
75 let config = self.config;
76
77 if let Some(e) = config.error {
78 return Err(e);
79 }
80
81 let host = config.host.unwrap_or_else(|| DEFAULT_HOST.to_string());
82 let credentials = config.credentials;
83
84 let client = {
85 let mut builder = {
86 let builder = config.builder.unwrap_or_else(Client::builder);
87 #[cfg(feature = "__tls")]
88 match config.tls {
89 #[cfg(feature = "default-tls")]
90 TlsBackend::Default => builder.use_native_tls(),
91 #[cfg(feature = "rustls-tls")]
92 TlsBackend::Rustls => builder.use_rustls_tls(),
93 }
94
95 #[cfg(not(feature = "__tls"))]
96 builder
97 };
98
99 let mut headers = config.headers;
100 if !headers.contains_key(USER_AGENT) {
101 headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_AGENT));
102 }
103
104 for proxy in config.proxies {
105 builder = builder.proxy(proxy);
106 }
107
108 builder
109 .default_headers(headers)
110 .build()
111 .map_err(error::builder)?
112 };
113
114 Ok(Modio {
115 inner: Arc::new(ClientRef {
116 host,
117 client,
118 credentials,
119 }),
120 })
121 }
122
123 pub fn client<F>(mut self, f: F) -> Builder
125 where
126 F: FnOnce(ClientBuilder) -> ClientBuilder,
127 {
128 self.config.builder = Some(f(Client::builder()));
129 self
130 }
131
132 pub fn host<S: Into<String>>(mut self, host: S) -> Builder {
136 self.config.host = Some(host.into());
137 self
138 }
139
140 pub fn use_test(mut self) -> Builder {
142 self.config.host = Some(TEST_HOST.into());
143 self
144 }
145
146 pub fn user_agent<V>(mut self, value: V) -> Builder
150 where
151 V: TryInto<HeaderValue>,
152 V::Error: Into<http::Error>,
153 {
154 match value.try_into() {
155 Ok(value) => {
156 self.config.headers.insert(USER_AGENT, value);
157 }
158 Err(e) => {
159 self.config.error = Some(error::builder(e.into()));
160 }
161 }
162 self
163 }
164
165 pub fn proxy(mut self, proxy: Proxy) -> Builder {
167 self.config.proxies.push(proxy);
168 self
169 }
170
171 pub fn target_platform(mut self, platform: TargetPlatform) -> Builder {
175 match HeaderValue::from_str(platform.as_str()) {
176 Ok(value) => {
177 self.config.headers.insert("X-Modio-Platform", value);
178 }
179 Err(e) => {
180 self.config.error = Some(error::builder(e));
181 }
182 }
183 self
184 }
185
186 pub fn target_portal(mut self, portal: TargetPortal) -> Builder {
190 match HeaderValue::from_str(portal.as_str()) {
191 Ok(value) => {
192 self.config.headers.insert("X-Modio-Portal", value);
193 }
194 Err(e) => {
195 self.config.error = Some(error::builder(e));
196 }
197 }
198 self
199 }
200
201 #[cfg(feature = "default-tls")]
203 pub fn use_default_tls(mut self) -> Builder {
204 self.config.tls = TlsBackend::Default;
205 self
206 }
207
208 #[cfg(feature = "rustls-tls")]
210 pub fn use_rustls_tls(mut self) -> Builder {
211 self.config.tls = TlsBackend::Rustls;
212 self
213 }
214}