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}