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}