slack_blocks/elems/button.rs
1//! # Button
2//! [slack api docs 🔗]
3//!
4//! Works with block types:
5//! - [Section 🔗]
6//! - [Actions 🔗]
7//!
8//! An interactive component that inserts a button.
9//! The button can be a trigger for anything from opening
10//! a simple link to starting a complex workflow.
11//!
12//! To use interactive components,
13//! you will need to make some changes
14//! to prepare your app.
15//!
16//! Read our [guide to enabling interactivity 🔗].
17//!
18//! [slack api docs 🔗]: https://api.slack.com/reference/block-kit/block-elements#button
19//! [Section 🔗]: https://api.slack.com/reference/block-kit/blocks#section
20//! [Actions 🔗]: https://api.slack.com/reference/block-kit/blocks#actions
21//! [guide to enabling interactivity 🔗]: https://api.slack.com/interactivity/handling
22
23use std::borrow::Cow;
24
25use serde::{Deserialize, Serialize};
26#[cfg(feature = "validation")]
27use validator::Validate;
28
29#[cfg(feature = "validation")]
30use crate::val_helpr::ValidationResult;
31use crate::{compose::Confirm, text};
32
33/// # Button
34/// [slack api docs 🔗]
35///
36/// Works with block types:
37/// - [Section 🔗]
38/// - [Actions 🔗]
39///
40/// An interactive component that inserts a button.
41/// The button can be a trigger for anything from opening
42/// a simple link to starting a complex workflow.
43///
44/// To use interactive components,
45/// you will need to make some changes
46/// to prepare your app.
47///
48/// Read our [guide to enabling interactivity 🔗].
49///
50/// [slack api docs 🔗]: https://api.slack.com/reference/block-kit/block-elements#button
51/// [Section 🔗]: https://api.slack.com/reference/block-kit/blocks#section
52/// [Actions 🔗]: https://api.slack.com/reference/block-kit/blocks#actions
53/// [guide to enabling interactivity 🔗]: https://api.slack.com/interactivity/handling
54#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
55#[cfg_attr(feature = "validation", derive(Validate))]
56pub struct Button<'a> {
57 #[cfg_attr(feature = "validation", validate(custom = "validate::text"))]
58 text: text::Text,
59
60 #[cfg_attr(feature = "validation", validate(length(max = 255)))]
61 action_id: Cow<'a, str>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
64 #[cfg_attr(feature = "validation", validate(custom = "validate::url"))]
65 url: Option<Cow<'a, str>>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
68 #[cfg_attr(feature = "validation", validate(custom = "validate::value"))]
69 value: Option<Cow<'a, str>>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
72 style: Option<Style>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
75 #[cfg_attr(feature = "validation", validate)]
76 confirm: Option<Confirm>,
77}
78
79impl<'a> Button<'a> {
80 /// Build a button!
81 ///
82 /// see build::ButtonBuilder for example.
83 pub fn builder() -> build::ButtonBuilderInit<'a> {
84 build::ButtonBuilderInit::new()
85 }
86
87 /// Validate that this Button element agrees with Slack's model requirements
88 ///
89 /// # Errors
90 /// - If `action_id` is longer than 255 chars
91 /// - If `text` is longer than 75 chars
92 /// - If `url` is longer than 3000 chars
93 /// - If `value` is longer than 2000 chars
94 ///
95 /// # Example
96 /// ```
97 /// use slack_blocks::elems::Button;
98 ///
99 /// let long_string = std::iter::repeat(' ').take(256).collect::<String>();
100 ///
101 /// let btn = Button::builder().text("Button")
102 /// .action_id(long_string)
103 /// .build();
104 ///
105 /// assert_eq!(true, matches!(btn.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/// Style to optionally decorate buttons with
115#[derive(Copy, Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
116#[serde(rename_all = "snake_case")]
117pub enum Style {
118 /// Gives buttons a green outline and text, ideal for affirmation or confirmation actions.
119 /// This should only be used for one button within a set.
120 Primary,
121 /// Gives buttons a red outline and text, and should be used when the action is destructive.
122 /// Use this even more sparingly than Primary.
123 Danger,
124}
125
126/// Button builder
127pub mod build {
128 use std::marker::PhantomData;
129
130 use super::*;
131 use crate::build::*;
132
133 /// Required builder methods
134 #[allow(non_camel_case_types)]
135 pub mod method {
136 /// ButtonBuilder.text
137 #[derive(Copy, Clone, Debug)]
138 pub struct text;
139
140 /// ButtonBuilder.action_id
141 #[derive(Copy, Clone, Debug)]
142 pub struct action_id;
143 }
144
145 /// Initial state for ButtonBuilder
146 pub type ButtonBuilderInit<'a> =
147 ButtonBuilder<'a,
148 RequiredMethodNotCalled<method::text>,
149 RequiredMethodNotCalled<method::action_id>>;
150
151 /// # Button Builder
152 ///
153 /// Allows you to construct safely, with compile-time checks
154 /// on required setter methods.
155 ///
156 /// # Required Methods
157 /// `ButtonBuilder::build()` is only available if these methods have been called:
158 /// - `action_id`
159 /// - `text`
160 ///
161 /// ```
162 /// use std::convert::TryFrom;
163 ///
164 /// use slack_blocks::{blocks, elems};
165 ///
166 /// let button = elems::Button::builder().text("do stuff!")
167 /// .action_id("stuff")
168 /// .build();
169 /// let block: blocks::Block =
170 /// blocks::Actions::builder().element(button).build().into();
171 /// ```
172 #[derive(Debug)]
173 pub struct ButtonBuilder<'a, Text, ActionId> {
174 text: Option<text::Text>,
175 action_id: Option<Cow<'a, str>>,
176 url: Option<Cow<'a, str>>,
177 value: Option<Cow<'a, str>>,
178 style: Option<Style>,
179 confirm: Option<Confirm>,
180 state: PhantomData<(Text, ActionId)>,
181 }
182
183 impl<'a, T, A> ButtonBuilder<'a, T, A> {
184 /// Construct a new button builder
185 pub fn new() -> Self {
186 Self { text: None,
187 action_id: None,
188 url: None,
189 value: None,
190 style: None,
191 confirm: None,
192 state: PhantomData::<_> }
193 }
194
195 /// Set `style` (Optional)
196 ///
197 /// Decorates buttons with alternative visual color schemes.
198 ///
199 /// Use this option with restraint.
200 ///
201 /// If this method is not called,
202 /// the default button style will be used.
203 pub fn style(mut self, style: Style) -> Self {
204 self.style = Some(style);
205 self
206 }
207
208 /// Set `confirm` (Optional)
209 ///
210 /// A [confirm object 🔗] that defines an optional confirmation dialog after the button is clicked.
211 ///
212 /// [confirm object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#confirm
213 pub fn confirm(mut self, confirm: Confirm) -> Self {
214 self.confirm = Some(confirm);
215 self
216 }
217
218 /// Set `url` (Optional)
219 ///
220 /// A URL to load in the user's browser when the button is clicked.
221 ///
222 /// Maximum length for this field is 3000 characters.
223 ///
224 /// If you're using url, you'll still receive an interaction payload
225 /// and will need to send an acknowledgement response.
226 pub fn url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
227 self.url = Some(url.into());
228 self
229 }
230
231 /// Set `value` (Optional)
232 ///
233 /// Add a meaningful value to send back to your app when this button is clicked.
234 ///
235 /// Maximum length for this field is 2000 characters.
236 pub fn value(mut self, value: impl Into<Cow<'a, str>>) -> Self {
237 self.value = Some(value.into());
238 self
239 }
240
241 /// Set `action_id` (**Required**)
242 ///
243 /// An identifier for this action.
244 ///
245 /// You can use this when you receive an interaction payload to [identify the source of the action 🔗].
246 ///
247 /// Should be unique among all other `action_id`s used elsewhere by your app.
248 ///
249 /// Maximum length for this field is 255 characters.
250 ///
251 /// [identify the source of the action 🔗]: https://api.slack.com/interactivity/handling#payloads
252 pub fn action_id(self,
253 action_id: impl Into<Cow<'a, str>>)
254 -> ButtonBuilder<'a, T, Set<method::action_id>> {
255 ButtonBuilder { text: self.text,
256 action_id: Some(action_id.into()),
257 url: self.url,
258 value: self.value,
259 style: self.style,
260 confirm: self.confirm,
261 state: PhantomData::<_> }
262 }
263
264 /// Alias for `text`
265 #[cfg(feature = "blox")]
266 #[cfg_attr(docsrs, doc(cfg(feature = "blox")))]
267 pub fn child(self,
268 text: impl Into<text::Plain>)
269 -> ButtonBuilder<'a, Set<method::text>, A> {
270 self.text(text)
271 }
272
273 /// Set `text` (**Required**)
274 ///
275 /// A plain [text object 🔗] that defines the button's text.
276 ///
277 /// Maximum length for the text in this field is 75 characters.
278 ///
279 /// [text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
280 pub fn text(self,
281 text: impl Into<text::Plain>)
282 -> ButtonBuilder<'a, Set<method::text>, A> {
283 ButtonBuilder { text: Some(text.into().into()),
284 action_id: self.action_id,
285 url: self.url,
286 value: self.value,
287 style: self.style,
288 confirm: self.confirm,
289 state: PhantomData::<_> }
290 }
291 }
292
293 impl<'a> ButtonBuilder<'a, Set<method::text>, Set<method::action_id>> {
294 /// All done building, now give me a darn button!
295 ///
296 /// > `no method name 'build' found for struct 'ButtonBuilder<...>'`?
297 /// Make sure all required setter methods have been called. See docs for `ButtonBuilder`.
298 ///
299 /// ```compile_fail
300 /// use slack_blocks::elems::Button;
301 ///
302 /// let foo = Button::builder().build(); // Won't compile!
303 /// ```
304 ///
305 /// ```
306 /// use slack_blocks::{compose::Opt, elems::Button};
307 ///
308 /// let foo = Button::builder().action_id("foo").text("Do foo").build();
309 /// ```
310 pub fn build(self) -> Button<'a> {
311 Button { text: self.text.unwrap(),
312 action_id: self.action_id.unwrap(),
313 url: self.url,
314 confirm: self.confirm,
315 style: self.style,
316 value: self.value }
317 }
318 }
319}
320
321#[cfg(feature = "validation")]
322mod validate {
323 use super::*;
324 use crate::{text,
325 val_helpr::{below_len, ValidatorResult}};
326
327 pub(super) fn text(text: &text::Text) -> ValidatorResult {
328 below_len("Button Text", 75, text.as_ref())
329 }
330 pub(super) fn url(url: &Cow<str>) -> ValidatorResult {
331 below_len("Button.url", 3000, url)
332 }
333 pub(super) fn value(value: &Cow<str>) -> ValidatorResult {
334 below_len("Button.text", 2000, value)
335 }
336}