modio/client/
builder.rs

1use http::header::{HeaderMap, HeaderValue, USER_AGENT};
2use http::uri::Authority;
3
4use crate::error::{self, Error};
5use crate::types::id::{GameId, UserId};
6use crate::types::{TargetPlatform, TargetPortal};
7
8use super::host::Host;
9use super::{Client, HDR_X_MODIO_PLATFORM, HDR_X_MODIO_PORTAL};
10
11use super::service::Svc;
12
13/// A builder for [`Client`].
14pub struct Builder {
15    host: Option<Host>,
16    api_key: Box<str>,
17    token: Option<Box<str>>,
18    headers: HeaderMap,
19    error: Option<Error>,
20}
21
22impl Builder {
23    /// Create a new builder with an API key.
24    pub fn new(api_key: String) -> Self {
25        Self {
26            host: None,
27            api_key: api_key.into_boxed_str(),
28            token: None,
29            headers: HeaderMap::new(),
30            error: None,
31        }
32    }
33
34    /// Build the [`Client`].
35    pub fn build(self) -> Result<Client, Error> {
36        if let Some(e) = self.error {
37            return Err(e);
38        }
39
40        let http = Svc::new();
41
42        let host = self.host.unwrap_or_default();
43
44        Ok(Client {
45            http,
46            host,
47            api_key: self.api_key,
48            token: self.token,
49            headers: self.headers,
50        })
51    }
52
53    /// Set the token to use for HTTP requests.
54    pub fn token(mut self, token: String) -> Self {
55        self.token = Some(create_token(token));
56        self
57    }
58
59    /// Use the default mod.io API host (`"api.mod.io"`).
60    pub fn use_default_env(mut self) -> Self {
61        self.host = Some(Host::Default);
62        self
63    }
64
65    /// Use the mod.io API test host (`"api.test.mod.io"`).
66    pub fn use_test_env(mut self) -> Self {
67        self.host = Some(Host::Test);
68        self
69    }
70
71    /// Set the mod.io API host to "g-{id}.modapi.io".
72    pub fn game_host(mut self, game_id: GameId) -> Self {
73        self.host = Some(Host::Game(game_id));
74        self
75    }
76
77    /// Set the mod.io API host to "u-{id}.modapi.io".
78    pub fn user_host(mut self, user_id: UserId) -> Self {
79        self.host = Some(Host::User(user_id));
80        self
81    }
82
83    /// Use the `GameId` from the request endpoint dynamically as game host, or fallback to the
84    /// default host.
85    pub fn dynamic_game_host(mut self) -> Self {
86        self.host = Some(Host::Dynamic);
87        self
88    }
89
90    /// Use the `GameId` from the request endpoint dynamically as game host, or fallback to the
91    /// custom host.
92    pub fn dynamic_game_host_with_custom<V>(mut self, host: V) -> Self
93    where
94        V: TryInto<Authority>,
95        V::Error: Into<http::Error>,
96    {
97        match host.try_into() {
98            Ok(host) => {
99                self.host = Some(Host::DynamicWithCustom(host));
100            }
101            Err(err) => {
102                self.error = Some(error::builder(err.into()));
103            }
104        }
105        self
106    }
107
108    /// Set the mod.io API host.
109    ///
110    /// Defaults to `"api.mod.io"` if not set.
111    pub fn host<V>(mut self, host: V) -> Self
112    where
113        V: TryInto<Authority>,
114        V::Error: Into<http::Error>,
115    {
116        match host.try_into() {
117            Ok(host) => {
118                self.host = Some(Host::Custom(host));
119            }
120            Err(err) => {
121                self.error = Some(error::builder(err.into()));
122            }
123        }
124        self
125    }
126
127    /// Set the user agent used for every request.
128    pub fn user_agent<V>(mut self, value: V) -> Self
129    where
130        V: TryInto<HeaderValue>,
131        V::Error: Into<http::Error>,
132    {
133        match value.try_into() {
134            Ok(value) => {
135                self.headers.insert(USER_AGENT, value);
136            }
137            Err(err) => {
138                self.error = Some(error::builder(err.into()));
139            }
140        }
141        self
142    }
143
144    /// Set the target platform.
145    ///
146    /// See the [mod.io docs](https://docs.mod.io/restapiref/#targeting-a-platform) for more information.
147    pub fn target_platform(mut self, platform: TargetPlatform) -> Self {
148        match HeaderValue::from_str(platform.as_str()) {
149            Ok(value) => {
150                self.headers.insert(HDR_X_MODIO_PLATFORM, value);
151            }
152            Err(err) => {
153                self.error = Some(error::builder(err));
154            }
155        }
156        self
157    }
158
159    /// Set the target portal.
160    ///
161    /// See the [mod.io docs](https://docs.mod.io/restapiref/#targeting-a-portal) for more information.
162    pub fn target_portal(mut self, portal: TargetPortal) -> Self {
163        match HeaderValue::from_str(portal.as_str()) {
164            Ok(value) => {
165                self.headers.insert(HDR_X_MODIO_PORTAL, value);
166            }
167            Err(err) => {
168                self.error = Some(error::builder(err));
169            }
170        }
171        self
172    }
173}
174
175pub(super) fn create_token(mut token: String) -> Box<str> {
176    if !token.starts_with("Bearer ") {
177        token.insert_str(0, "Bearer ");
178    }
179    token.into_boxed_str()
180}
181
182#[cfg(test)]
183mod tests {
184    use super::create_token;
185
186    #[test]
187    fn test_create_token() {
188        assert_eq!("Bearer token", &*create_token("token".to_owned()));
189        assert_eq!("Bearer token", &*create_token("Bearer token".to_owned()));
190    }
191}