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::types::id::{GameId, UserId};
10use crate::{TargetPlatform, TargetPortal};
11
12use super::{ClientRef, Modio};
13use super::{DEFAULT_AGENT, DEFAULT_HOST, TEST_HOST};
14
15/// A `Builder` can be used to create a `Modio` client with custom configuration.
16#[must_use]
17pub struct Builder {
18    config: Config,
19}
20
21struct Config {
22    host: Option<String>,
23    credentials: Credentials,
24    builder: Option<ClientBuilder>,
25    headers: HeaderMap,
26    proxies: Vec<Proxy>,
27    #[cfg(feature = "__tls")]
28    tls: TlsBackend,
29    error: Option<Error>,
30}
31
32#[cfg(feature = "__tls")]
33enum TlsBackend {
34    #[cfg(feature = "default-tls")]
35    Default,
36    #[cfg(feature = "rustls-tls")]
37    Rustls,
38}
39
40#[cfg(feature = "__tls")]
41#[allow(clippy::derivable_impls)]
42impl Default for TlsBackend {
43    fn default() -> TlsBackend {
44        #[cfg(feature = "default-tls")]
45        {
46            TlsBackend::Default
47        }
48        #[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))]
49        {
50            TlsBackend::Rustls
51        }
52    }
53}
54
55impl Builder {
56    /// Constructs a new `Builder`.
57    ///
58    /// This is the same as `Modio::builder(credentials)`.
59    pub fn new<C: Into<Credentials>>(credentials: C) -> Builder {
60        Builder {
61            config: Config {
62                host: None,
63                credentials: credentials.into(),
64                builder: None,
65                headers: HeaderMap::new(),
66                proxies: Vec::new(),
67                #[cfg(feature = "__tls")]
68                tls: TlsBackend::default(),
69                error: None,
70            },
71        }
72    }
73
74    /// Returns a `Modio` client that uses this `Builder` configuration.
75    pub fn build(self) -> Result<Modio> {
76        let config = self.config;
77
78        if let Some(e) = config.error {
79            return Err(e);
80        }
81
82        let host = config.host.unwrap_or_else(|| DEFAULT_HOST.to_string());
83        let credentials = config.credentials;
84
85        let client = {
86            let mut builder = {
87                let builder = config.builder.unwrap_or_else(Client::builder);
88                #[cfg(feature = "__tls")]
89                match config.tls {
90                    #[cfg(feature = "default-tls")]
91                    TlsBackend::Default => builder.use_native_tls(),
92                    #[cfg(feature = "rustls-tls")]
93                    TlsBackend::Rustls => builder.use_rustls_tls(),
94                }
95
96                #[cfg(not(feature = "__tls"))]
97                builder
98            };
99
100            let mut headers = config.headers;
101            if !headers.contains_key(USER_AGENT) {
102                headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_AGENT));
103            }
104
105            for proxy in config.proxies {
106                builder = builder.proxy(proxy);
107            }
108
109            builder
110                .default_headers(headers)
111                .build()
112                .map_err(error::builder)?
113        };
114
115        Ok(Modio {
116            inner: Arc::new(ClientRef {
117                host,
118                client,
119                credentials,
120            }),
121        })
122    }
123
124    /// Configure the underlying `reqwest` client using `reqwest::ClientBuilder`.
125    pub fn client<F>(mut self, f: F) -> Builder
126    where
127        F: FnOnce(ClientBuilder) -> ClientBuilder,
128    {
129        self.config.builder = Some(f(Client::builder()));
130        self
131    }
132
133    /// Set the mod.io API host to "https://g-{id}.modapi.io/v1".
134    pub fn game_host(mut self, game_id: GameId) -> Self {
135        self.config.host = Some(format!("https://g-{game_id}.modapi.io/v1"));
136        self
137    }
138
139    /// Set the mod.io API host to "https://u-{id}.modapi.io/v1".
140    pub fn user_host(mut self, user_id: UserId) -> Self {
141        self.config.host = Some(format!("https://u-{user_id}.modapi.io/v1"));
142        self
143    }
144
145    /// Set the mod.io api host.
146    ///
147    /// Defaults to `"https://api.mod.io/v1"`
148    pub fn host<S: Into<String>>(mut self, host: S) -> Builder {
149        self.config.host = Some(host.into());
150        self
151    }
152
153    /// Use the mod.io api test host.
154    pub fn use_test(mut self) -> Builder {
155        self.config.host = Some(TEST_HOST.into());
156        self
157    }
158
159    /// Set the user agent used for every request.
160    ///
161    /// Defaults to `"modio/{version}"`
162    pub fn user_agent<V>(mut self, value: V) -> Builder
163    where
164        V: TryInto<HeaderValue>,
165        V::Error: Into<http::Error>,
166    {
167        match value.try_into() {
168            Ok(value) => {
169                self.config.headers.insert(USER_AGENT, value);
170            }
171            Err(e) => {
172                self.config.error = Some(error::builder(e.into()));
173            }
174        }
175        self
176    }
177
178    /// Add a `Proxy` to the list of proxies the client will use.
179    pub fn proxy(mut self, proxy: Proxy) -> Builder {
180        self.config.proxies.push(proxy);
181        self
182    }
183
184    /// Set the target platform.
185    ///
186    /// See the [mod.io docs](https://docs.mod.io/restapiref/#targeting-a-platform) for more information.
187    pub fn target_platform(mut self, platform: TargetPlatform) -> Builder {
188        match HeaderValue::from_str(platform.as_str()) {
189            Ok(value) => {
190                self.config.headers.insert("X-Modio-Platform", value);
191            }
192            Err(e) => {
193                self.config.error = Some(error::builder(e));
194            }
195        }
196        self
197    }
198
199    /// Set the target portal.
200    ///
201    /// See the [mod.io docs](https://docs.mod.io/restapiref/#targeting-a-portal) for more information.
202    pub fn target_portal(mut self, portal: TargetPortal) -> Builder {
203        match HeaderValue::from_str(portal.as_str()) {
204            Ok(value) => {
205                self.config.headers.insert("X-Modio-Portal", value);
206            }
207            Err(e) => {
208                self.config.error = Some(error::builder(e));
209            }
210        }
211        self
212    }
213
214    /// Use native TLS backend.
215    #[cfg(feature = "default-tls")]
216    pub fn use_default_tls(mut self) -> Builder {
217        self.config.tls = TlsBackend::Default;
218        self
219    }
220
221    /// Use rustls TLS backend.
222    #[cfg(feature = "rustls-tls")]
223    pub fn use_rustls_tls(mut self) -> Builder {
224        self.config.tls = TlsBackend::Rustls;
225        self
226    }
227}