slack_blocks/compose/
confirm.rs

1//! # Confirm Dialog
2//! [slack api docs 🔗]
3//!
4//! An object that defines a dialog that provides a confirmation step to any interactive element.
5//! This dialog will ask the user to confirm their action by offering a confirm and deny buttons.
6//!
7//! [slack api docs 🔗]: https://api.slack.com/reference/block-kit/composition-objects#confirm
8
9use serde::{Deserialize, Serialize};
10#[cfg(feature = "validation")]
11use validator::Validate;
12
13use crate::text;
14#[cfg(feature = "validation")]
15use crate::val_helpr::ValidationResult;
16
17/// # Confirm Dialog
18/// [slack api docs 🔗]
19///
20/// An object that defines a dialog that provides a confirmation step to any interactive element.
21/// This dialog will ask the user to confirm their action by offering a confirm and deny buttons.
22///
23/// [slack api docs 🔗]: https://api.slack.com/reference/block-kit/composition-objects#confirm
24#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
25#[cfg_attr(feature = "validation", derive(Validate))]
26pub struct Confirm {
27  #[cfg_attr(feature = "validation", validate(custom = "validate::title"))]
28  title: text::Text,
29
30  #[cfg_attr(feature = "validation", validate(custom = "validate::text"))]
31  text: text::Text,
32
33  #[cfg_attr(feature = "validation", validate(custom = "validate::confirm"))]
34  confirm: text::Text,
35
36  #[cfg_attr(feature = "validation", validate(custom = "validate::deny"))]
37  deny: text::Text,
38
39  #[serde(skip_serializing_if = "Option::is_none")]
40  style: Option<ConfirmStyle>,
41}
42
43impl Confirm {
44  /// Build a new Confirm object
45  ///
46  /// See ConfirmBuilder for example
47  pub fn builder() -> build::ConfirmBuilderInit {
48    build::ConfirmBuilderInit::new()
49  }
50
51  /// Validate that this Confirm composition object
52  /// agrees with Slack's model requirements
53  ///
54  /// # Errors
55  /// - If `title` longer than 100 chars
56  /// - If `text` longer than 300 chars
57  /// - If `confirm` longer than 30 chars
58  /// - If `deny` longer than 30 chars
59  ///
60  /// # Example
61  /// ```
62  /// use slack_blocks::compose::{Confirm, ConfirmStyle};
63  /// use slack_blocks::text;
64  ///
65  /// let dialog = Confirm::builder().title(
66  ///         "Are you sure?",).text(
67  ///         text::Mrkdwn::from("Are you _sure_ you're sure?\nThis action is permanent."),).confirm(
68  ///         "I'm sure.",).deny(
69  ///         "I'm not sure! Oh, geez, I just don't know! Help me decide, please??? Gosh, this is scary...",)
70  ///     .style(ConfirmStyle::Danger)
71  ///     .build();
72  ///
73  /// assert_eq!(true, matches!(dialog.validate(), Err(_)));
74  /// ```
75  #[cfg(feature = "validation")]
76  #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
77  pub fn validate(&self) -> ValidationResult {
78    Validate::validate(self)
79  }
80}
81
82/// The possible styles of the confirm button on your dialog.
83#[derive(Copy, Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
84#[serde(rename_all = "snake_case")]
85pub enum ConfirmStyle {
86  /// Display the button with a red background on desktop,
87  /// or red text on mobile.
88  Danger,
89  /// Display the button with a green background on desktop,
90  /// or blue text on mobile.
91  Primary,
92}
93
94/// Build a Confirm object
95pub mod build {
96  use std::marker::PhantomData;
97
98  use super::*;
99  use crate::build::*;
100
101  /// Builder methods
102  #[allow(non_camel_case_types)]
103  pub mod method {
104    /// ConfirmBuilder.title
105    #[derive(Debug, Copy, Clone)]
106    pub struct title;
107
108    /// ConfirmBuilder.text
109    #[derive(Debug, Copy, Clone)]
110    pub struct text;
111
112    /// ConfirmBuilder.confirm
113    #[derive(Debug, Copy, Clone)]
114    pub struct confirm;
115
116    /// ConfirmBuilder.deny
117    #[derive(Debug, Copy, Clone)]
118    pub struct deny;
119  }
120
121  /// Initial state for Confirm Builder
122  pub type ConfirmBuilderInit =
123    ConfirmBuilder<RequiredMethodNotCalled<method::title>,
124                   RequiredMethodNotCalled<method::text>,
125                   RequiredMethodNotCalled<method::confirm>,
126                   RequiredMethodNotCalled<method::deny>>;
127
128  /// # Confirm Builder
129  ///
130  /// Allows you to construct safely, with compile-time checks
131  /// on required setter methods.
132  ///
133  /// # Required Methods
134  /// `ConfirmBuilder::build()` is only available if these methods have been called:
135  ///  - `title`
136  ///  - `text`
137  ///  - `confirm`
138  ///  - `deny`
139  ///
140  /// ```
141  /// use slack_blocks::compose::Confirm;
142  ///
143  /// let foo = Confirm::builder().title("do stuff?")
144  ///                             .text_plain("stuff")
145  ///                             .confirm("do the stuff")
146  ///                             .deny("wait no")
147  ///                             .build();
148  /// ```
149  #[derive(Debug)]
150  pub struct ConfirmBuilder<Title, Text, Confirm, Deny> {
151    title: Option<text::Text>,
152    text: Option<text::Text>,
153    confirm: Option<text::Text>,
154    deny: Option<text::Text>,
155    style: Option<ConfirmStyle>,
156    state: PhantomData<(Title, Text, Confirm, Deny)>,
157  }
158
159  impl<Title, Text, Confirm, Deny> ConfirmBuilder<Title, Text, Confirm, Deny> {
160    /// Construct a new confirm builder
161    pub fn new() -> Self {
162      Self { title: None,
163             text: None,
164             confirm: None,
165             deny: None,
166             style: None,
167             state: PhantomData::<_> }
168    }
169
170    /// Set `style` (**Required**)
171    ///
172    /// Defines the color scheme applied to the `confirm` button.
173    ///
174    /// A value of `danger` will display the button with a red background on desktop, or red text on mobile.
175    ///
176    /// A value of `primary` will display the button with a green background on desktop, or blue text on mobile.
177    ///
178    /// If this field is not provided, the default value will be `primary`.
179    pub fn style(mut self, style: ConfirmStyle) -> Self {
180      self.style = Some(style);
181      self
182    }
183
184    /// Set `title` (**Required**)
185    ///
186    /// A [`plain_text`-only text object 🔗] that defines the dialog's title.
187    ///
188    /// Maximum length for this field is 100 characters.
189    ///
190    /// [`plain_text`-only text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
191    pub fn title(self,
192                 t: impl Into<text::Plain>)
193                 -> ConfirmBuilder<Set<method::title>, Text, Confirm, Deny>
194    {
195      ConfirmBuilder { text: self.text,
196                       title: Some(t.into().into()),
197                       confirm: self.confirm,
198                       deny: self.deny,
199                       style: self.style,
200                       state: PhantomData::<_> }
201    }
202
203    /// Set `confirm` (**Required**)
204    ///
205    /// A [`plain_text`-only text object 🔗] to define
206    /// the text of the button that confirms the action.
207    ///
208    /// Maximum length for the `text` in this field is 30 characters.
209    ///
210    /// [`plain_text`-only text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
211    pub fn confirm(
212      self,
213      t: impl Into<text::Plain>)
214      -> ConfirmBuilder<Title, Text, Set<method::confirm>, Deny> {
215      ConfirmBuilder { text: self.text,
216                       title: self.title,
217                       confirm: Some(t.into().into()),
218                       deny: self.deny,
219                       style: self.style,
220                       state: PhantomData::<_> }
221    }
222
223    /// Set `deny` (**Required**)
224    ///
225    /// A [`plain_text`-only text object 🔗] to define
226    /// the text of the button that cancels the action.
227    ///
228    /// Maximum length for the `text` in this field is 30 characters.
229    ///
230    /// [`plain_text`-only text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
231    pub fn deny(self,
232                t: impl Into<text::Plain>)
233                -> ConfirmBuilder<Title, Text, Confirm, Set<method::deny>> {
234      ConfirmBuilder { text: self.text,
235                       title: self.title,
236                       confirm: self.confirm,
237                       deny: Some(t.into().into()),
238                       style: self.style,
239                       state: PhantomData::<_> }
240    }
241
242    /// Set `text` (**Required**)
243    ///
244    /// A [text object 🔗] that defines the explanatory text that
245    /// appears in the confirm dialog.
246    ///
247    /// Maximum length for the `text` in this field is 300 characters.
248    ///
249    /// [text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
250    pub fn text(self,
251                t: impl Into<text::Text>)
252                -> ConfirmBuilder<Title, Set<method::text>, Confirm, Deny> {
253      ConfirmBuilder { text: Some(t.into().into()),
254                       title: self.title,
255                       confirm: self.confirm,
256                       deny: self.deny,
257                       style: self.style,
258                       state: PhantomData::<_> }
259    }
260
261    /// Set `text` to some plain text (**Required**)
262    ///
263    /// A [text object 🔗] that defines the explanatory text that
264    /// appears in the confirm dialog.
265    ///
266    /// Maximum length for the `text` in this field is 300 characters.
267    ///
268    /// [text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
269    pub fn text_plain(
270      self,
271      t: impl Into<text::Plain>)
272      -> ConfirmBuilder<Title, Set<method::text>, Confirm, Deny> {
273      self.text(t.into())
274    }
275
276    /// Set `text` to some markdown (**Required**)
277    ///
278    /// A [text object 🔗] that defines the explanatory text that
279    /// appears in the confirm dialog.
280    ///
281    /// Maximum length for the `text` in this field is 300 characters.
282    ///
283    /// [text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
284    pub fn text_md(
285      self,
286      t: impl Into<text::Mrkdwn>)
287      -> ConfirmBuilder<Title, Set<method::text>, Confirm, Deny> {
288      self.text(t.into())
289    }
290  }
291
292  impl
293    ConfirmBuilder<Set<method::title>,
294                   Set<method::text>,
295                   Set<method::confirm>,
296                   Set<method::deny>>
297  {
298    /// All done building, now give me a darn confirm object!
299    ///
300    /// > `no method name 'build' found for struct 'ConfirmBuilder<...>'`?
301    /// Make sure all required setter methods have been called. See docs for `ConfirmBuilder`.
302    ///
303    /// ```compile_fail
304    /// use slack_blocks::compose::Confirm;
305    ///
306    /// let foo = Confirm::builder().build(); // Won't compile!
307    /// ```
308    ///
309    /// ```
310    /// use slack_blocks::compose::Confirm;
311    ///
312    /// let foo = Confirm::builder().title("do stuff?")
313    ///                             .text_plain("stuff")
314    ///                             .confirm("do the stuff")
315    ///                             .deny("wait no")
316    ///                             .build();
317    /// ```
318    pub fn build(self) -> Confirm {
319      Confirm { text: self.text.unwrap(),
320                title: self.title.unwrap(),
321                confirm: self.confirm.unwrap(),
322                deny: self.deny.unwrap(),
323                style: self.style }
324    }
325  }
326}
327
328#[cfg(feature = "validation")]
329mod validate {
330  use crate::{text, val_helpr::*};
331
332  pub(super) fn text(text: &text::Text) -> ValidatorResult {
333    below_len("Confirmation Dialog text", 300, text.as_ref())
334  }
335
336  pub(super) fn title(text: &text::Text) -> ValidatorResult {
337    below_len("Confirmation Dialog title", 100, text.as_ref())
338  }
339
340  pub(super) fn confirm(text: &text::Text) -> ValidatorResult {
341    below_len("Confirmation Dialog confirmation text", 30, text.as_ref())
342  }
343
344  pub(super) fn deny(text: &text::Text) -> ValidatorResult {
345    below_len("Confirmation Dialog deny text", 30, text.as_ref())
346  }
347}