serenity/model/guild/
role.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4#[cfg(feature = "model")]
5use crate::builder::EditRole;
6#[cfg(all(feature = "cache", feature = "model"))]
7use crate::cache::Cache;
8#[cfg(feature = "model")]
9use crate::http::Http;
10#[cfg(all(feature = "cache", feature = "model"))]
11use crate::internal::prelude::*;
12use crate::model::prelude::*;
13use crate::model::utils::is_false;
14
15fn minus1_as_0<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<u16, D::Error> {
16    i16::deserialize(deserializer).map(|val| if val == -1 { 0 } else { val as u16 })
17}
18
19/// Information about a role within a guild.
20///
21/// A role represents a set of permissions, and can be attached to one or multiple users. A role has
22/// various miscellaneous configurations, such as being assigned a colour. Roles are unique per
23/// guild and do not cross over to other guilds in any way, and can have channel-specific permission
24/// overrides in addition to guild-level permissions.
25///
26/// [Discord docs](https://discord.com/developers/docs/topics/permissions#role-object).
27#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
28#[derive(Clone, Debug, Default, Deserialize, Serialize)]
29#[non_exhaustive]
30pub struct Role {
31    /// The Id of the role. Can be used to calculate the role's creation date.
32    pub id: RoleId,
33    /// The Id of the Guild the Role is in.
34    #[serde(default)]
35    pub guild_id: GuildId,
36    /// The colour of the role.
37    #[serde(rename = "color")]
38    pub colour: Colour,
39    /// Indicator of whether the role is pinned above lesser roles.
40    ///
41    /// In the client, this causes [`Member`]s in the role to be seen above those in roles with a
42    /// lower [`Self::position`].
43    pub hoist: bool,
44    /// Indicator of whether the role is managed by an integration service.
45    pub managed: bool,
46    /// Indicator of whether the role can be mentioned, similar to mentioning a specific member or
47    /// `@everyone`.
48    ///
49    /// Only members of the role will be notified if a role is mentioned with this set to `true`.
50    #[serde(default)]
51    pub mentionable: bool,
52    /// The name of the role.
53    pub name: String,
54    /// A set of permissions that the role has been assigned.
55    ///
56    /// See the [`permissions`] module for more information.
57    ///
58    /// [`permissions`]: crate::model::permissions
59    pub permissions: Permissions,
60    /// The role's position in the position list. Roles are considered higher in hierarchy if their
61    /// position is higher.
62    ///
63    /// The `@everyone` role is usually either `-1` or `0`.
64    #[serde(deserialize_with = "minus1_as_0")]
65    pub position: u16,
66    /// The tags this role has. It can be used to determine if this role is a special role in this
67    /// guild such as guild subscriber role, or if the role is linked to an [`Integration`] or a
68    /// bot.
69    ///
70    /// [`Integration`]: super::Integration
71    #[serde(default)]
72    pub tags: RoleTags,
73    /// Role icon image hash.
74    pub icon: Option<ImageHash>,
75    /// Role unicoded image.
76    pub unicode_emoji: Option<String>,
77}
78
79#[cfg(feature = "model")]
80impl Role {
81    /// Deletes the role.
82    ///
83    /// **Note** Requires the [Manage Roles] permission.
84    ///
85    /// # Errors
86    ///
87    /// Returns [`Error::Http`] if the current user lacks permission to delete this role.
88    ///
89    /// [Manage Roles]: Permissions::MANAGE_ROLES
90    #[inline]
91    pub async fn delete(&mut self, http: impl AsRef<Http>) -> Result<()> {
92        http.as_ref().delete_role(self.guild_id, self.id, None).await
93    }
94
95    /// Edits a [`Role`], optionally setting its new fields.
96    ///
97    /// Requires the [Manage Roles] permission.
98    ///
99    /// # Examples
100    ///
101    /// See the documentation of [`EditRole`] for details.
102    ///
103    /// # Errors
104    ///
105    /// Returns [`Error::Http`] if the current user does not have permission to Manage Roles.
106    ///
107    /// [Manage Roles]: Permissions::MANAGE_ROLES
108    #[inline]
109    pub async fn edit(&mut self, http: impl AsRef<Http>, builder: EditRole<'_>) -> Result<()> {
110        *self = self.guild_id.edit_role(http.as_ref(), self.id, builder).await?;
111        Ok(())
112    }
113
114    /// Check that the role has the given permission.
115    #[inline]
116    #[must_use]
117    pub fn has_permission(&self, permission: Permissions) -> bool {
118        self.permissions.contains(permission)
119    }
120
121    /// Checks whether the role has all of the given permissions.
122    ///
123    /// The 'precise' argument is used to check if the role's permissions are precisely equivalent
124    /// to the given permissions. If you need only check that the role has at least the given
125    /// permissions, pass `false`.
126    #[inline]
127    #[must_use]
128    pub fn has_permissions(&self, permissions: Permissions, precise: bool) -> bool {
129        if precise {
130            self.permissions == permissions
131        } else {
132            self.permissions.contains(permissions)
133        }
134    }
135
136    #[inline]
137    #[must_use]
138    /// Generates a URL to the Role icon's image.
139    pub fn icon_url(&self) -> Option<String> {
140        self.icon.map(|icon| {
141            let ext = if icon.is_animated() { "gif" } else { "webp" };
142
143            cdn!("/role-icons/{}/{}.{}", self.id, icon, ext)
144        })
145    }
146}
147
148impl fmt::Display for Role {
149    /// Format a mention for the role, pinging its members.
150    // This is in the format of: `<@&ROLE_ID>`.
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        fmt::Display::fmt(&self.mention(), f)
153    }
154}
155
156impl Eq for Role {}
157
158impl Ord for Role {
159    fn cmp(&self, other: &Role) -> Ordering {
160        if self.position == other.position {
161            self.id.cmp(&other.id)
162        } else {
163            self.position.cmp(&other.position)
164        }
165    }
166}
167
168impl PartialEq for Role {
169    fn eq(&self, other: &Role) -> bool {
170        self.id == other.id
171    }
172}
173
174impl PartialOrd for Role {
175    fn partial_cmp(&self, other: &Role) -> Option<Ordering> {
176        Some(self.cmp(other))
177    }
178}
179
180#[cfg(feature = "model")]
181impl RoleId {
182    /// Tries to find the [`Role`] by its Id in the cache.
183    #[cfg(feature = "cache")]
184    #[deprecated = "Use Guild::roles. This performs a loop over the entire cache!"]
185    pub fn to_role_cached(self, cache: impl AsRef<Cache>) -> Option<Role> {
186        for guild_entry in cache.as_ref().guilds.iter() {
187            let guild = guild_entry.value();
188
189            if !guild.roles.contains_key(&self) {
190                continue;
191            }
192
193            if let Some(role) = guild.roles.get(&self) {
194                return Some(role.clone());
195            }
196        }
197
198        None
199    }
200}
201
202impl From<Role> for RoleId {
203    /// Gets the Id of a role.
204    fn from(role: Role) -> RoleId {
205        role.id
206    }
207}
208
209impl From<&Role> for RoleId {
210    /// Gets the Id of a role.
211    fn from(role: &Role) -> RoleId {
212        role.id
213    }
214}
215
216/// The tags of a [`Role`].
217///
218/// [Discord docs](https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure).
219#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
220#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
221#[non_exhaustive]
222pub struct RoleTags {
223    /// The Id of the bot the [`Role`] belongs to.
224    pub bot_id: Option<UserId>,
225    /// The Id of the integration the [`Role`] belongs to.
226    pub integration_id: Option<IntegrationId>,
227    /// Whether this is the guild's premium subscriber role.
228    #[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
229    pub premium_subscriber: bool,
230    /// The id of this role's subscription sku and listing.
231    pub subscription_listing_id: Option<SkuId>,
232    /// Whether this role is available for purchase.
233    #[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
234    pub available_for_purchase: bool,
235    /// Whether this role is a guild's linked role.
236    #[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
237    pub guild_connections: bool,
238}
239
240/// A premium subscriber role is reported with the field present and the value `null`.
241mod bool_as_option_unit {
242    use std::fmt;
243
244    use serde::de::{Error, Visitor};
245    use serde::{Deserializer, Serializer};
246
247    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
248        deserializer.deserialize_option(NullValueVisitor)
249    }
250
251    #[allow(clippy::trivially_copy_pass_by_ref)]
252    pub fn serialize<S: Serializer>(_: &bool, serializer: S) -> Result<S::Ok, S::Error> {
253        serializer.serialize_none()
254    }
255
256    struct NullValueVisitor;
257
258    impl Visitor<'_> for NullValueVisitor {
259        type Value = bool;
260
261        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262            f.write_str("null value")
263        }
264
265        fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
266            Ok(true)
267        }
268
269        /// Called by the `simd_json` crate
270        fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
271            Ok(true)
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::RoleTags;
279    use crate::json::{assert_json, json};
280
281    #[test]
282    fn premium_subscriber_role_serde() {
283        let value = RoleTags {
284            bot_id: None,
285            integration_id: None,
286            premium_subscriber: true,
287            subscription_listing_id: None,
288            available_for_purchase: false,
289            guild_connections: false,
290        };
291
292        assert_json(
293            &value,
294            json!({"bot_id": null, "integration_id": null, "premium_subscriber": null, "subscription_listing_id": null}),
295        );
296    }
297
298    #[test]
299    fn non_premium_subscriber_role_serde() {
300        let value = RoleTags {
301            bot_id: None,
302            integration_id: None,
303            premium_subscriber: false,
304            subscription_listing_id: None,
305            available_for_purchase: false,
306            guild_connections: false,
307        };
308
309        assert_json(
310            &value,
311            json!({"bot_id": null, "integration_id": null, "subscription_listing_id": null}),
312        );
313    }
314}