automatons_github/resource/
account.rs

1use std::fmt::{Display, Formatter};
2
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::resource::NodeId;
7use crate::{id, name};
8
9id!(
10    /// Unique account id
11    ///
12    /// The [`AccountId`] is a unique, numerical id that is used to interact with an account through
13    /// [GitHub's REST API](https://docs.github.com/en/rest).
14    AccountId
15);
16
17name!(
18    /// Unique account name
19    ///
20    /// Accounts on GitHub have a unique, human-readable name that is used throughout GitHub's
21    /// website.
22    Login
23);
24
25/// GitHub account type
26///
27/// GitHub differentiates between three different kinds of accounts: bots, organizations, and users.
28/// Bots represent (third-party) integrations with the platform, often driven by GitHub Apps.
29/// Organizations provide a space for users to collaborate and share resources. And user accounts
30/// represent the humans that build software on GitHub.
31#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
32pub enum AccountType {
33    /// Bot account
34    Bot,
35
36    /// Organization account
37    Organization,
38
39    /// User account
40    User,
41}
42
43/// GitHub account
44///
45/// GitHub references accounts in many events and API responses. Accounts are a lightweight
46/// representation of three other resources: bots, users, and organizations. They provide a unified
47/// interface to information that is shared between all account types, and hide a lot of information
48/// that would unnecessarily increase payload sizes.
49#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
50pub struct Account {
51    login: Login,
52    id: AccountId,
53    node_id: NodeId,
54    avatar_url: Url,
55    url: Url,
56    html_url: Url,
57    followers_url: Url,
58    following_url: Url,
59    gists_url: Url,
60    starred_url: Url,
61    subscriptions_url: Url,
62    organizations_url: Url,
63    repos_url: Url,
64    events_url: Url,
65    received_events_url: Url,
66    site_admin: bool,
67
68    #[serde(rename = "type")]
69    account_type: AccountType,
70}
71
72impl Account {
73    /// Returns the account's unique [`Login`].
74    #[cfg_attr(feature = "tracing", tracing::instrument)]
75    pub fn login(&self) -> &Login {
76        &self.login
77    }
78
79    /// Returns the account's unique [`AccountId`].
80    #[cfg_attr(feature = "tracing", tracing::instrument)]
81    pub fn id(&self) -> AccountId {
82        self.id
83    }
84
85    /// Returns the account's unique [`NodeId`].
86    #[cfg_attr(feature = "tracing", tracing::instrument)]
87    pub fn node_id(&self) -> &NodeId {
88        &self.node_id
89    }
90
91    /// Returns the URl to the account's avatar.
92    #[cfg_attr(feature = "tracing", tracing::instrument)]
93    pub fn avatar_url(&self) -> &Url {
94        &self.avatar_url
95    }
96
97    /// Returns the API endpoint to query the account.
98    #[cfg_attr(feature = "tracing", tracing::instrument)]
99    pub fn url(&self) -> &Url {
100        &self.url
101    }
102
103    /// Returns the URL to the account.
104    #[cfg_attr(feature = "tracing", tracing::instrument)]
105    pub fn html_url(&self) -> &Url {
106        &self.html_url
107    }
108
109    /// Returns the API endpoint to query the account's followers.
110    #[cfg_attr(feature = "tracing", tracing::instrument)]
111    pub fn followers_url(&self) -> &Url {
112        &self.followers_url
113    }
114
115    /// Returns the API endpoint to query the accounts that this account follows.
116    #[cfg_attr(feature = "tracing", tracing::instrument)]
117    pub fn following_url(&self) -> &Url {
118        &self.following_url
119    }
120
121    /// Returns the API endpoint to query the account's gists.
122    #[cfg_attr(feature = "tracing", tracing::instrument)]
123    pub fn gists_url(&self) -> &Url {
124        &self.gists_url
125    }
126
127    /// Returns the API endpoint to query the repositories that the account has starred.
128    #[cfg_attr(feature = "tracing", tracing::instrument)]
129    pub fn starred_url(&self) -> &Url {
130        &self.starred_url
131    }
132
133    /// Returns the API endpoint to query the account's subscriptions.
134    #[cfg_attr(feature = "tracing", tracing::instrument)]
135    pub fn subscriptions_url(&self) -> &Url {
136        &self.subscriptions_url
137    }
138
139    /// Returns the API endpoint to query the account's organizations.
140    #[cfg_attr(feature = "tracing", tracing::instrument)]
141    pub fn organizations_url(&self) -> &Url {
142        &self.organizations_url
143    }
144
145    /// Returns the API endpoint to query the account's repositories.
146    #[cfg_attr(feature = "tracing", tracing::instrument)]
147    pub fn repos_url(&self) -> &Url {
148        &self.repos_url
149    }
150
151    /// Returns the API endpoint to query the account's events.
152    #[cfg_attr(feature = "tracing", tracing::instrument)]
153    pub fn events_url(&self) -> &Url {
154        &self.events_url
155    }
156
157    /// Returns the API endpoint to query the events that the account has received.
158    #[cfg_attr(feature = "tracing", tracing::instrument)]
159    pub fn received_events_url(&self) -> &Url {
160        &self.received_events_url
161    }
162
163    /// Indicates whether the account is a site admin.
164    #[cfg_attr(feature = "tracing", tracing::instrument)]
165    pub fn site_admin(&self) -> bool {
166        self.site_admin
167    }
168}
169
170impl Display for Account {
171    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
172        write!(f, "{}", self.login)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use url::{ParseError, Url};
179
180    use crate::resource::account::AccountType;
181
182    use super::Account;
183
184    #[rustfmt::skip]
185    fn account() -> Result<Account, ParseError> {
186        Ok(Account {
187            login: "dependabot[bot]".into(),
188            id: 49699333.into(),
189            node_id: "MDM6Qm90NDk2OTkzMzM=".into(),
190            avatar_url: Url::parse("https://avatars.githubusercontent.com/in/29110?v=4")?,
191            url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D")?,
192            html_url: Url::parse("https://github.com/apps/dependabot")?,
193            followers_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/followers")?,
194            following_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}")?,
195            gists_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}")?,
196            starred_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}")?,
197            subscriptions_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/subscriptions")?,
198            organizations_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/orgs")?,
199            repos_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/repos")?,
200            events_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}")?,
201            received_events_url: Url::parse("https://api.github.com/users/dependabot%5Bbot%5D/received_events")?,
202            account_type: AccountType::Bot,
203            site_admin: false,
204        })
205    }
206
207    #[test]
208    fn trait_deserialize() {
209        let json = r#"
210        {
211            "login": "dependabot[bot]",
212            "id": 49699333,
213            "node_id": "MDM6Qm90NDk2OTkzMzM=",
214            "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4",
215            "gravatar_id": "",
216            "url": "https://api.github.com/users/dependabot%5Bbot%5D",
217            "html_url": "https://github.com/apps/dependabot",
218            "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers",
219            "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}",
220            "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}",
221            "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}",
222            "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions",
223            "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs",
224            "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos",
225            "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}",
226            "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events",
227            "type": "Bot",
228            "site_admin": false
229        }
230        "#;
231
232        let account: Account = serde_json::from_str(json).unwrap();
233
234        assert_eq!("dependabot[bot]", account.login().get());
235    }
236
237    #[test]
238    fn trait_display() {
239        let account: Account = account().unwrap();
240
241        assert_eq!("dependabot[bot]", account.to_string());
242    }
243
244    #[test]
245    fn trait_send() {
246        fn assert_send<T: Send>() {}
247        assert_send::<Account>();
248    }
249
250    #[test]
251    fn trait_sync() {
252        fn assert_sync<T: Sync>() {}
253        assert_sync::<Account>();
254    }
255}