slack_blocks/elems/
checkboxes.rs

1//! # Checkbox Group
2//!
3//! A checkbox group that allows a user to choose multiple items from a list of possible options.
4//!
5//! [slack api docs 🔗]
6//!
7//! Works in [blocks 🔗]: Section, Actions, Input
8//! Works in [app surfaces 🔗]: Home tabs, Modals, Messages
9//!
10//! [slack api docs 🔗]: https://api.slack.com/reference/block-kit/block-elements#checkboxes
11//! [blocks 🔗]: https://api.slack.com/reference/block-kit/blocks
12//! [app surfaces 🔗]: https://api.slack.com/surfaces
13
14use std::borrow::Cow;
15
16use serde::{Deserialize as De, Serialize as Ser};
17#[cfg(feature = "validation")]
18use validator::Validate;
19
20#[cfg(feature = "validation")]
21use crate::val_helpr::*;
22use crate::{compose::{opt::{AnyText, NoUrl},
23                      Confirm,
24                      Opt},
25            text};
26
27type MyOpt<'a> = Opt<'a, AnyText, NoUrl>;
28
29#[cfg(feature = "validation")]
30fn validate_options<'a>(o: &Cow<'a, [MyOpt<'a>]>) -> ValidatorResult {
31  below_len("options", 10, o.as_ref())
32}
33
34#[cfg(feature = "validation")]
35fn validate_initial_options<'a>(o: &Cow<'a, [MyOpt<'a>]>) -> ValidatorResult {
36  below_len("initial_options", 10, o.as_ref())
37}
38
39/// # Checkbox Group
40///
41/// A checkbox group that allows a user to choose multiple items from a list of possible options.
42///
43/// [slack api docs 🔗]
44///
45/// Works in [blocks 🔗]: Section, Actions, Input
46/// Works in [app surfaces 🔗]: Home tabs, Modals, Messages
47///
48/// [slack api docs 🔗]: https://api.slack.com/reference/block-kit/block-elements#checkboxes
49/// [blocks 🔗]: https://api.slack.com/reference/block-kit/blocks
50/// [app surfaces 🔗]: https://api.slack.com/surfaces
51#[derive(Clone, Debug, Hash, PartialEq, Ser, De)]
52#[cfg_attr(feature = "validation", derive(Validate))]
53pub struct Checkboxes<'a> {
54  #[cfg_attr(feature = "validation", validate(length(max = 255)))]
55  action_id: Cow<'a, str>,
56
57  #[cfg_attr(feature = "validation", validate(custom = "validate_options"))]
58  options: Cow<'a, [MyOpt<'a>]>,
59
60  #[cfg_attr(feature = "validation",
61             validate(custom = "validate_initial_options"))]
62  #[serde(skip_serializing_if = "Option::is_none")]
63  initial_options: Option<Cow<'a, [MyOpt<'a>]>>,
64
65  #[cfg_attr(feature = "validation", validate)]
66  #[serde(skip_serializing_if = "Option::is_none")]
67  confirm: Option<Confirm>,
68}
69
70impl<'a> Checkboxes<'a> {
71  /// Build a new checkboxes element.
72  ///
73  /// # Example
74  /// see example for `build::CheckboxesBuilder`.
75  pub fn builder() -> build::CheckboxesBuilderInit<'a> {
76    build::CheckboxesBuilderInit::new()
77  }
78
79  /// Validate that this element agrees with Slack's model requirements.
80  ///
81  /// # Errors
82  /// - length of `action_id` greater than 255
83  /// - length of `options` greater than 10
84  /// - length of `initial_options` greater than 10
85  /// - one or more of `options` is invalid // TODO
86  /// - one or more of `initial_options` is invalid // TODO
87  /// - `initial_option` is set and an invalid `Opt`
88  /// - `confirm` is set and an invalid `Confirm`
89  ///
90  /// # Example
91  /// ```
92  /// use slack_blocks::{compose::Opt, elems::Checkboxes};
93  ///
94  /// fn repeat<T: Copy>(el: T, n: usize) -> impl Iterator<Item = T> {
95  ///   std::iter::repeat(el).take(n)
96  /// }
97  ///
98  /// let long_string: String = repeat('a', 256).collect();
99  /// let opt = Opt::builder().text_md("foo").value("bar").build();
100  ///
101  /// let opts = repeat(&opt, 11).map(|o| o.clone()).collect::<Vec<_>>();
102  ///
103  /// let input = Checkboxes::builder().action_id(long_string)
104  ///                                  .options(opts)
105  ///                                  .build();
106  ///
107  /// assert!(matches!(input.validate(), Err(_)))
108  /// ```
109  #[cfg(feature = "validation")]
110  #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
111  pub fn validate(&self) -> ValidationResult {
112    Validate::validate(self)
113  }
114}
115
116/// Checkbox group builder
117pub mod build {
118  use std::marker::PhantomData;
119
120  use super::*;
121  use crate::build::*;
122
123  /// Required builder methods
124  #[allow(non_camel_case_types)]
125  pub mod method {
126    /// CheckboxesBuilder.action_id
127    #[derive(Copy, Clone, Debug)]
128    pub struct action_id;
129    /// CheckboxesBuilder.options
130    #[derive(Copy, Clone, Debug)]
131    pub struct options;
132  }
133
134  /// Initial state for Checkbox builder
135  pub type CheckboxesBuilderInit<'a> =
136    CheckboxesBuilder<'a,
137                      RequiredMethodNotCalled<method::action_id>,
138                      RequiredMethodNotCalled<method::options>>;
139
140  /// Checkbox group builder
141  ///
142  /// Allows you to construct safely, with compile-time checks
143  /// on required setter methods.
144  ///
145  /// # Required Methods
146  /// `CheckboxesBuilder::build()` is only available if these methods have been called:
147  ///  - `action_id`
148  ///  - `options`
149  ///
150  /// # Example
151  /// ```
152  /// use std::convert::TryFrom;
153  ///
154  /// use slack_blocks::{blocks::{Actions, Block},
155  ///                    compose::Opt,
156  ///                    elems::{BlockElement, Checkboxes}};
157  ///
158  /// mod usa {
159  ///   pub struct State {
160  ///     pub name: String,
161  ///     pub abbrev: String,
162  ///   }
163  ///
164  ///   pub fn arizona() -> State {
165  ///     State { name: String::from("Arizona"),
166  ///             abbrev: String::from("AZ") }
167  ///   }
168  ///
169  ///   pub fn get_states() -> Vec<State> {
170  ///     // ...
171  ///     # vec![]
172  ///   }
173  /// }
174  ///
175  /// let state_opt = |state: usa::State| {
176  ///   Opt::builder().text_plain(state.name)
177  ///                 .value(state.abbrev)
178  ///                 .build()
179  /// };
180  ///
181  /// let states: Vec<Opt<_, _>> =
182  ///   usa::get_states().into_iter().map(state_opt).collect();
183  ///
184  /// let boxes =
185  ///   Checkboxes::builder().action_id("state_picker")
186  ///                        .options(states)
187  ///                        .initial_options(vec![state_opt(usa::arizona())])
188  ///                        .build();
189  ///
190  /// let block: Block = Actions::builder().element(boxes).build().into();
191  ///
192  /// // <send block to slack API>
193  /// ```
194  #[derive(Debug)]
195  pub struct CheckboxesBuilder<'a, A, O> {
196    action_id: Option<Cow<'a, str>>,
197    options: Option<Vec<MyOpt<'a>>>,
198    initial_options: Option<Vec<MyOpt<'a>>>,
199    confirm: Option<Confirm>,
200    state: PhantomData<(A, O)>,
201  }
202
203  impl<'a, A, O> CheckboxesBuilder<'a, A, O> {
204    /// Create a new builder
205    pub fn new() -> Self {
206      Self { action_id: None,
207             options: None,
208             initial_options: None,
209             confirm: None,
210             state: PhantomData::<_> }
211    }
212
213    /// Set `action_id` (Optional)
214    ///
215    /// An identifier for the action triggered when the checkbox group is changed.
216    ///
217    /// You can use this when you receive an interaction payload to [identify the source of the action 🔗].
218    ///
219    /// Should be unique among all other `action_id`s in the containing block.
220    ///
221    /// Maximum length for this field is 255 characters.
222    ///
223    /// [identify the source of the action 🔗]: https://api.slack.com/interactivity/handling#payloads
224    pub fn action_id<S>(self,
225                        action_id: S)
226                        -> CheckboxesBuilder<'a, Set<method::action_id>, O>
227      where S: Into<Cow<'a, str>>
228    {
229      CheckboxesBuilder { action_id: Some(action_id.into()),
230                          options: self.options,
231                          initial_options: self.initial_options,
232                          confirm: self.confirm,
233                          state: PhantomData::<_> }
234    }
235
236    /// Append an `option` to `options`
237    #[cfg(feature = "blox")]
238    #[cfg_attr(docsrs, doc(cfg(feature = "blox")))]
239    pub fn child<T: Into<text::Text>>(
240      self,
241      option: Opt<'a, T, NoUrl>)
242      -> CheckboxesBuilder<'a, A, Set<method::options>> {
243      self.option(option)
244    }
245
246    /// Set `options` (**Required**)
247    ///
248    /// An array of [option objects 🔗].
249    ///
250    /// A maximum of 10 options are allowed.
251    ///
252    /// [option objects 🔗]: https://api.slack.com/reference/block-kit/composition-objects#option
253    pub fn options<T: Into<text::Text>>(
254      self,
255      options: Vec<Opt<'a, T, NoUrl>>)
256      -> CheckboxesBuilder<'a, A, Set<method::options>> {
257      CheckboxesBuilder { action_id: self.action_id,
258                          options: Some(options.into_iter()
259                                               .map(|o| o.into())
260                                               .collect()),
261                          initial_options: self.initial_options,
262                          confirm: self.confirm,
263                          state: PhantomData::<_> }
264    }
265
266    /// Append an `option` to `options`
267    pub fn option<T: Into<text::Text>>(
268      self,
269      option: Opt<'a, T, NoUrl>)
270      -> CheckboxesBuilder<'a, A, Set<method::options>> {
271      let options = match self.options {
272        | Some(mut options) => {
273          options.push(option.into());
274          options
275        },
276        | None => vec![option.into()],
277      };
278
279      CheckboxesBuilder { action_id: self.action_id,
280                          options: Some(options),
281                          initial_options: self.initial_options,
282                          confirm: self.confirm,
283                          state: PhantomData::<_> }
284    }
285
286    /// Set `initial_options` (Optional)
287    ///
288    /// An array of [option objects 🔗] that exactly matches one or more
289    /// of the options within `options`.
290    ///
291    /// These options will be selected when the checkbox group initially loads.
292    ///
293    /// [option objects 🔗]: https://api.slack.com/reference/messaging/composition-objects#option
294    pub fn initial_options<T: Into<text::Text>>(mut self,
295                                                options: Vec<Opt<'a,
296                                                        T,
297                                                        NoUrl>>)
298                                                -> Self {
299      self.initial_options =
300        Some(options.into_iter().map(|o| o.into()).collect());
301      self
302    }
303
304    /// Set `confirm` (Optional)
305    ///
306    /// A [confirm object 🔗] that defines an optional confirmation dialog
307    /// that appears after clicking one of the checkboxes in this element.
308    ///
309    /// [confirm object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#confirm
310    pub fn confirm(mut self, confirm: Confirm) -> Self {
311      self.confirm = Some(confirm);
312      self
313    }
314  }
315
316  impl<'a> CheckboxesBuilder<'a, Set<method::action_id>, Set<method::options>> {
317    /// All done building, now give me a darn checkbox group!
318    ///
319    /// > `no method name 'build' found for struct 'CheckboxesBuilder<...>'`?
320    /// Make sure all required setter methods have been called. See docs for `CheckboxesBuilder`.
321    ///
322    /// ```compile_fail
323    /// use slack_blocks::elems::Checkboxes;
324    ///
325    /// let foo = Checkboxes::builder().build(); // Won't compile!
326    /// ```
327    ///
328    /// ```
329    /// use slack_blocks::{compose::Opt, elems::Checkboxes};
330    ///
331    /// let foo = Checkboxes::builder().action_id("foo")
332    ///                                .options(vec![Opt::builder().text_plain("foo")
333    ///                                                            .value("bar")
334    ///                                                            .build()])
335    ///                                .build();
336    /// ```
337    pub fn build(self) -> Checkboxes<'a> {
338      Checkboxes { action_id: self.action_id.unwrap(),
339                   options: self.options.unwrap().into(),
340                   initial_options: self.initial_options.map(|os| os.into()),
341                   confirm: self.confirm }
342    }
343  }
344}