modio/client/mod.rs
1use std::sync::Arc;
2
3use reqwest::Client;
4
5use crate::auth::{Auth, Credentials, Token};
6use crate::download::{DownloadAction, Downloader};
7use crate::error::Result;
8use crate::games::{GameRef, Games};
9use crate::mods::ModRef;
10use crate::reports::Reports;
11use crate::request::RequestBuilder;
12use crate::routing::Route;
13use crate::types::id::{GameId, ModId};
14use crate::user::Me;
15
16mod builder;
17
18pub use builder::Builder;
19
20const DEFAULT_HOST: &str = "https://api.mod.io/v1";
21const TEST_HOST: &str = "https://api.test.mod.io/v1";
22const DEFAULT_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), '/', env!("CARGO_PKG_VERSION"));
23
24/// Endpoint interface to interacting with the [mod.io](https://mod.io) API.
25#[derive(Clone, Debug)]
26pub struct Modio {
27 pub(crate) inner: Arc<ClientRef>,
28}
29
30#[derive(Debug)]
31pub(crate) struct ClientRef {
32 pub(crate) host: String,
33 pub(crate) client: Client,
34 pub(crate) credentials: Credentials,
35}
36
37impl Modio {
38 /// Constructs a new `Builder` to configure a `Modio` client.
39 ///
40 /// This is the same as `Builder::new(credentials)`.
41 pub fn builder<C: Into<Credentials>>(credentials: C) -> Builder {
42 Builder::new(credentials)
43 }
44
45 /// Create an endpoint to [https://api.mod.io/v1](https://docs.mod.io/restapiref/#mod-io-api-v1).
46 pub fn new<C>(credentials: C) -> Result<Self>
47 where
48 C: Into<Credentials>,
49 {
50 Builder::new(credentials).build()
51 }
52
53 /// Create an endpoint to a different host.
54 pub fn host<H, C>(host: H, credentials: C) -> Result<Self>
55 where
56 H: Into<String>,
57 C: Into<Credentials>,
58 {
59 Builder::new(credentials).host(host).build()
60 }
61
62 /// Return an endpoint with new credentials.
63 #[must_use]
64 pub fn with_credentials<CR>(&self, credentials: CR) -> Self
65 where
66 CR: Into<Credentials>,
67 {
68 Self {
69 inner: Arc::new(ClientRef {
70 host: self.inner.host.clone(),
71 client: self.inner.client.clone(),
72 credentials: credentials.into(),
73 }),
74 }
75 }
76
77 /// Return an endpoint with a new token.
78 #[must_use]
79 pub fn with_token<T>(&self, token: T) -> Self
80 where
81 T: Into<Token>,
82 {
83 Self {
84 inner: Arc::new(ClientRef {
85 host: self.inner.host.clone(),
86 client: self.inner.client.clone(),
87 credentials: Credentials {
88 api_key: self.inner.credentials.api_key.clone(),
89 token: Some(token.into()),
90 },
91 }),
92 }
93 }
94
95 /// Return a reference to an interface for requesting access tokens.
96 pub fn auth(&self) -> Auth {
97 Auth::new(self.clone())
98 }
99
100 /// Return a reference to an interface that provides access to game information.
101 pub fn games(&self) -> Games {
102 Games::new(self.clone())
103 }
104
105 /// Return a reference to a game.
106 pub fn game(&self, game_id: GameId) -> GameRef {
107 GameRef::new(self.clone(), game_id)
108 }
109
110 /// Return a reference to a mod.
111 pub fn mod_(&self, game_id: GameId, mod_id: ModId) -> ModRef {
112 ModRef::new(self.clone(), game_id, mod_id)
113 }
114
115 /// Returns [`Downloader`] for saving to file or retrieving
116 /// the data via [`Stream`].
117 ///
118 /// The download fails with [`modio::download::Error`] as source
119 /// if a primary file, a specific file or a specific version is not found.
120 ///
121 /// [`Downloader`]: crate::download::Downloader
122 /// [`modio::download::Error`]: crate::download::Error
123 /// [`Stream`]: futures_util::Stream
124 ///
125 /// # Example
126 /// ```no_run
127 /// use futures_util::{future, TryStreamExt};
128 /// use modio::download::{DownloadAction, ResolvePolicy};
129 /// use modio::types::id::Id;
130 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
131 /// # let modio = modio::Modio::new("user-or-game-api-key")?;
132 ///
133 /// // Download the primary file of a mod.
134 /// let action = DownloadAction::Primary {
135 /// game_id: Id::new(5),
136 /// mod_id: Id::new(19),
137 /// };
138 /// modio
139 /// .download(action)
140 /// .await?
141 /// .save_to_file("mod.zip")
142 /// .await?;
143 ///
144 /// // Download the specific file of a mod.
145 /// let action = DownloadAction::File {
146 /// game_id: Id::new(5),
147 /// mod_id: Id::new(19),
148 /// file_id: Id::new(101),
149 /// };
150 /// modio
151 /// .download(action)
152 /// .await?
153 /// .save_to_file("mod.zip")
154 /// .await?;
155 ///
156 /// // Download the specific version of a mod.
157 /// // if multiple files are found then the latest file is downloaded.
158 /// // Set policy to `ResolvePolicy::Fail` to return with
159 /// // `modio::download::Error::MultipleFilesFound` as source error.
160 /// let action = DownloadAction::Version {
161 /// game_id: Id::new(5),
162 /// mod_id: Id::new(19),
163 /// version: "0.1".to_string(),
164 /// policy: ResolvePolicy::Latest,
165 /// };
166 /// modio
167 /// .download(action)
168 /// .await?
169 /// .stream()
170 /// .try_for_each(|bytes| {
171 /// println!("Bytes: {:?}", bytes);
172 /// future::ok(())
173 /// })
174 /// .await?;
175 /// # Ok(())
176 /// # }
177 /// ```
178 pub async fn download<A>(&self, action: A) -> Result<Downloader>
179 where
180 DownloadAction: From<A>,
181 {
182 Downloader::new(self.clone(), action.into()).await
183 }
184
185 /// Return a reference to an interface that provides access to resources owned by the user
186 /// associated with the current authentication credentials.
187 pub fn user(&self) -> Me {
188 Me::new(self.clone())
189 }
190
191 /// Return a reference to an interface to report games, mods and users.
192 pub fn reports(&self) -> Reports {
193 Reports::new(self.clone())
194 }
195
196 pub(crate) fn request(&self, route: Route) -> RequestBuilder {
197 RequestBuilder::new(self.clone(), route)
198 }
199}