twilight_util/builder/message/
select_menu.rs

1use twilight_model::channel::{
2    ChannelType,
3    message::{
4        EmojiReactionType,
5        component::{SelectDefaultValue, SelectMenu, SelectMenuOption, SelectMenuType},
6    },
7};
8
9/// Create a select menu option with a builder
10#[derive(Clone, Debug, Eq, PartialEq)]
11#[must_use = "must be built into a select menu option"]
12pub struct SelectMenuOptionBuilder(SelectMenuOption);
13
14impl SelectMenuOptionBuilder {
15    /// Create a new select menu option builder.
16    pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
17        Self(SelectMenuOption {
18            default: false,
19            description: None,
20            emoji: None,
21            label: label.into(),
22            value: value.into(),
23        })
24    }
25
26    /// Set whether this option is the default
27    pub const fn default(mut self, default: bool) -> Self {
28        self.0.default = default;
29
30        self
31    }
32
33    /// Set the associated emoji
34    pub fn emoji(mut self, emoji: EmojiReactionType) -> Self {
35        self.0.emoji.replace(emoji);
36
37        self
38    }
39
40    /// Set the description
41    pub fn description(mut self, description: impl Into<String>) -> Self {
42        self.0.description.replace(description.into());
43
44        self
45    }
46
47    /// Build into a select menu option
48    pub fn build(self) -> SelectMenuOption {
49        self.0
50    }
51}
52
53impl From<SelectMenuOptionBuilder> for SelectMenuOption {
54    fn from(builder: SelectMenuOptionBuilder) -> Self {
55        builder.build()
56    }
57}
58
59/// Create a select menu with a builder.
60#[derive(Clone, Debug, Eq, PartialEq)]
61#[must_use = "must be built into a select menu"]
62pub struct SelectMenuBuilder(SelectMenu);
63
64impl SelectMenuBuilder {
65    /// Create a new select menu builder.
66    pub fn new(custom_id: impl Into<String>, kind: SelectMenuType) -> Self {
67        Self(SelectMenu {
68            custom_id: custom_id.into(),
69            disabled: false,
70            max_values: None,
71            min_values: None,
72            options: None,
73            placeholder: None,
74            id: None,
75            channel_types: None,
76            default_values: None,
77            kind,
78            required: None,
79        })
80    }
81
82    /// Set whether this select menu is disabled.
83    pub const fn disabled(mut self, disabled: bool) -> Self {
84        self.0.disabled = disabled;
85
86        self
87    }
88
89    /// Set the max values of this select menu.
90    pub const fn max_values(mut self, max_values: u8) -> Self {
91        self.0.max_values.replace(max_values);
92
93        self
94    }
95
96    /// Set the min values of this select menu.
97    pub const fn min_values(mut self, min_values: u8) -> Self {
98        self.0.min_values.replace(min_values);
99
100        self
101    }
102
103    /// Add an option to this select menu.
104    #[allow(clippy::missing_panics_doc)] // this does not panic; unwrap is never called on None
105    pub fn option(mut self, option: impl Into<SelectMenuOption>) -> Self {
106        if self.0.options.is_none() {
107            self.0.options.replace(Vec::new());
108        }
109
110        self.0.options.as_mut().unwrap().push(option.into());
111
112        self
113    }
114
115    /// Set the placeholder for this select menu.
116    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
117        self.0.placeholder.replace(placeholder.into());
118
119        self
120    }
121
122    /// Set the identifier of this select menu.
123    pub fn id(mut self, id: impl Into<i32>) -> Self {
124        self.0.id.replace(id.into());
125
126        self
127    }
128
129    /// Set the channel types of this select menu.
130    pub fn channel_types(mut self, channel_types: Vec<ChannelType>) -> Self {
131        self.0.channel_types.replace(channel_types);
132
133        self
134    }
135
136    /// Set the default values of this select menu.
137    pub fn default_values(mut self, default_values: Vec<SelectDefaultValue>) -> Self {
138        self.0.default_values.replace(default_values);
139
140        self
141    }
142
143    /// Set whether this select menu is required in a modal.
144    ///
145    /// Ignored in messages.
146    pub const fn required(mut self, required: bool) -> Self {
147        self.0.required.replace(required);
148
149        self
150    }
151
152    /// Build into a select menu,
153    pub fn build(self) -> SelectMenu {
154        self.0
155    }
156}
157
158impl From<SelectMenuBuilder> for SelectMenu {
159    fn from(builder: SelectMenuBuilder) -> Self {
160        builder.build()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use static_assertions::assert_impl_all;
168    use std::fmt::Debug;
169
170    assert_impl_all!(SelectMenuBuilder: Clone, Debug, Eq, PartialEq, Send, Sync);
171    assert_impl_all!(SelectMenu: From<SelectMenuBuilder>);
172
173    #[test]
174    fn builder() {
175        let expected_option = SelectMenuOption {
176            default: false,
177            description: Some("test".to_string()),
178            emoji: None,
179            label: "bar".to_string(),
180            value: "foo".to_string(),
181        };
182
183        let expected = SelectMenu {
184            custom_id: "foo".to_string(),
185            disabled: false,
186            max_values: None,
187            min_values: None,
188            options: Some(vec![expected_option]),
189            placeholder: None,
190            id: None,
191            channel_types: None,
192            default_values: None,
193            kind: SelectMenuType::Text,
194            required: None,
195        };
196
197        let actual = SelectMenuBuilder::new("foo", SelectMenuType::Text)
198            .option(SelectMenuOptionBuilder::new("bar", "foo").description("test"))
199            .build();
200
201        assert_eq!(actual, expected);
202    }
203}