twilight_model/gateway/payload/outgoing/
request_guild_members.rs

1use crate::{
2    gateway::opcode::OpCode,
3    id::{
4        marker::{GuildMarker, UserMarker},
5        Id,
6    },
7};
8use serde::{Deserialize, Serialize};
9use std::{
10    error::Error,
11    fmt::{Display, Formatter, Result as FmtResult},
12};
13
14/// Provided IDs is invalid for the request.
15///
16/// Returned by [`RequestGuildMembersBuilder::user_ids`].
17#[derive(Debug)]
18pub struct UserIdsError {
19    kind: UserIdsErrorType,
20}
21
22impl UserIdsError {
23    /// Immutable reference to the type of error that occurred.
24    #[must_use = "retrieving the type has no effect if left unused"]
25    pub const fn kind(&self) -> &UserIdsErrorType {
26        &self.kind
27    }
28
29    /// Consume the error, returning the source error if there is any.
30    #[allow(clippy::unused_self)]
31    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
32    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
33        None
34    }
35
36    /// Consume the error, returning the owned error type and the source error.
37    #[must_use = "consuming the error into its parts has no effect if left unused"]
38    pub fn into_parts(self) -> (UserIdsErrorType, Option<Box<dyn Error + Send + Sync>>) {
39        (self.kind, None)
40    }
41
42    const fn too_many(ids: Vec<Id<UserMarker>>) -> Self {
43        Self {
44            kind: UserIdsErrorType::TooMany { ids },
45        }
46    }
47}
48
49impl Display for UserIdsError {
50    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
51        match &self.kind {
52            UserIdsErrorType::TooMany { ids } => {
53                Display::fmt(&ids.len(), f)?;
54                f.write_str(" user IDs were provided when only a maximum of 100 is allowed")
55            }
56        }
57    }
58}
59
60impl Error for UserIdsError {}
61
62/// Type of [`UserIdsError`] that occurred.
63#[derive(Debug)]
64#[non_exhaustive]
65pub enum UserIdsErrorType {
66    /// More than 100 user IDs were provided.
67    TooMany {
68        /// Provided list of user IDs.
69        ids: Vec<Id<UserMarker>>,
70    },
71}
72
73#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
74pub struct RequestGuildMembers {
75    pub d: RequestGuildMembersInfo,
76    pub op: OpCode,
77}
78
79impl RequestGuildMembers {
80    /// Create a new builder to configure a guild members request.
81    ///
82    /// This is an alias to [`RequestGuildMembersBuilder::new`]. Refer to its
83    /// documentation for more information.
84    pub const fn builder(guild_id: Id<GuildMarker>) -> RequestGuildMembersBuilder {
85        RequestGuildMembersBuilder::new(guild_id)
86    }
87}
88
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct RequestGuildMembersBuilder {
91    guild_id: Id<GuildMarker>,
92    nonce: Option<String>,
93    presences: Option<bool>,
94}
95
96impl RequestGuildMembersBuilder {
97    /// Create a new builder to configure and construct a
98    /// [`RequestGuildMembers`].
99    pub const fn new(guild_id: Id<GuildMarker>) -> Self {
100        Self {
101            guild_id,
102            nonce: None,
103            presences: None,
104        }
105    }
106
107    /// Set the nonce to identify the member chunk response.
108    ///
109    /// By default, this uses Discord's default.
110    #[must_use = "has no effect if not built into a RequestGuildMembers"]
111    pub fn nonce(self, nonce: impl Into<String>) -> Self {
112        self._nonce(nonce.into())
113    }
114
115    fn _nonce(mut self, nonce: String) -> Self {
116        self.nonce.replace(nonce);
117
118        self
119    }
120
121    /// Request that guild members' presences are included in member chunks.
122    ///
123    /// By default, this uses Discord's default.
124    #[must_use = "has no effect if not built into a RequestGuildMembers"]
125    pub fn presences(mut self, presences: bool) -> Self {
126        self.presences.replace(presences);
127
128        self
129    }
130
131    /// Consume the builder, creating a request for users whose usernames start
132    /// with the provided string and optionally limiting the number of members
133    /// to retrieve.
134    ///
135    /// If you specify no limit, then Discord's default will be used, which will
136    /// be an unbounded number of members. Specifying 0 is also equivalent.
137    ///
138    /// To request the entire member list, pass in an empty string and no limit.
139    /// You must also have the `GUILD_MEMBERS` intent enabled.
140    ///
141    /// # Examples
142    ///
143    /// Request all of the guild members that start with the letter "a" and
144    /// their presences:
145    ///
146    /// ```
147    /// use twilight_model::{gateway::payload::outgoing::RequestGuildMembers, id::Id};
148    ///
149    /// let request = RequestGuildMembers::builder(Id::new(1))
150    ///     .presences(true)
151    ///     .query("a", None);
152    ///
153    /// assert_eq!(Id::new(1), request.d.guild_id);
154    /// assert_eq!(Some(0), request.d.limit);
155    /// assert_eq!(Some("a"), request.d.query.as_deref());
156    /// assert_eq!(Some(true), request.d.presences);
157    /// ```
158    pub fn query(self, query: impl Into<String>, limit: Option<u64>) -> RequestGuildMembers {
159        self._query(query.into(), limit)
160    }
161
162    fn _query(self, query: String, limit: Option<u64>) -> RequestGuildMembers {
163        RequestGuildMembers {
164            d: RequestGuildMembersInfo {
165                guild_id: self.guild_id,
166                limit: Some(limit.unwrap_or_default()),
167                nonce: self.nonce,
168                presences: self.presences,
169                query: Some(query),
170                user_ids: None,
171            },
172            op: OpCode::RequestGuildMembers,
173        }
174    }
175
176    /// Consume the builder, creating a request that requests the provided
177    /// member in the specified guild(s).
178    ///
179    /// # Examples
180    ///
181    /// Request a member within a guild and specify a nonce of "test":
182    ///
183    /// ```
184    /// use twilight_model::{
185    ///     gateway::payload::outgoing::request_guild_members::{
186    ///         RequestGuildMemberId, RequestGuildMembers,
187    ///     },
188    ///     id::Id,
189    /// };
190    ///
191    /// let request = RequestGuildMembers::builder(Id::new(1))
192    ///     .nonce("test")
193    ///     .user_id(Id::new(2));
194    ///
195    /// assert_eq!(
196    ///     Some(RequestGuildMemberId::One(Id::new(2))),
197    ///     request.d.user_ids
198    /// );
199    /// ```
200    #[allow(clippy::missing_const_for_fn)]
201    pub fn user_id(self, user_id: Id<UserMarker>) -> RequestGuildMembers {
202        RequestGuildMembers {
203            d: RequestGuildMembersInfo {
204                guild_id: self.guild_id,
205                limit: None,
206                nonce: self.nonce,
207                presences: self.presences,
208                query: None,
209                user_ids: Some(RequestGuildMemberId::One(user_id)),
210            },
211            op: OpCode::RequestGuildMembers,
212        }
213    }
214
215    /// Consume the builder, creating a request that requests the provided
216    /// user(s) in the specified guild(s).
217    ///
218    /// Only up to 100 user IDs can be requested at once.
219    ///
220    /// # Examples
221    ///
222    /// Request two members within one guild and specify a nonce of "test":
223    ///
224    /// ```
225    /// use twilight_model::{
226    ///     gateway::payload::outgoing::request_guild_members::{
227    ///         RequestGuildMemberId,
228    ///         RequestGuildMembers,
229    ///     },
230    ///     id::Id,
231    /// };
232    ///
233    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
234    /// let request = RequestGuildMembers::builder(Id::new(1))
235    ///     .nonce("test")
236    ///     .user_ids(vec![Id::new(2), Id::new(2)])?;
237    ///
238    /// assert!(matches!(request.d.user_ids, Some(RequestGuildMemberId::Multiple(ids)) if ids.len() == 2));
239    /// # Ok(()) }
240    /// ```
241    ///
242    /// # Errors
243    ///
244    /// Returns a [`UserIdsErrorType::TooMany`] error type if more than 100 user
245    /// IDs were provided.
246    pub fn user_ids(
247        self,
248        user_ids: impl Into<Vec<Id<UserMarker>>>,
249    ) -> Result<RequestGuildMembers, UserIdsError> {
250        self._user_ids(user_ids.into())
251    }
252
253    fn _user_ids(self, user_ids: Vec<Id<UserMarker>>) -> Result<RequestGuildMembers, UserIdsError> {
254        if user_ids.len() > 100 {
255            return Err(UserIdsError::too_many(user_ids));
256        }
257
258        Ok(RequestGuildMembers {
259            d: RequestGuildMembersInfo {
260                guild_id: self.guild_id,
261                limit: None,
262                nonce: self.nonce,
263                presences: self.presences,
264                query: None,
265                user_ids: Some(RequestGuildMemberId::Multiple(user_ids)),
266            },
267            op: OpCode::RequestGuildMembers,
268        })
269    }
270}
271
272#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
273pub struct RequestGuildMembersInfo {
274    /// Guild ID.
275    pub guild_id: Id<GuildMarker>,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    /// Maximum number of members to request.
278    pub limit: Option<u64>,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub nonce: Option<String>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub presences: Option<bool>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub query: Option<String>,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub user_ids: Option<RequestGuildMemberId<Id<UserMarker>>>,
287}
288
289/// One or a list of IDs in a request.
290#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
291#[serde(untagged)]
292pub enum RequestGuildMemberId<T> {
293    /// Single ID specified.
294    One(T),
295    /// List of IDs specified.
296    Multiple(Vec<T>),
297}
298
299impl<T> From<T> for RequestGuildMemberId<T> {
300    fn from(id: T) -> Self {
301        Self::One(id)
302    }
303}
304
305impl<T> From<Vec<T>> for RequestGuildMemberId<T> {
306    fn from(ids: Vec<T>) -> Self {
307        Self::Multiple(ids)
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::RequestGuildMembersBuilder;
314    use static_assertions::assert_impl_all;
315    use std::fmt::Debug;
316
317    assert_impl_all!(
318        RequestGuildMembersBuilder: Clone,
319        Debug,
320        Eq,
321        PartialEq,
322        Send,
323        Sync
324    );
325}