use crate::{
client::Client,
error::Error as HttpError,
request::{self, validate, AuditLogReason, AuditLogReasonError, Pending, Request},
routing::Route,
};
use hyper::body::Bytes;
use serde::{de::DeserializeSeed, Serialize};
use std::{
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
future::Future,
pin::Pin,
task::{Context, Poll},
};
use twilight_model::{
guild::member::{Member, MemberDeserializer},
id::{ChannelId, GuildId, RoleId, UserId},
};
#[cfg(not(feature = "simd-json"))]
use serde_json::Value;
#[cfg(feature = "simd-json")]
use simd_json::value::OwnedValue as Value;
#[derive(Debug)]
pub struct UpdateGuildMemberError {
kind: UpdateGuildMemberErrorType,
}
impl UpdateGuildMemberError {
#[must_use = "retrieving the type has no effect if left unused"]
pub const fn kind(&self) -> &UpdateGuildMemberErrorType {
&self.kind
}
#[allow(clippy::unused_self)]
#[must_use = "consuming the error and retrieving the source has no effect if left unused"]
pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
None
}
#[must_use = "consuming the error into its parts has no effect if left unused"]
pub fn into_parts(
self,
) -> (
UpdateGuildMemberErrorType,
Option<Box<dyn Error + Send + Sync>>,
) {
(self.kind, None)
}
}
impl Display for UpdateGuildMemberError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match &self.kind {
UpdateGuildMemberErrorType::NicknameInvalid { .. } => {
f.write_str("the nickname length is invalid")
}
}
}
}
impl Error for UpdateGuildMemberError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum UpdateGuildMemberErrorType {
NicknameInvalid { nickname: String },
}
#[derive(Default, Serialize)]
struct UpdateGuildMemberFields {
#[allow(clippy::option_option)]
#[serde(skip_serializing_if = "Option::is_none")]
channel_id: Option<Option<ChannelId>>,
#[serde(skip_serializing_if = "Option::is_none")]
deaf: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
mute: Option<bool>,
#[allow(clippy::option_option)]
#[serde(skip_serializing_if = "Option::is_none")]
nick: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
roles: Option<Vec<RoleId>>,
}
pub struct UpdateGuildMember<'a> {
fields: UpdateGuildMemberFields,
fut: Option<Pending<'a, Bytes>>,
guild_id: GuildId,
http: &'a Client,
user_id: UserId,
reason: Option<String>,
}
impl<'a> UpdateGuildMember<'a> {
pub(crate) fn new(http: &'a Client, guild_id: GuildId, user_id: UserId) -> Self {
Self {
fields: UpdateGuildMemberFields::default(),
fut: None,
guild_id,
http,
user_id,
reason: None,
}
}
pub fn channel_id(mut self, channel_id: impl Into<Option<ChannelId>>) -> Self {
self.fields.channel_id.replace(channel_id.into());
self
}
pub fn deaf(mut self, deaf: bool) -> Self {
self.fields.deaf.replace(deaf);
self
}
pub fn mute(mut self, mute: bool) -> Self {
self.fields.mute.replace(mute);
self
}
pub fn nick(self, nick: impl Into<Option<String>>) -> Result<Self, UpdateGuildMemberError> {
self._nick(nick.into())
}
fn _nick(mut self, nick: Option<String>) -> Result<Self, UpdateGuildMemberError> {
if let Some(nick) = nick {
if !validate::nickname(&nick) {
return Err(UpdateGuildMemberError {
kind: UpdateGuildMemberErrorType::NicknameInvalid { nickname: nick },
});
}
self.fields.nick.replace(Some(nick));
} else {
self.fields.nick = None;
}
Ok(self)
}
pub fn roles(mut self, roles: Vec<RoleId>) -> Self {
self.fields.roles.replace(roles);
self
}
fn request(&self) -> Result<Request, HttpError> {
let mut request = Request::builder(Route::UpdateMember {
guild_id: self.guild_id.0,
user_id: self.user_id.0,
})
.json(&self.fields)?;
if let Some(reason) = &self.reason {
request = request.headers(request::audit_header(reason)?);
}
Ok(request.build())
}
fn start(&mut self) -> Result<(), HttpError> {
let request = self.request()?;
self.fut.replace(Box::pin(self.http.request_bytes(request)));
Ok(())
}
}
impl<'a> AuditLogReason for UpdateGuildMember<'a> {
fn reason(mut self, reason: impl Into<String>) -> Result<Self, AuditLogReasonError> {
self.reason
.replace(AuditLogReasonError::validate(reason.into())?);
Ok(self)
}
}
impl Future for UpdateGuildMember<'_> {
type Output = Result<Member, HttpError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
if let Some(fut) = self.as_mut().fut.as_mut() {
let bytes = match fut.as_mut().poll(cx) {
Poll::Ready(Ok(bytes)) => bytes,
Poll::Ready(Err(why)) => return Poll::Ready(Err(why)),
Poll::Pending => return Poll::Pending,
};
let mut bytes = bytes.as_ref().to_vec();
let value =
crate::json::from_slice::<Value>(&mut bytes).map_err(HttpError::json)?;
let member_deserializer = MemberDeserializer::new(self.guild_id);
let member = member_deserializer
.deserialize(value)
.map_err(HttpError::json)?;
return Poll::Ready(Ok(member));
}
if let Err(why) = self.as_mut().start() {
return Poll::Ready(Err(why));
}
}
}
}
#[cfg(test)]
mod tests {
use super::{UpdateGuildMember, UpdateGuildMemberFields};
use crate::{request::Request, routing::Route, Client};
use std::error::Error;
use twilight_model::id::{GuildId, UserId};
#[test]
fn test_request() -> Result<(), Box<dyn Error>> {
let client = Client::new("foo");
let guild_id = GuildId(1);
let user_id = UserId(2);
let builder = UpdateGuildMember::new(&client, guild_id, user_id)
.deaf(true)
.mute(true);
let actual = builder.request()?;
let body = UpdateGuildMemberFields {
channel_id: None,
deaf: Some(true),
mute: Some(true),
nick: None,
roles: None,
};
let route = Route::UpdateMember {
guild_id: guild_id.0,
user_id: user_id.0,
};
let expected = Request::builder(route).json(&body)?.build();
assert_eq!(actual.body, expected.body);
assert_eq!(actual.path, expected.path);
Ok(())
}
}