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#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
28#[derive(Clone, Debug, Default, Deserialize, Serialize)]
29#[non_exhaustive]
30pub struct Role {
31 pub id: RoleId,
33 #[serde(default)]
35 pub guild_id: GuildId,
36 #[serde(rename = "color")]
38 pub colour: Colour,
39 pub hoist: bool,
44 pub managed: bool,
46 #[serde(default)]
51 pub mentionable: bool,
52 pub name: String,
54 pub permissions: Permissions,
60 #[serde(deserialize_with = "minus1_as_0")]
65 pub position: u16,
66 #[serde(default)]
72 pub tags: RoleTags,
73 pub icon: Option<ImageHash>,
75 pub unicode_emoji: Option<String>,
77}
78
79#[cfg(feature = "model")]
80impl Role {
81 #[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 #[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 #[inline]
116 #[must_use]
117 pub fn has_permission(&self, permission: Permissions) -> bool {
118 self.permissions.contains(permission)
119 }
120
121 #[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 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 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 #[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 fn from(role: Role) -> RoleId {
205 role.id
206 }
207}
208
209impl From<&Role> for RoleId {
210 fn from(role: &Role) -> RoleId {
212 role.id
213 }
214}
215
216#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
220#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
221#[non_exhaustive]
222pub struct RoleTags {
223 pub bot_id: Option<UserId>,
225 pub integration_id: Option<IntegrationId>,
227 #[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
229 pub premium_subscriber: bool,
230 pub subscription_listing_id: Option<SkuId>,
232 #[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
234 pub available_for_purchase: bool,
235 #[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
237 pub guild_connections: bool,
238}
239
240mod 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 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}