slack_messaging/blocks/
input.rs

1use crate::blocks::elements::{
2    Checkboxes, DatePicker, DatetimePicker, EmailInput, FileInput, MultiSelectMenuConversations,
3    MultiSelectMenuExternalDataSource, MultiSelectMenuPublicChannels, MultiSelectMenuStaticOptions,
4    MultiSelectMenuUsers, NumberInput, PlainTextInput, RadioButtonGroup, RichTextInput,
5    SelectMenuConversations, SelectMenuExternalDataSource, SelectMenuPublicChannels,
6    SelectMenuStaticOptions, SelectMenuUsers, TimePicker, UrlInput,
7};
8use crate::composition_objects::{Plain, Text};
9use crate::validators::*;
10
11use serde::Serialize;
12use slack_messaging_derive::Builder;
13
14/// [Input block](https://docs.slack.dev/reference/block-kit/blocks/input-block)
15/// representation.
16///
17/// # Fields and Validations
18///
19/// For more details, see the [official
20/// documentation](https://docs.slack.dev/reference/block-kit/blocks/input-block).
21///
22/// | Field | Type | Required | Validation |
23/// |-------|------|----------|------------|
24/// | label | [Text]<[Plain]> | Yes | Max length 2000 characters |
25/// | element | [InputElement] | Yes | N/A |
26/// | dispatch_action | bool | No | N/A |
27/// | block_id | String | No | Max length 255 characters |
28/// | hint | [Text]<[Plain]> | No | Max length 2000 characters |
29/// | optional | bool | No | N/A |
30///
31/// # Example
32///
33/// ```
34/// use slack_messaging::plain_text;
35/// use slack_messaging::blocks::Input;
36/// use slack_messaging::blocks::elements::PlainTextInput;
37/// # use std::error::Error;
38///
39/// # fn try_main() -> Result<(), Box<dyn Error>> {
40/// let input = Input::builder()
41///     .block_id("input_1")
42///     .label(plain_text!("label text")?)
43///     .element(
44///         PlainTextInput::builder()
45///             .action_id("text_area_1")
46///             .multiline(true)
47///             .placeholder(plain_text!("Enter some plain text.")?)
48///             .build()?
49///     )
50///     .optional(true)
51///     .build()?;
52///
53/// let expected = serde_json::json!({
54///     "type": "input",
55///     "block_id": "input_1",
56///     "label": {
57///         "type": "plain_text",
58///         "text": "label text"
59///     },
60///     "element": {
61///         "type": "plain_text_input",
62///         "action_id": "text_area_1",
63///         "multiline": true,
64///         "placeholder": {
65///             "type": "plain_text",
66///             "text": "Enter some plain text."
67///         }
68///     },
69///     "optional": true
70/// });
71///
72/// let json = serde_json::to_value(input).unwrap();
73///
74/// assert_eq!(json, expected);
75/// #     Ok(())
76/// # }
77/// # fn main() {
78/// #     try_main().unwrap()
79/// # }
80/// ```
81#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
82#[serde(tag = "type", rename = "input")]
83pub struct Input {
84    #[builder(validate("required", "text_object::max_2000"))]
85    pub(crate) label: Option<Text<Plain>>,
86
87    #[builder(validate("required"))]
88    pub(crate) element: Option<InputElement>,
89
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub(crate) dispatch_action: Option<bool>,
92
93    #[serde(skip_serializing_if = "Option::is_none")]
94    #[builder(validate("text::max_255"))]
95    pub(crate) block_id: Option<String>,
96
97    #[serde(skip_serializing_if = "Option::is_none")]
98    #[builder(validate("text_object::max_2000"))]
99    pub(crate) hint: Option<Text<Plain>>,
100
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub(crate) optional: Option<bool>,
103}
104
105/// Objects that can be an element of the [Input]'s element field.
106#[derive(Debug, Clone, Serialize, PartialEq)]
107#[serde(untagged)]
108pub enum InputElement {
109    /// [Checkbox group](https://docs.slack.dev/reference/block-kit/block-elements/checkboxes-element)
110    /// representation
111    Checkboxes(Box<Checkboxes>),
112
113    /// [Date picker element](https://docs.slack.dev/reference/block-kit/block-elements/date-picker-element)
114    /// representation
115    DatePicker(Box<DatePicker>),
116
117    /// [Datetime picker element](https://docs.slack.dev/reference/block-kit/block-elements/datetime-picker-element)
118    /// representation
119    DatetimePicker(Box<DatetimePicker>),
120
121    /// [Email input element](https://docs.slack.dev/reference/block-kit/block-elements/email-input-element)
122    /// representation
123    EmailInput(Box<EmailInput>),
124
125    /// [File input element](https://docs.slack.dev/reference/block-kit/block-elements/file-input-element)
126    /// representation
127    FileInput(Box<FileInput>),
128
129    /// [Multi select menu of static options](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#static_multi_select)
130    /// representation
131    MultiSelectMenuStaticOptions(Box<MultiSelectMenuStaticOptions>),
132
133    /// [Multi select menu of external data source](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select)
134    /// representation
135    MultiSelectMenuExternalDataSource(Box<MultiSelectMenuExternalDataSource>),
136
137    /// [Multi select menu of users](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#users_multi_select)
138    /// representation
139    MultiSelectMenuUsers(Box<MultiSelectMenuUsers>),
140
141    /// [Multi select menu of conversations](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#conversation_multi_select)
142    /// representation
143    MultiSelectMenuConversations(Box<MultiSelectMenuConversations>),
144
145    /// [Multi select menu of public channels](https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#channel_multi_select)
146    /// representation
147    MultiSelectMenuPublicChannels(Box<MultiSelectMenuPublicChannels>),
148
149    /// [Number input element](https://docs.slack.dev/reference/block-kit/block-elements/number-input-element)
150    /// representation
151    NumberInput(Box<NumberInput>),
152
153    /// [Plain-text input element](https://docs.slack.dev/reference/block-kit/block-elements/plain-text-input-element)
154    /// representation
155    PlainTextInput(Box<PlainTextInput>),
156
157    /// [Radio buton group element](https://docs.slack.dev/reference/block-kit/block-elements/radio-button-group-element)
158    /// representation
159    RadioButtonGroup(Box<RadioButtonGroup>),
160
161    /// [Rich text input element](https://docs.slack.dev/reference/block-kit/block-elements/rich-text-input-element)
162    /// representation
163    RichTextInput(Box<RichTextInput>),
164
165    /// [Select menu of static options](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#static_select)
166    /// representation
167    SelectMenuStaticOptions(Box<SelectMenuStaticOptions>),
168
169    /// [Select menu of external data source](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select)
170    /// representation
171    SelectMenuExternalDataSource(Box<SelectMenuExternalDataSource>),
172
173    /// [Select menu of users](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#users_select)
174    /// representation
175    SelectMenuUsers(Box<SelectMenuUsers>),
176
177    /// [Select menu of conversations](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#conversations_select)
178    /// representation
179    SelectMenuConversations(Box<SelectMenuConversations>),
180
181    /// [Select menu of public channels](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#channels_select)
182    /// representation
183    SelectMenuPublicChannels(Box<SelectMenuPublicChannels>),
184
185    /// [Time picker element](https://docs.slack.dev/reference/block-kit/block-elements/time-picker-element)
186    /// representation
187    TimePicker(Box<TimePicker>),
188
189    /// [URL input element](https://docs.slack.dev/reference/block-kit/block-elements/url-input-element)
190    /// representation
191    UrlInput(Box<UrlInput>),
192}
193
194macro_rules! input_from {
195    ($($ty:ident,)*) => {
196        $(
197            impl From<$ty> for InputElement {
198                fn from(value: $ty) -> Self {
199                    Self::$ty(Box::new(value))
200                }
201            }
202         )*
203    }
204}
205
206input_from! {
207    Checkboxes,
208    DatePicker,
209    DatetimePicker,
210    EmailInput,
211    FileInput,
212    MultiSelectMenuStaticOptions,
213    MultiSelectMenuExternalDataSource,
214    MultiSelectMenuUsers,
215    MultiSelectMenuConversations,
216    MultiSelectMenuPublicChannels,
217    NumberInput,
218    PlainTextInput,
219    RadioButtonGroup,
220    RichTextInput,
221    SelectMenuStaticOptions,
222    SelectMenuExternalDataSource,
223    SelectMenuUsers,
224    SelectMenuConversations,
225    SelectMenuPublicChannels,
226    TimePicker,
227    UrlInput,
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use crate::blocks::elements::test_helpers::*;
234    use crate::composition_objects::test_helpers::*;
235    use crate::errors::*;
236
237    #[test]
238    fn it_implements_builder() {
239        let expected = Input {
240            label: Some(plain_text("foo")),
241            element: Some(text_input().into()),
242            dispatch_action: Some(true),
243            block_id: Some("input_0".into()),
244            hint: Some(plain_text("bar")),
245            optional: Some(true),
246        };
247
248        let val = Input::builder()
249            .set_label(Some(plain_text("foo")))
250            .set_element(Some(text_input()))
251            .set_dispatch_action(Some(true))
252            .set_block_id(Some("input_0"))
253            .set_hint(Some(plain_text("bar")))
254            .set_optional(Some(true))
255            .build()
256            .unwrap();
257
258        assert_eq!(val, expected);
259
260        let val = Input::builder()
261            .label(plain_text("foo"))
262            .element(text_input())
263            .dispatch_action(true)
264            .block_id("input_0")
265            .hint(plain_text("bar"))
266            .optional(true)
267            .build()
268            .unwrap();
269
270        assert_eq!(val, expected);
271    }
272
273    #[test]
274    fn it_requires_label_field() {
275        let err = Input::builder().element(text_input()).build().unwrap_err();
276        assert_eq!(err.object(), "Input");
277
278        let errors = err.field("label");
279        assert!(errors.includes(ValidationErrorKind::Required));
280    }
281
282    #[test]
283    fn it_requires_label_less_than_2000_characters_long() {
284        let err = Input::builder()
285            .label(plain_text("a".repeat(2001)))
286            .element(text_input())
287            .build()
288            .unwrap_err();
289        assert_eq!(err.object(), "Input");
290
291        let errors = err.field("label");
292        assert!(errors.includes(ValidationErrorKind::MaxTextLength(2000)));
293    }
294
295    #[test]
296    fn it_requires_element_field() {
297        let err = Input::builder()
298            .label(plain_text("foo"))
299            .build()
300            .unwrap_err();
301        assert_eq!(err.object(), "Input");
302
303        let errors = err.field("element");
304        assert!(errors.includes(ValidationErrorKind::Required));
305    }
306
307    #[test]
308    fn it_requires_block_id_less_than_255_characters_long() {
309        let err = Input::builder()
310            .label(plain_text("foo"))
311            .element(text_input())
312            .block_id("a".repeat(256))
313            .build()
314            .unwrap_err();
315        assert_eq!(err.object(), "Input");
316
317        let errors = err.field("block_id");
318        assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
319    }
320
321    #[test]
322    fn it_requires_hint_less_then_2000_characters_long() {
323        let err = Input::builder()
324            .label(plain_text("foo"))
325            .element(text_input())
326            .hint(plain_text("a".repeat(2001)))
327            .build()
328            .unwrap_err();
329        assert_eq!(err.object(), "Input");
330
331        let errors = err.field("hint");
332        assert!(errors.includes(ValidationErrorKind::MaxTextLength(2000)));
333    }
334}