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}