use std::cmp::Ordering;
use std::fmt;
#[cfg(feature = "model")]
use crate::builder::EditRole;
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::Cache;
#[cfg(feature = "model")]
use crate::http::Http;
#[cfg(all(feature = "cache", feature = "model"))]
use crate::internal::prelude::*;
use crate::model::prelude::*;
use crate::model::utils::is_false;
fn minus1_as_0<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<u16, D::Error> {
i16::deserialize(deserializer).map(|val| if val == -1 { 0 } else { val as u16 })
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Role {
pub id: RoleId,
#[serde(default)]
pub guild_id: GuildId,
#[serde(rename = "color")]
pub colour: Colour,
#[serde(rename = "colors")]
pub colours: RoleColours,
pub hoist: bool,
pub managed: bool,
#[serde(default)]
pub mentionable: bool,
pub name: String,
pub permissions: Permissions,
#[serde(deserialize_with = "minus1_as_0")]
pub position: u16,
#[serde(default)]
pub tags: RoleTags,
pub icon: Option<ImageHash>,
pub unicode_emoji: Option<String>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct RoleColours {
#[serde(rename = "primary_color")]
pub primary_colour: Colour,
#[serde(rename = "secondary_color")]
pub secondary_colour: Option<Colour>,
#[serde(rename = "tertiary_color")]
pub tertiary_colour: Option<Colour>,
}
#[cfg(feature = "model")]
impl Role {
#[inline]
pub async fn delete(&mut self, http: impl AsRef<Http>) -> Result<()> {
http.as_ref().delete_role(self.guild_id, self.id, None).await
}
#[inline]
pub async fn edit(&mut self, http: impl AsRef<Http>, builder: EditRole<'_>) -> Result<()> {
*self = self.guild_id.edit_role(http.as_ref(), self.id, builder).await?;
Ok(())
}
#[inline]
#[must_use]
pub fn has_permission(&self, permission: Permissions) -> bool {
self.permissions.contains(permission)
}
#[inline]
#[must_use]
pub fn has_permissions(&self, permissions: Permissions, precise: bool) -> bool {
if precise {
self.permissions == permissions
} else {
self.permissions.contains(permissions)
}
}
#[inline]
#[must_use]
pub fn icon_url(&self) -> Option<String> {
self.icon.map(|icon| {
let ext = if icon.is_animated() { "gif" } else { "webp" };
cdn!("/role-icons/{}/{}.{}", self.id, icon, ext)
})
}
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.mention(), f)
}
}
impl Eq for Role {}
impl Ord for Role {
fn cmp(&self, other: &Role) -> Ordering {
if self.position == other.position {
other.id.cmp(&self.id)
} else {
self.position.cmp(&other.position)
}
}
}
impl PartialEq for Role {
fn eq(&self, other: &Role) -> bool {
self.id == other.id
}
}
impl PartialOrd for Role {
fn partial_cmp(&self, other: &Role) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "model")]
impl RoleId {
#[cfg(feature = "cache")]
#[deprecated = "Use Guild::roles. This performs a loop over the entire cache!"]
pub fn to_role_cached(self, cache: impl AsRef<Cache>) -> Option<Role> {
for guild_entry in cache.as_ref().guilds.iter() {
let guild = guild_entry.value();
if !guild.roles.contains_key(&self) {
continue;
}
if let Some(role) = guild.roles.get(&self) {
return Some(role.clone());
}
}
None
}
}
impl From<Role> for RoleId {
fn from(role: Role) -> RoleId {
role.id
}
}
impl From<&Role> for RoleId {
fn from(role: &Role) -> RoleId {
role.id
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[non_exhaustive]
pub struct RoleTags {
pub bot_id: Option<UserId>,
pub integration_id: Option<IntegrationId>,
#[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
pub premium_subscriber: bool,
pub subscription_listing_id: Option<SkuId>,
#[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
pub available_for_purchase: bool,
#[serde(default, skip_serializing_if = "is_false", with = "bool_as_option_unit")]
pub guild_connections: bool,
}
mod bool_as_option_unit {
use std::fmt;
use serde::de::{Error, Visitor};
use serde::{Deserializer, Serializer};
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
deserializer.deserialize_option(NullValueVisitor)
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn serialize<S: Serializer>(_: &bool, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_none()
}
struct NullValueVisitor;
impl Visitor<'_> for NullValueVisitor {
type Value = bool;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("null value")
}
fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
Ok(true)
}
fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
Ok(true)
}
}
}
#[cfg(test)]
mod tests {
use super::RoleTags;
use crate::json::{assert_json, json};
#[test]
fn premium_subscriber_role_serde() {
let value = RoleTags {
bot_id: None,
integration_id: None,
premium_subscriber: true,
subscription_listing_id: None,
available_for_purchase: false,
guild_connections: false,
};
assert_json(
&value,
json!({"bot_id": null, "integration_id": null, "premium_subscriber": null, "subscription_listing_id": null}),
);
}
#[test]
fn non_premium_subscriber_role_serde() {
let value = RoleTags {
bot_id: None,
integration_id: None,
premium_subscriber: false,
subscription_listing_id: None,
available_for_purchase: false,
guild_connections: false,
};
assert_json(
&value,
json!({"bot_id": null, "integration_id": null, "subscription_listing_id": null}),
);
}
}