slack_blocks/elems/select/external.rs
1//! # Select menu with external data source
2//! [slack api docs 🔗]
3//!
4//! This select menu will load its options from an external data source,
5//! allowing for a dynamic list of options.
6//!
7//! ## Setup
8//! [Slack API doc guide for setting up an external data source 🔗](https://api.slack.com/reference/block-kit/block-elements#external_select__setup)
9//!
10//! [slack api docs 🔗]: https://api.slack.com/reference/block-kit/block-elements#external_select
11
12use std::borrow::Cow;
13
14use serde::{Deserialize, Serialize};
15#[cfg(feature = "validation")]
16use validator::Validate;
17
18#[cfg(feature = "validation")]
19use crate::val_helpr::ValidationResult;
20use crate::{compose,
21 compose::{opt::NoUrl, Confirm},
22 text};
23
24type Opt<'a> = compose::Opt<'a, text::Plain, NoUrl>;
25type OptGroup<'a> = compose::OptGroup<'a, text::Plain, NoUrl>;
26type OptOrOptGroup<'a> = compose::OptOrOptGroup<'a, text::Plain, NoUrl>;
27
28/// # Select menu with external data source
29/// [slack api docs 🔗]
30///
31/// This select menu will load its options from an external data source,
32/// allowing for a dynamic list of options.
33///
34/// ## Setup
35/// [Slack API doc guide for setting up an external data source 🔗](https://api.slack.com/reference/block-kit/block-elements#external_select__setup)
36///
37/// [slack api docs 🔗]: https://api.slack.com/reference/block-kit/block-elements#external_select
38#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
39#[cfg_attr(feature = "validation", derive(Validate))]
40pub struct External<'a> {
41 #[cfg_attr(feature = "validation",
42 validate(custom = "super::validate::placeholder"))]
43 placeholder: text::Text,
44
45 #[cfg_attr(feature = "validation", validate(length(max = 255)))]
46 action_id: Cow<'a, str>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
49 initial_option: Option<OptOrOptGroup<'a>>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
52 min_query_length: Option<u64>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
55 #[cfg_attr(feature = "validation", validate)]
56 confirm: Option<Confirm>,
57}
58
59impl<'a> External<'a> {
60 /// Build a new external select element
61 ///
62 /// # Examples
63 /// ```
64 /// use std::convert::TryFrom;
65 ///
66 /// use slack_blocks::{blocks::{Actions, Block},
67 /// compose::Opt,
68 /// elems::{select, BlockElement},
69 /// text};
70 ///
71 /// let select =
72 /// select::External::builder().placeholder("Choose your favorite city!")
73 /// .action_id("fave_city")
74 /// .build();
75 ///
76 /// let block: Block = Actions::builder().element(select).build().into();
77 /// ```
78 pub fn builder() -> build::ExternalBuilderInit<'a> {
79 build::ExternalBuilderInit::new()
80 }
81
82 /// Validate that this user select agrees with Slack's model requirements
83 ///
84 /// # Errors
85 /// - If `from_placeholder_and_action_id` was called with
86 /// `placeholder` longer than 150 chars
87 /// - If `from_placeholder_and_action_id` was called with
88 /// `action_id` longer than 255 chars
89 ///
90 /// # Example
91 /// ```
92 /// use slack_blocks::elems::select;
93 ///
94 /// let select = select::External::builder().placeholder(
95 /// r#"Hey I really would appreciate it if you chose
96 /// a channel relatively soon, so that we can figure out
97 /// where we need to send this poll, ok? it's kind of
98 /// important that you specify where this poll should be
99 /// sent, in case we haven't made that super clear.
100 /// If you understand, could you pick a channel, already??"#,
101 /// )
102 /// .action_id("ABC123")
103 /// .build();
104 ///
105 /// assert!(matches!(select.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/// External Select Builder
115pub mod build {
116 use std::marker::PhantomData;
117
118 use super::*;
119 use crate::{build::*,
120 elems::select::{multi, select_kind}};
121
122 /// Required builder methods
123 #[allow(non_camel_case_types)]
124 pub mod method {
125 /// PublicChannelBuilder.placeholder
126 #[derive(Copy, Clone, Debug)]
127 pub struct placeholder;
128 /// PublicChannelBuilder.initial_option(s) or initial_option_group(s)
129 #[derive(Copy, Clone, Debug)]
130 pub struct initial_option;
131 /// PublicChannelBuilder.action_id
132 #[derive(Copy, Clone, Debug)]
133 pub struct action_id;
134 }
135
136 /// External Select builder
137 ///
138 /// Allows you to construct a External Select safely, with compile-time checks
139 /// on required setter methods.
140 ///
141 /// # Required Methods
142 /// `ExternalBuilder::build()` is only available if these methods have been called:
143 /// - `placeholder`
144 /// - `action_id`
145 /// - `options` or `option_groups`
146 ///
147 /// NOTE: I'm experimenting with an API that deviates from the `from_foo_and_bar`.
148 /// If you're a user of this library, please give me feedback in the repository
149 /// as to which pattern you like more. This will most likely be the new builder pattern
150 /// for every structure in this crate.
151 ///
152 /// # Example
153 /// ```
154 /// use std::convert::TryFrom;
155 ///
156 /// use slack_blocks::{elems::{select::External, BlockElement},
157 /// blocks::{Actions, Block},
158 /// compose::Opt};
159 ///
160 /// let select =
161 /// External::builder().placeholder("Choose your favorite programming language!")
162 /// .action_id("lang_chosen")
163 /// .build();
164 ///
165 /// let block: Block =
166 /// Actions::builder().element(select).build().into();
167 ///
168 /// // <send block to API>
169 /// ```
170 #[derive(Debug)]
171 pub struct ExternalBuilder<'a, Multi, Placeholder, ActionId, Options> {
172 placeholder: Option<text::Text>,
173 action_id: Option<Cow<'a, str>>,
174 confirm: Option<Confirm>,
175 initial_option: Option<OptOrOptGroup<'a>>,
176 initial_options: Option<Cow<'a, [OptOrOptGroup<'a>]>>,
177 max_selected_items: Option<u32>,
178 min_query_length: Option<u64>,
179 state: PhantomData<(Multi, Placeholder, ActionId, Options)>,
180 }
181
182 /// Initial state for ExternalBuilder.
183 ///
184 /// Users will be able to choose one of the options.
185 ///
186 /// To allow choosing many, use `slack_blocks::elems::select::multi::External::builder`.
187 pub type ExternalBuilderInit<'a> =
188 ExternalBuilder<'a,
189 select_kind::Single,
190 RequiredMethodNotCalled<method::placeholder>,
191 RequiredMethodNotCalled<method::action_id>,
192 OptionalMethodNotCalled<method::initial_option>>;
193
194 /// Initial state for ExternalBuilder.
195 ///
196 /// Users will be able to choose many options.
197 pub type MultiExternalBuilderInit<'a> =
198 ExternalBuilder<'a,
199 select_kind::Multi,
200 RequiredMethodNotCalled<method::placeholder>,
201 RequiredMethodNotCalled<method::action_id>,
202 OptionalMethodNotCalled<method::initial_option>>;
203
204 // Methods that are always available
205 impl<'a, M, P, A, O> ExternalBuilder<'a, M, P, A, O> {
206 /// Construct a new ExternalBuilder
207 pub fn new() -> Self {
208 Self { placeholder: None,
209 action_id: None,
210 initial_option: None,
211 initial_options: None,
212 max_selected_items: None,
213 confirm: None,
214 min_query_length: None,
215 state: PhantomData::<_> }
216 }
217
218 /// Change the marker type params to some other arbitrary marker type params
219 fn cast_state<P2, A2, O2>(self) -> ExternalBuilder<'a, M, P2, A2, O2> {
220 ExternalBuilder { placeholder: self.placeholder,
221 action_id: self.action_id,
222 confirm: self.confirm,
223 initial_option: self.initial_option,
224 initial_options: self.initial_options,
225 max_selected_items: self.max_selected_items,
226 min_query_length: self.min_query_length,
227 state: PhantomData::<_> }
228 }
229
230 /// Set `placeholder` (**Required**)
231 ///
232 /// A [`plain_text` only text object 🔗] that defines
233 /// the placeholder text shown on the menu.
234 /// Maximum length for the `text` in this field is 150 characters.
235 ///
236 /// [`plain_text` only text object 🔗]: https://api.slack.comhttps://api.slack.com/reference/block-kit/composition-objects#text
237 pub fn placeholder(
238 mut self,
239 text: impl Into<text::Plain>)
240 -> ExternalBuilder<'a, M, Set<method::placeholder>, A, O> {
241 self.placeholder = Some(text.into().into());
242 self.cast_state()
243 }
244
245 /// Set `action_id` (**Required**)
246 ///
247 /// An identifier for the action triggered when a menu option is selected.
248 /// You can use this when you receive an interaction payload to [identify the source of the action 🔗].
249 /// Should be unique among all other `action_id`s used elsewhere by your app.
250 /// Maximum length for this field is 255 characters.
251 ///
252 /// [identify the source of the action 🔗]: https://api.slack.comhttps://api.slack.com/interactivity/handling#payloads
253 pub fn action_id(
254 mut self,
255 text: impl Into<Cow<'a, str>>)
256 -> ExternalBuilder<'a, M, P, Set<method::action_id>, O> {
257 self.action_id = Some(text.into());
258 self.cast_state()
259 }
260
261 /// Set `confirm` (Optional)
262 ///
263 /// A [confirm object 🔗] that defines an
264 /// optional confirmation dialog that appears after
265 /// a menu item is selected.
266 ///
267 /// [confirm object 🔗]: https://api.slack.comhttps://api.slack.com/reference/block-kit/composition-objects#confirm
268 pub fn confirm(mut self, confirm: Confirm) -> Self {
269 self.confirm = Some(confirm);
270 self
271 }
272
273 /// Set `min_query_length` (Optional)
274 ///
275 /// When the typeahead field is used, a request will be sent on every character change.
276 ///
277 /// If you prefer fewer requests or more fully ideated queries,
278 /// use the `min_query_length` attribute to tell Slack the fewest number
279 /// of typed characters required before dispatch.
280 ///
281 /// The default value is `3`.
282 pub fn min_query_length(mut self, min: u64) -> Self {
283 self.min_query_length = Some(min);
284 self
285 }
286 }
287
288 impl<'a, P, A, O> ExternalBuilder<'a, select_kind::Multi, P, A, O> {
289 /// Set `max_selected_items` (Optional)
290 ///
291 /// Specifies the maximum number of items that can be selected in the menu.
292 ///
293 /// Minimum number is 1.
294 pub fn max_selected_items(mut self, max: u32) -> Self {
295 self.max_selected_items = Some(max);
296 self
297 }
298 }
299 impl<'a, P, A>
300 ExternalBuilder<'a,
301 select_kind::Multi,
302 P,
303 A,
304 OptionalMethodNotCalled<method::initial_option>>
305 {
306 /// Set `initial_options` (Optional)
307 ///
308 /// An array of [option objects 🔗] that exactly match one or more of the options loaded from the external data source.
309 ///
310 /// These options will be selected when the menu initially loads.
311 ///
312 /// [option objects 🔗]: https://api.slack.com/reference/messaging/composition-objects#option
313 pub fn initial_options<I>(
314 mut self,
315 options: I)
316 -> ExternalBuilder<'a,
317 select_kind::Multi,
318 P,
319 A,
320 Set<method::initial_option>>
321 where I: IntoIterator<Item = Opt<'a>>
322 {
323 self.initial_options = Some(options.into_iter()
324 .map(|o| OptOrOptGroup::<'a>::Opt(o))
325 .collect());
326 self.cast_state()
327 }
328
329 /// Set `initial_options` (Optional)
330 ///
331 /// An array of [option objects 🔗] that exactly match one or more of the options loaded from the external data source.
332 ///
333 /// These options will be selected when the menu initially loads.
334 ///
335 /// [option objects 🔗]: https://api.slack.com/reference/messaging/composition-objects#option
336 pub fn initial_option_groups<I>(
337 mut self,
338 option_groups: I)
339 -> ExternalBuilder<'a,
340 select_kind::Multi,
341 P,
342 A,
343 Set<method::initial_option>>
344 where I: IntoIterator<Item = OptGroup<'a>>
345 {
346 self.initial_options =
347 Some(option_groups.into_iter()
348 .map(|o| OptOrOptGroup::<'a>::OptGroup(o))
349 .collect());
350 self.cast_state()
351 }
352 }
353
354 impl<'a, P, A>
355 ExternalBuilder<'a,
356 select_kind::Single,
357 P,
358 A,
359 OptionalMethodNotCalled<method::initial_option>>
360 {
361 /// Set `initial_option` (Optional)
362 ///
363 /// A single option that exactly matches one of the options
364 /// loaded from the external data source.
365 ///
366 /// This option will be selected when the menu initially loads.
367 pub fn initial_option(
368 mut self,
369 option: Opt<'a>)
370 -> ExternalBuilder<'a,
371 select_kind::Single,
372 P,
373 A,
374 Set<method::initial_option>> {
375 self.initial_option = Some(OptOrOptGroup::<'a>::Opt(option));
376 self.cast_state()
377 }
378
379 /// Set `initial_option` (Optional)
380 ///
381 /// A single option group that exactly matches one of the option groups
382 /// loaded from the external data source.
383 ///
384 /// This option will be selected when the menu initially loads.
385 pub fn initial_option_group(
386 mut self,
387 option_group: OptGroup<'a>)
388 -> ExternalBuilder<'a,
389 select_kind::Single,
390 P,
391 A,
392 Set<method::initial_option>> {
393 self.initial_option = Some(OptOrOptGroup::<'a>::OptGroup(option_group));
394 self.cast_state()
395 }
396 }
397
398 impl<'a, O>
399 ExternalBuilder<'a,
400 select_kind::Single,
401 Set<method::placeholder>,
402 Set<method::action_id>,
403 O>
404 {
405 /// All done building, now give me a darn select element!
406 ///
407 /// > `no method name 'build' found for struct 'select::static_::build::ExternalBuilder<...>'`?
408 /// Make sure all required setter methods have been called. See docs for `ExternalBuilder`.
409 ///
410 /// ```compile_fail
411 /// use slack_blocks::elems::select::External;
412 ///
413 /// let sel = External::builder().build(); // Won't compile!
414 /// ```
415 ///
416 /// ```
417 /// use slack_blocks::elems::select::External;
418 ///
419 /// let sel = External::builder().placeholder("foo")
420 /// .action_id("bar")
421 /// .build();
422 /// ```
423 pub fn build(self) -> External<'a> {
424 External { placeholder: self.placeholder.unwrap(),
425 action_id: self.action_id.unwrap(),
426 confirm: self.confirm,
427 initial_option: self.initial_option,
428 min_query_length: self.min_query_length }
429 }
430 }
431
432 impl<'a, O>
433 ExternalBuilder<'a,
434 select_kind::Multi,
435 Set<method::placeholder>,
436 Set<method::action_id>,
437 O>
438 {
439 /// All done building, now give me a darn select element!
440 ///
441 /// > `no method name 'build' found for struct 'select::static_::build::ExternalBuilder<...>'`?
442 /// Make sure all required setter methods have been called. See docs for `ExternalBuilder`.
443 ///
444 /// ```compile_fail
445 /// use slack_blocks::elems::select::External;
446 ///
447 /// let sel = External::builder().build(); // Won't compile!
448 /// ```
449 ///
450 /// ```
451 /// use slack_blocks::elems::select::External;
452 ///
453 /// let sel = External::builder().placeholder("foo")
454 /// .action_id("bar")
455 /// .build();
456 /// ```
457 pub fn build(self) -> multi::External<'a> {
458 multi::External { placeholder: self.placeholder.unwrap(),
459 action_id: self.action_id.unwrap(),
460 confirm: self.confirm,
461 initial_options: self.initial_options,
462 min_query_length: self.min_query_length,
463 max_selected_items: self.max_selected_items }
464 }
465 }
466}