use std::cmp::Ordering;
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
use std::error::Error as StdError;
use std::fmt;
#[cfg(feature = "model")]
use crate::builder::EditRole;
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::Cache;
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
use crate::cache::FromStrAndCache;
#[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;
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
use crate::utils::parse_role;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Role {
pub id: RoleId,
pub guild_id: GuildId,
#[cfg(feature = "utils")]
#[serde(rename = "color")]
pub colour: Colour,
#[cfg(not(feature = "utils"))]
#[serde(rename = "color")]
pub colour: u32,
pub hoist: bool,
pub managed: bool,
#[serde(default)]
pub mentionable: bool,
pub name: String,
pub permissions: Permissions,
pub position: i64,
#[serde(default)]
pub tags: RoleTags,
pub icon: Option<String>,
pub unicode_emoji: Option<String>,
}
#[derive(Deserialize)]
pub(crate) struct InterimRole {
pub id: RoleId,
#[serde(default)]
pub guild_id: GuildId,
#[cfg(feature = "utils")]
#[serde(rename = "color")]
pub colour: Colour,
#[cfg(not(feature = "utils"))]
#[serde(rename = "color")]
pub colour: u32,
pub hoist: bool,
pub managed: bool,
#[serde(default)]
pub mentionable: bool,
pub name: String,
pub permissions: Permissions,
pub position: i64,
#[serde(default)]
pub tags: RoleTags,
}
impl From<InterimRole> for Role {
fn from(r: InterimRole) -> Self {
Self {
id: r.id,
guild_id: r.guild_id,
colour: r.colour,
hoist: r.hoist,
managed: r.managed,
mentionable: r.mentionable,
name: r.name,
permissions: r.permissions,
position: r.position,
tags: r.tags,
icon: None,
unicode_emoji: None,
}
}
}
#[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.0, self.id.0).await
}
#[inline]
pub async fn edit(
&self,
http: impl AsRef<Http>,
f: impl FnOnce(&mut EditRole) -> &mut EditRole,
) -> Result<Role> {
self.guild_id.edit_role(http, self.id, f).await
}
#[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)
}
}
}
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 {
self.id.cmp(&other.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")]
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<'a> From<&'a Role> for RoleId {
fn from(role: &Role) -> RoleId {
role.id
}
}
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
#[derive(Debug)]
pub enum RoleParseError {
NotPresentInCache,
InvalidRole,
}
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
impl fmt::Display for RoleParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotPresentInCache => f.write_str("not present in cache"),
Self::InvalidRole => f.write_str("invalid role"),
}
}
}
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
impl StdError for RoleParseError {}
#[cfg(all(feature = "cache", feature = "model", feature = "utils"))]
impl FromStrAndCache for Role {
type Err = RoleParseError;
fn from_str<CRL>(cache: CRL, s: &str) -> StdResult<Self, Self::Err>
where
CRL: AsRef<Cache> + Send + Sync,
{
match parse_role(s) {
Some(x) => match RoleId(x).to_role_cached(&cache) {
Some(role) => Ok(role),
None => Err(RoleParseError::NotPresentInCache),
},
None => Err(RoleParseError::InvalidRole),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[non_exhaustive]
pub struct RoleTags {
pub bot_id: Option<UserId>,
pub integration_id: Option<IntegrationId>,
#[serde(default, skip_serializing_if = "is_false", with = "premium_subscriber")]
pub premium_subscriber: bool,
}
mod premium_subscriber {
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<'de> Visitor<'de> 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 serde_test::{assert_tokens, Token};
use super::RoleTags;
#[test]
fn premium_subscriber_role_serde() {
let value = RoleTags {
bot_id: None,
integration_id: None,
premium_subscriber: true,
};
assert_tokens(&value, &[
Token::Struct {
name: "RoleTags",
len: 3,
},
Token::Str("bot_id"),
Token::None,
Token::Str("integration_id"),
Token::None,
Token::Str("premium_subscriber"),
Token::None,
Token::StructEnd,
]);
}
#[test]
fn non_premium_subscriber_role_serde() {
let value = RoleTags {
bot_id: None,
integration_id: None,
premium_subscriber: false,
};
assert_tokens(&value, &[
Token::Struct {
name: "RoleTags",
len: 2,
},
Token::Str("bot_id"),
Token::None,
Token::Str("integration_id"),
Token::None,
Token::StructEnd,
]);
}
}