slack_blocks/elems/select/
external.rs

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