twilight_model/guild/
role.rs

1use super::{RoleFlags, RoleTags};
2use crate::{
3    guild::Permissions,
4    id::{marker::RoleMarker, Id},
5    util::image_hash::ImageHash,
6};
7use serde::{Deserialize, Serialize};
8use std::cmp::Ordering;
9
10#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
11pub struct Role {
12    pub color: u32,
13    pub hoist: bool,
14    /// Icon image hash.
15    ///
16    /// Present if the guild has the `ROLE_ICONS` feature and if the role has
17    /// one.
18    ///
19    /// See [Discord Docs/Image Formatting].
20    ///
21    /// [Discord Docs/Image Formatting]: https://discord.com/developers/docs/reference#image-formatting
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub icon: Option<ImageHash>,
24    pub id: Id<RoleMarker>,
25    pub managed: bool,
26    pub mentionable: bool,
27    pub name: String,
28    pub permissions: Permissions,
29    pub position: i64,
30    /// Flags for this role.
31    pub flags: RoleFlags,
32    /// Tags about the role.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub tags: Option<RoleTags>,
35    /// Icon unicode emoji.
36    ///
37    /// Present if the guild has the `ROLE_ICONS` feature and if the role has
38    /// one.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub unicode_emoji: Option<String>,
41}
42
43impl Ord for Role {
44    /// Compare two roles to each other using their position and ID.
45    ///
46    /// Roles are primarily ordered by their position in descending order. For example,
47    /// a role with a position of 17 is considered a higher role than one with a
48    /// position of 12.
49    ///
50    /// Discord does not guarantee that role positions are positive, unique, or contiguous. When
51    /// two or more roles have the same position then the order is based on the roles' IDs in
52    /// ascending order. For example, given two roles with positions of 10 then a role
53    /// with an ID of 1 would be considered a higher role than one with an ID of 20.
54    ///
55    /// ### Examples
56    ///
57    /// Compare the position of two roles:
58    ///
59    /// ```
60    /// # use twilight_model::{guild::{Permissions, Role, RoleFlags}, id::Id};
61    /// # use std::cmp::Ordering;
62    /// let role_a = Role {
63    ///     id: Id::new(123),
64    ///     position: 12,
65    /// #   color: 0,
66    /// #   hoist: true,
67    /// #   icon: None,
68    /// #   managed: false,
69    /// #   mentionable: true,
70    /// #   name: "test".to_owned(),
71    /// #   permissions: Permissions::ADMINISTRATOR,
72    /// #   flags: RoleFlags::empty(),
73    /// #   tags: None,
74    /// #   unicode_emoji: None,
75    ///     // ...
76    /// };
77    /// let role_b = Role {
78    ///     id: Id::new(456),
79    ///     position: 13,
80    /// #   color: 0,
81    /// #   hoist: true,
82    /// #   icon: None,
83    /// #   managed: false,
84    /// #   mentionable: true,
85    /// #   name: "test".to_owned(),
86    /// #   permissions: Permissions::ADMINISTRATOR,
87    /// #   flags: RoleFlags::empty(),
88    /// #   tags: None,
89    /// #   unicode_emoji: None,
90    ///     // ...
91    /// };
92    /// assert_eq!(Ordering::Less, role_a.cmp(&role_b));
93    /// assert_eq!(Ordering::Greater, role_b.cmp(&role_a));
94    /// assert_eq!(Ordering::Equal, role_a.cmp(&role_a));
95    /// assert_eq!(Ordering::Equal, role_b.cmp(&role_b));
96    /// ```
97    ///
98    /// Compare the position of two roles with the same position:
99    ///
100    /// ```
101    /// # use twilight_model::{guild::{Permissions, Role, RoleFlags}, id::Id};
102    /// # use std::cmp::Ordering;
103    /// let role_a = Role {
104    ///     id: Id::new(123),
105    ///     position: 12,
106    /// #   color: 0,
107    /// #   hoist: true,
108    /// #   icon: None,
109    /// #   managed: false,
110    /// #   mentionable: true,
111    /// #   name: "test".to_owned(),
112    /// #   permissions: Permissions::ADMINISTRATOR,
113    /// #   flags: RoleFlags::empty(),
114    /// #   tags: None,
115    /// #   unicode_emoji: None,
116    /// };
117    /// let role_b = Role {
118    ///     id: Id::new(456),
119    ///     position: 12,
120    /// #   color: 0,
121    /// #   hoist: true,
122    /// #   icon: None,
123    /// #   managed: false,
124    /// #   mentionable: true,
125    /// #   name: "test".to_owned(),
126    /// #   permissions: Permissions::ADMINISTRATOR,
127    /// #   flags: RoleFlags::empty(),
128    /// #   tags: None,
129    /// #   unicode_emoji: None,
130    /// };
131    /// assert_eq!(Ordering::Less, role_a.cmp(&role_b));
132    /// assert_eq!(Ordering::Greater, role_b.cmp(&role_a));
133    /// assert_eq!(Ordering::Equal, role_a.cmp(&role_a));
134    /// assert_eq!(Ordering::Equal, role_b.cmp(&role_b));
135    /// ```
136    fn cmp(&self, other: &Self) -> Ordering {
137        self.position
138            .cmp(&other.position)
139            .then(self.id.get().cmp(&other.id.get()))
140    }
141}
142
143impl PartialOrd for Role {
144    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
145        Some(self.cmp(other))
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::{Permissions, Role};
152    use crate::{guild::RoleFlags, id::Id};
153    use serde::{Deserialize, Serialize};
154    use serde_test::Token;
155    use static_assertions::{assert_fields, assert_impl_all};
156    use std::{fmt::Debug, hash::Hash};
157
158    assert_fields!(
159        Role: color,
160        hoist,
161        icon,
162        id,
163        managed,
164        mentionable,
165        name,
166        permissions,
167        position,
168        tags,
169        unicode_emoji
170    );
171
172    assert_impl_all!(
173        Role: Clone,
174        Debug,
175        Deserialize<'static>,
176        Eq,
177        Hash,
178        PartialEq,
179        Serialize
180    );
181
182    #[test]
183    fn role() {
184        let role = Role {
185            color: 0,
186            hoist: true,
187            icon: None,
188            id: Id::new(123),
189            managed: false,
190            mentionable: true,
191            name: "test".to_owned(),
192            permissions: Permissions::ADMINISTRATOR,
193            position: 12,
194            flags: RoleFlags::IN_PROMPT,
195            tags: None,
196            unicode_emoji: None,
197        };
198
199        serde_test::assert_tokens(
200            &role,
201            &[
202                Token::Struct {
203                    name: "Role",
204                    len: 9,
205                },
206                Token::Str("color"),
207                Token::U32(0),
208                Token::Str("hoist"),
209                Token::Bool(true),
210                Token::Str("id"),
211                Token::NewtypeStruct { name: "Id" },
212                Token::Str("123"),
213                Token::Str("managed"),
214                Token::Bool(false),
215                Token::Str("mentionable"),
216                Token::Bool(true),
217                Token::Str("name"),
218                Token::Str("test"),
219                Token::Str("permissions"),
220                Token::Str("8"),
221                Token::Str("position"),
222                Token::I64(12),
223                Token::Str("flags"),
224                Token::U64(1),
225                Token::StructEnd,
226            ],
227        );
228    }
229}