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}