1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use std::sync::Arc;

use reqwest::Client;

use crate::auth::{Auth, Credentials, Token};
use crate::download::{DownloadAction, Downloader};
use crate::error::Result;
use crate::games::{GameRef, Games};
use crate::mods::ModRef;
use crate::reports::Reports;
use crate::request::RequestBuilder;
use crate::routing::Route;
use crate::types::id::{GameId, ModId};
use crate::user::Me;

mod builder;

pub use builder::Builder;

const DEFAULT_HOST: &str = "https://api.mod.io/v1";
const TEST_HOST: &str = "https://api.test.mod.io/v1";
const DEFAULT_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), '/', env!("CARGO_PKG_VERSION"));

/// Endpoint interface to interacting with the [mod.io](https://mod.io) API.
#[derive(Clone, Debug)]
pub struct Modio {
    pub(crate) inner: Arc<ClientRef>,
}

#[derive(Debug)]
pub(crate) struct ClientRef {
    pub(crate) host: String,
    pub(crate) client: Client,
    pub(crate) credentials: Credentials,
}

impl Modio {
    /// Constructs a new `Builder` to configure a `Modio` client.
    ///
    /// This is the same as `Builder::new(credentials)`.
    pub fn builder<C: Into<Credentials>>(credentials: C) -> Builder {
        Builder::new(credentials)
    }

    /// Create an endpoint to [https://api.mod.io/v1](https://docs.mod.io/#mod-io-api-v1).
    pub fn new<C>(credentials: C) -> Result<Self>
    where
        C: Into<Credentials>,
    {
        Builder::new(credentials).build()
    }

    /// Create an endpoint to a different host.
    pub fn host<H, C>(host: H, credentials: C) -> Result<Self>
    where
        H: Into<String>,
        C: Into<Credentials>,
    {
        Builder::new(credentials).host(host).build()
    }

    /// Return an endpoint with new credentials.
    #[must_use]
    pub fn with_credentials<CR>(&self, credentials: CR) -> Self
    where
        CR: Into<Credentials>,
    {
        Self {
            inner: Arc::new(ClientRef {
                host: self.inner.host.clone(),
                client: self.inner.client.clone(),
                credentials: credentials.into(),
            }),
        }
    }

    /// Return an endpoint with a new token.
    #[must_use]
    pub fn with_token<T>(&self, token: T) -> Self
    where
        T: Into<Token>,
    {
        Self {
            inner: Arc::new(ClientRef {
                host: self.inner.host.clone(),
                client: self.inner.client.clone(),
                credentials: Credentials {
                    api_key: self.inner.credentials.api_key.clone(),
                    token: Some(token.into()),
                },
            }),
        }
    }

    /// Return a reference to an interface for requesting access tokens.
    pub fn auth(&self) -> Auth {
        Auth::new(self.clone())
    }

    /// Return a reference to an interface that provides access to game information.
    pub fn games(&self) -> Games {
        Games::new(self.clone())
    }

    /// Return a reference to a game.
    pub fn game(&self, game_id: GameId) -> GameRef {
        GameRef::new(self.clone(), game_id)
    }

    /// Return a reference to a mod.
    pub fn mod_(&self, game_id: GameId, mod_id: ModId) -> ModRef {
        ModRef::new(self.clone(), game_id, mod_id)
    }

    /// Returns [`Downloader`] for saving to file or retrieving
    /// the data via [`Stream`].
    ///
    /// The download fails with [`modio::download::Error`] as source
    /// if a primary file, a specific file or a specific version is not found.
    ///
    /// [`Downloader`]: crate::download::Downloader
    /// [`modio::download::Error`]: crate::download::Error
    /// [`Stream`]: futures_util::Stream
    ///
    /// # Example
    /// ```no_run
    /// use futures_util::{future, TryStreamExt};
    /// use modio::download::{DownloadAction, ResolvePolicy};
    /// use modio::types::id::Id;
    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
    /// #    let modio = modio::Modio::new("user-or-game-api-key")?;
    ///
    /// // Download the primary file of a mod.
    /// let action = DownloadAction::Primary {
    ///     game_id: Id::new(5),
    ///     mod_id: Id::new(19),
    /// };
    /// modio
    ///     .download(action)
    ///     .await?
    ///     .save_to_file("mod.zip")
    ///     .await?;
    ///
    /// // Download the specific file of a mod.
    /// let action = DownloadAction::File {
    ///     game_id: Id::new(5),
    ///     mod_id: Id::new(19),
    ///     file_id: Id::new(101),
    /// };
    /// modio
    ///     .download(action)
    ///     .await?
    ///     .save_to_file("mod.zip")
    ///     .await?;
    ///
    /// // Download the specific version of a mod.
    /// // if multiple files are found then the latest file is downloaded.
    /// // Set policy to `ResolvePolicy::Fail` to return with
    /// // `modio::download::Error::MultipleFilesFound` as source error.
    /// let action = DownloadAction::Version {
    ///     game_id: Id::new(5),
    ///     mod_id: Id::new(19),
    ///     version: "0.1".to_string(),
    ///     policy: ResolvePolicy::Latest,
    /// };
    /// modio
    ///     .download(action)
    ///     .await?
    ///     .stream()
    ///     .try_for_each(|bytes| {
    ///         println!("Bytes: {:?}", bytes);
    ///         future::ok(())
    ///     })
    ///     .await?;
    /// #    Ok(())
    /// # }
    /// ```
    pub async fn download<A>(&self, action: A) -> Result<Downloader>
    where
        DownloadAction: From<A>,
    {
        Downloader::new(self.clone(), action.into()).await
    }

    /// Return a reference to an interface that provides access to resources owned by the user
    /// associated with the current authentication credentials.
    pub fn user(&self) -> Me {
        Me::new(self.clone())
    }

    /// Return a reference to an interface to report games, mods and users.
    pub fn reports(&self) -> Reports {
        Reports::new(self.clone())
    }

    pub(crate) fn request(&self, route: Route) -> RequestBuilder {
        RequestBuilder::new(self.clone(), route)
    }
}