modio/client/
builder.rs

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/// A `Builder` can be used to create a `Modio` client with custom configuration.
15#[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    /// Constructs a new `Builder`.
56    ///
57    /// This is the same as `Modio::builder(credentials)`.
58    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    /// Returns a `Modio` client that uses this `Builder` configuration.
74    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    /// Configure the underlying `reqwest` client using `reqwest::ClientBuilder`.
124    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    /// Set the mod.io api host.
133    ///
134    /// Defaults to `"https://api.mod.io/v1"`
135    pub fn host<S: Into<String>>(mut self, host: S) -> Builder {
136        self.config.host = Some(host.into());
137        self
138    }
139
140    /// Use the mod.io api test host.
141    pub fn use_test(mut self) -> Builder {
142        self.config.host = Some(TEST_HOST.into());
143        self
144    }
145
146    /// Set the user agent used for every request.
147    ///
148    /// Defaults to `"modio/{version}"`
149    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    /// Add a `Proxy` to the list of proxies the client will use.
166    pub fn proxy(mut self, proxy: Proxy) -> Builder {
167        self.config.proxies.push(proxy);
168        self
169    }
170
171    /// Set the target platform.
172    ///
173    /// See the [mod.io docs](https://docs.mod.io/restapiref/#targeting-a-platform) for more information.
174    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    /// Set the target portal.
187    ///
188    /// See the [mod.io docs](https://docs.mod.io/restapiref/#targeting-a-portal) for more information.
189    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    /// Use native TLS backend.
202    #[cfg(feature = "default-tls")]
203    pub fn use_default_tls(mut self) -> Builder {
204        self.config.tls = TlsBackend::Default;
205        self
206    }
207
208    /// Use rustls TLS backend.
209    #[cfg(feature = "rustls-tls")]
210    pub fn use_rustls_tls(mut self) -> Builder {
211        self.config.tls = TlsBackend::Rustls;
212        self
213    }
214}