use_github_api/users/
mod.rs

1#[cfg(not(test))]
2use crate::url;
3use crate::{check_for_errors, schema::users::contextual_info, GithubClient};
4use serde_json::from_str;
5use std::error::Error;
6
7#[non_exhaustive]
8pub struct Users<'a> {
9    #[cfg_attr(test, allow(dead_code))]
10    client: &'a GithubClient<'a>,
11}
12
13use crate::schema::{
14    users::{list, single},
15    GitHubError,
16};
17impl<'a> Users<'a> {
18    pub(crate) fn new(client: &'a GithubClient<'a>) -> Self {
19        Users { client }
20    }
21
22    /// Fetches a list of users.
23    pub async fn list(&self, cfg: Option<list::Params>) -> Result<Vec<list::User>, Box<dyn Error>> {
24        #[cfg(test)]
25        let text = crate::mock_response!(&self, "users", "list", cfg);
26        #[cfg(not(test))]
27        let text = {
28            let result = self
29                .client
30                .reqwest_client
31                .get(url!(self, "/users"))
32                .query(&cfg)
33                .send()
34                .await?;
35            result.text().await?
36        };
37        let json_result = from_str::<Vec<list::User>>(&text);
38        match json_result {
39            Ok(data) => Ok(data),
40            Err(err) => {
41                if err.is_data() {
42                    let error_data = from_str::<GitHubError>(&text)?;
43                    check_for_errors!(error_data, err);
44                } else {
45                    Err(err.into())
46                }
47            }
48        }
49    }
50
51    /// Fetches a specific user.
52    /// If authenticated, it will show a few more fields.
53    /// If the current authenticated user is the same as the user being fetched, a few more fields will exist.
54    /// # Errors
55    /// Will error if the user does not exist.
56    pub async fn user(&self, username: &str) -> Result<single::User, Box<dyn Error>> {
57        #[cfg(test)]
58        let text = crate::mock_response!(&self, "users", "user", username);
59        #[cfg(not(test))]
60        let text = {
61            let result = self
62                .client
63                .reqwest_client
64                .get(url!(self, "/users/{}", username))
65                .send()
66                .await?;
67            result.text().await?
68        };
69
70        match from_str::<single::User>(&text) {
71            Ok(data) => Ok(data),
72            Err(err) => {
73                if err.is_data() {
74                    let error_data = from_str::<GitHubError>(&text)?;
75                    check_for_errors!(error_data, err);
76                } else {
77                    Err(err.into())
78                }
79            }
80        }
81    }
82
83    #[cfg(any(feature = "auth", doc))]
84    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
85    /// Fetches contextual info (like the hovercard you see on github). Requires auth.
86    /// It can either work with the username alone (and then it will fetch profile data), or it can work with a subject type (like "repository") and id (the repository's id on the API).
87    /// # Errors
88    /// Will error if the user doesn't exist.
89    pub async fn contextual_info(
90        &self,
91        username: &str,
92        cfg: Option<contextual_info::Params>,
93    ) -> Result<contextual_info::User, Box<dyn Error>> {
94        #[cfg(test)]
95        let text = crate::mock_response!(&self, "users", "contextual_info", (username, cfg));
96        #[cfg(not(test))]
97        let text = {
98            let result = self
99                .client
100                .reqwest_client
101                .get(url!(self, "/users/{}/hovercard", username))
102                .query(&cfg)
103                .send()
104                .await?;
105            result.text().await?
106        };
107
108        match from_str::<contextual_info::User>(&text) {
109            Ok(data) => Ok(data),
110            Err(err) => {
111                if err.is_data() {
112                    let error_data = from_str::<GitHubError>(&text)?;
113                    check_for_errors!(error_data, err);
114                } else {
115                    Err(err.into())
116                }
117            }
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    #[cfg(feature = "auth")]
125    use crate::constants::FAKE_TOKEN;
126
127    use super::*;
128
129    #[tokio::test]
130    async fn list_works() {
131        let client = GithubClient::new(
132            #[cfg(feature = "enterprise")]
133            "https://something.com/api/v3",
134            #[cfg(feature = "auth")]
135            FAKE_TOKEN,
136        )
137        .unwrap();
138
139        let users = Users::new(&client);
140        let data = users.list(None).await.unwrap();
141        assert_eq!(data[0].login, "mojombo");
142        assert_eq!(data[0].id, 1);
143    }
144
145    #[tokio::test]
146    async fn single_works() {
147        let client = GithubClient::new(
148            #[cfg(feature = "enterprise")]
149            "https://something.com/api/v3",
150            #[cfg(feature = "auth")]
151            FAKE_TOKEN,
152        )
153        .unwrap();
154        let users = Users::new(&client);
155        let data = users.user("mojombo").await.unwrap();
156        assert_eq!(data.login, "mojombo");
157    }
158
159    #[tokio::test]
160    #[cfg(feature = "auth")]
161    async fn context_info_works() {
162        #[cfg(feature = "auth")]
163        let client = GithubClient::new(
164            #[cfg(feature = "enterprise")]
165            "https://something.com/api/v3",
166            FAKE_TOKEN,
167        )
168        .unwrap();
169        let users = Users::new(&client);
170        let data = users.contextual_info("mojombo", None).await.unwrap();
171        assert_eq!(data.contexts[0].message, "Member of @toml-lang");
172    }
173}