slack_blocks/elems/select/conversation.rs
1//! # Select Conversation List
2//!
3//! [slack api docs 🔗](https://api.slack.com/reference/block-kit/block-elements#conversation_select)
4//!
5//! This select menu will populate its options with a list of public and private channels,
6//! DMs, and MPIMs visible to the current user in the active workspace.
7
8use std::borrow::Cow;
9
10use serde::{Deserialize, Serialize};
11#[cfg(feature = "validation")]
12use validator::Validate;
13
14#[cfg(feature = "validation")]
15use crate::val_helpr::ValidationResult;
16use crate::{compose::{Confirm, ConversationFilter},
17 text};
18
19/// # Select Conversation List
20///
21/// [slack api docs 🔗](https://api.slack.com/reference/block-kit/block-elements#conversation_select)
22///
23/// This select menu will populate its options with a list of public and private channels,
24/// DMs, and MPIMs visible to the current user in the active workspace.
25#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
26#[cfg_attr(feature = "validation", derive(Validate))]
27pub struct Conversation<'a> {
28 #[cfg_attr(feature = "validation",
29 validate(custom = "super::validate::placeholder"))]
30 placeholder: text::Text,
31
32 #[cfg_attr(feature = "validation", validate(length(max = 255)))]
33 action_id: Cow<'a, str>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 #[cfg_attr(feature = "validation", validate)]
37 confirm: Option<Confirm>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
40 initial_channel: Option<Cow<'a, str>>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
43 default_to_current_conversation: Option<bool>,
44
45 #[cfg_attr(feature = "validation", validate)]
46 #[serde(skip_serializing_if = "Option::is_none")]
47 filter: Option<ConversationFilter>,
48}
49
50impl<'a> Conversation<'a> {
51 /// Build a new conversation multi-select element
52 ///
53 /// # Examples
54 /// ```
55 /// use std::convert::TryFrom;
56 ///
57 /// use slack_blocks::{blocks::{Actions, Block},
58 /// compose::Opt,
59 /// elems::{select, BlockElement},
60 /// text};
61 ///
62 /// let select =
63 /// select::Conversation::builder().placeholder("Choose your favorite channel!")
64 /// .action_id("fave_channel")
65 /// .build();
66 ///
67 /// let block: Block = Actions::builder().element(select).build().into();
68 /// ```
69 pub fn builder() -> build::ConversationBuilderInit<'a> {
70 build::ConversationBuilderInit::new()
71 }
72
73 /// Validate that this conversation select agrees with Slack's model requirements
74 ///
75 /// # Errors
76 /// - If `from_placeholder_and_action_id` was called with
77 /// `placeholder` longer than 150 chars
78 /// - If `from_placeholder_and_action_id` was called with
79 /// `action_id` longer than 255 chars
80 /// - If `with_confirm` was called with an invalid `Confirm` structure
81 ///
82 /// # Example
83 /// ```
84 /// use slack_blocks::elems::select;
85 ///
86 /// let select = select::Conversation::builder().placeholder(
87 /// r#"Hey I really would appreciate it if you chose
88 /// a channel relatively soon, so that we can figure out
89 /// where we need to send this poll, ok? it's kind of
90 /// important that you specify where this poll should be
91 /// sent, in case we haven't made that super clear.
92 /// If you understand, could you pick a channel, already??"#,
93 /// )
94 /// .action_id("ABC123")
95 /// .build();
96 ///
97 /// assert!(matches!(select.validate(), Err(_)))
98 /// ```
99 #[cfg(feature = "validation")]
100 #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
101 pub fn validate(&self) -> ValidationResult {
102 Validate::validate(&self)
103 }
104}
105
106/// Conversation Select Builder
107pub mod build {
108 use std::marker::PhantomData;
109
110 use super::*;
111 use crate::{build::*,
112 elems::select::{multi, select_kind}};
113
114 /// Required builder methods
115 #[allow(non_camel_case_types)]
116 pub mod method {
117 /// ConversationBuilder.placeholder
118 #[derive(Copy, Clone, Debug)]
119 pub struct placeholder;
120 /// ConversationBuilder.action_id
121 #[derive(Copy, Clone, Debug)]
122 pub struct action_id;
123 /// ConversationBuilder.initial_channel(s)
124 #[derive(Copy, Clone, Debug)]
125 pub struct initial_channel;
126 }
127
128 /// Conversation Select builder
129 ///
130 /// Allows you to construct a Conversation Select safely, with compile-time checks
131 /// on required setter methods.
132 ///
133 /// # Required Methods
134 /// `ConversationBuilder::build()` is only available if these methods have been called:
135 /// - `placeholder`
136 /// - `action_id`
137 ///
138 /// NOTE: I'm experimenting with an API that deviates from the `from_foo_and_bar`.
139 /// If you're a user of this library, please give me feedback in the repository
140 /// as to which pattern you like more. This will most likely be the new builder pattern
141 /// for every structure in this crate.
142 ///
143 /// # Example
144 /// ```
145 /// use std::convert::TryFrom;
146 ///
147 /// use slack_blocks::{blocks::{Actions, Block},
148 /// compose::Opt,
149 /// elems::{select::Conversation, BlockElement}};
150 ///
151 /// let select =
152 /// Conversation::builder().placeholder("Choose your favorite channel!")
153 /// .action_id("favorite_channel")
154 /// .build();
155 ///
156 /// let block: Block = Actions::builder().element(select).build().into();
157 ///
158 /// // <send block to API>
159 /// ```
160 #[derive(Debug)]
161 pub struct ConversationBuilder<'a,
162 Multi,
163 Placeholder,
164 ActionId,
165 InitialChannel> {
166 placeholder: Option<text::Text>,
167 action_id: Option<Cow<'a, str>>,
168 confirm: Option<Confirm>,
169 filter: Option<ConversationFilter>,
170 default_to_current_conversation: Option<bool>,
171 initial_channel: Option<Cow<'a, str>>,
172 initial_channels: Option<Cow<'a, [String]>>,
173 max_selected_items: Option<u32>,
174 state: PhantomData<(Multi, Placeholder, ActionId, InitialChannel)>,
175 }
176
177 /// Initial state for ConversationBuilder.
178 ///
179 /// Users will be able to choose one of the options.
180 ///
181 /// To allow choosing many, use `slack_blocks::elems::select::multi::Conversation::builder`.
182 pub type ConversationBuilderInit<'a> =
183 ConversationBuilder<'a,
184 select_kind::Single,
185 RequiredMethodNotCalled<method::placeholder>,
186 RequiredMethodNotCalled<method::action_id>,
187 OptionalMethodNotCalled<method::initial_channel>>;
188
189 /// Initial state for ConversationBuilder.
190 ///
191 /// Users will be able to choose many options.
192 pub type MultiConversationBuilderInit<'a> =
193 ConversationBuilder<'a,
194 select_kind::Multi,
195 RequiredMethodNotCalled<method::placeholder>,
196 RequiredMethodNotCalled<method::action_id>,
197 OptionalMethodNotCalled<method::initial_channel>>;
198
199 // Methods that are always available
200 impl<'a, M, P, A, I> ConversationBuilder<'a, M, P, A, I> {
201 /// Construct a new ConversationBuilder
202 pub fn new() -> Self {
203 Self { placeholder: None,
204 action_id: None,
205 filter: None,
206 default_to_current_conversation: None,
207 initial_channel: None,
208 initial_channels: None,
209 max_selected_items: None,
210 confirm: None,
211 state: PhantomData::<_> }
212 }
213
214 /// Change the marker type params to some other arbitrary marker type params
215 fn cast_state<P2, A2, I2>(self) -> ConversationBuilder<'a, M, P2, A2, I2> {
216 ConversationBuilder { placeholder: self.placeholder,
217 action_id: self.action_id,
218 confirm: self.confirm,
219 filter: self.filter,
220 default_to_current_conversation:
221 self.default_to_current_conversation,
222 initial_channel: self.initial_channel,
223 initial_channels: self.initial_channels,
224 max_selected_items: self.max_selected_items,
225 state: PhantomData::<_> }
226 }
227
228 /// Set `placeholder` (**Required**)
229 ///
230 /// A [`plain_text` only text object 🔗] that defines
231 /// the placeholder text shown on the menu.
232 /// Maximum length for the `text` in this field is 150 characters.
233 ///
234 /// [`plain_text` only text object 🔗]: https://api.slack.comhttps://api.slack.com/reference/block-kit/composition-objects#text
235 pub fn placeholder(
236 mut self,
237 text: impl Into<text::Plain>)
238 -> ConversationBuilder<'a, M, Set<method::placeholder>, A, I> {
239 self.placeholder = Some(text.into().into());
240 self.cast_state()
241 }
242
243 /// Set `action_id` (**Required**)
244 ///
245 /// An identifier for the action triggered when a menu option is selected.
246 /// You can use this when you receive an interaction payload to [identify the source of the action 🔗].
247 /// Should be unique among all other `action_id`s used elsewhere by your app.
248 /// Maximum length for this field is 255 characters.
249 ///
250 /// [identify the source of the action 🔗]: https://api.slack.comhttps://api.slack.com/interactivity/handling#payloads
251 pub fn action_id(
252 mut self,
253 text: impl Into<Cow<'a, str>>)
254 -> ConversationBuilder<'a, M, P, Set<method::action_id>, I> {
255 self.action_id = Some(text.into());
256 self.cast_state()
257 }
258
259 /// Set `confirm` (Optional)
260 ///
261 /// A [confirm object 🔗] that defines an
262 /// optional confirmation dialog that appears after
263 /// a menu item is selected.
264 ///
265 /// [confirm object 🔗]: https://api.slack.comhttps://api.slack.com/reference/block-kit/composition-objects#confirm
266 pub fn confirm(mut self, confirm: Confirm) -> Self {
267 self.confirm = Some(confirm);
268 self
269 }
270
271 /// Set `filter` (Optional)
272 ///
273 /// A [filter object 🔗] that defines an
274 /// optional confirmation dialog that appears after
275 /// a menu item is selected.
276 ///
277 /// [filter object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#filter_conversations
278 pub fn filter(mut self, filter: ConversationFilter) -> Self {
279 self.filter = Some(filter);
280 self
281 }
282 }
283
284 impl<'a, M, P, A>
285 ConversationBuilder<'a,
286 M,
287 P,
288 A,
289 OptionalMethodNotCalled<method::initial_channel>>
290 {
291 /// Corresponds to `default_to_current_conversation = true` (Optional, exclusive with `initial_channel` or `initial_channels`)
292 ///
293 /// Pre-populates the select menu with the conversation that the user was viewing when they opened the modal,
294 /// if available.
295 ///
296 /// Default is false.
297 pub fn initial_channel_current(
298 mut self)
299 -> ConversationBuilder<'a, M, P, A, Set<method::initial_channel>> {
300 self.default_to_current_conversation = Some(true);
301 self.cast_state()
302 }
303 }
304
305 impl<'a, P, A>
306 ConversationBuilder<'a,
307 select_kind::Single,
308 P,
309 A,
310 OptionalMethodNotCalled<method::initial_channel>>
311 {
312 /// Set `initial_channel` (Optional, exclusive with `initial_channel_current`)
313 ///
314 /// The ID of any valid conversation to be pre-selected when the menu loads.
315 ///
316 /// If `default_to_current_conversation` is called, this will take precedence.
317 pub fn initial_channel<S>(
318 mut self,
319 channel: S)
320 -> ConversationBuilder<'a,
321 select_kind::Single,
322 P,
323 A,
324 Set<method::initial_channel>>
325 where S: Into<Cow<'a, str>>
326 {
327 self.initial_channel = Some(channel.into());
328 self.cast_state()
329 }
330 }
331
332 impl<'a, P, A>
333 ConversationBuilder<'a,
334 select_kind::Multi,
335 P,
336 A,
337 OptionalMethodNotCalled<method::initial_channel>>
338 {
339 /// Set `initial_channel` (Optional, exclusive with `initial_channel_current`)
340 ///
341 /// A collection of IDs of any valid conversations to be pre-selected when the menu loads.
342 pub fn initial_channels<S>(
343 mut self,
344 channels: S)
345 -> ConversationBuilder<'a,
346 select_kind::Multi,
347 P,
348 A,
349 Set<method::initial_channel>>
350 where S: Into<Cow<'a, [String]>>
351 {
352 self.initial_channels = Some(channels.into());
353 self.cast_state()
354 }
355 }
356
357 impl<'a, P, A, I> ConversationBuilder<'a, select_kind::Multi, P, A, I> {
358 /// Set `max_selected_items` (Optional)
359 ///
360 /// Specifies the maximum number of items that can be selected in the menu.
361 ///
362 /// Minimum number is 1.
363 pub fn max_selected_items(mut self, max: u32) -> Self {
364 self.max_selected_items = Some(max);
365 self
366 }
367 }
368
369 impl<'a, I>
370 ConversationBuilder<'a,
371 select_kind::Single,
372 Set<method::placeholder>,
373 Set<method::action_id>,
374 I>
375 {
376 /// All done building, now give me a darn select element!
377 ///
378 /// > `no method name 'build' found for struct 'select::static_::build::ConversationBuilder<...>'`?
379 /// Make sure all required setter methods have been called. See docs for `ConversationBuilder`.
380 ///
381 /// ```compile_fail
382 /// use slack_blocks::elems::select::Conversation;
383 ///
384 /// let sel = Conversation::builder().build(); // Won't compile!
385 /// ```
386 ///
387 /// ```
388 /// use slack_blocks::elems::select::Conversation;
389 ///
390 /// let sel = Conversation::builder().placeholder("foo")
391 /// .action_id("bar")
392 /// .build();
393 /// ```
394 pub fn build(self) -> Conversation<'a> {
395 Conversation { placeholder: self.placeholder.unwrap(),
396 action_id: self.action_id.unwrap(),
397 filter: self.filter,
398 default_to_current_conversation:
399 self.default_to_current_conversation,
400 confirm: self.confirm,
401 initial_channel: self.initial_channel }
402 }
403 }
404
405 impl<'a, I>
406 ConversationBuilder<'a,
407 select_kind::Multi,
408 Set<method::placeholder>,
409 Set<method::action_id>,
410 I>
411 {
412 /// All done building, now give me a darn select element!
413 ///
414 /// > `no method name 'build' found for struct 'select::static_::build::ConversationBuilder<...>'`?
415 /// Make sure all required setter methods have been called. See docs for `ConversationBuilder`.
416 ///
417 /// ```compile_fail
418 /// use slack_blocks::elems::select;
419 ///
420 /// let sel = select::multi::Conversation::builder().build(); // Won't compile!
421 /// ```
422 ///
423 /// ```
424 /// use slack_blocks::elems::select;
425 ///
426 /// let sel = select::multi::Conversation::builder().placeholder("foo")
427 /// .action_id("bar")
428 /// .build();
429 /// ```
430 pub fn build(self) -> multi::Conversation<'a> {
431 multi::Conversation { placeholder: self.placeholder.unwrap(),
432 action_id: self.action_id.unwrap(),
433 confirm: self.confirm,
434 filter: self.filter,
435 default_to_current_conversation:
436 self.default_to_current_conversation,
437 initial_channels: self.initial_channels,
438 max_selected_items: self.max_selected_items }
439 }
440 }
441}