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}