slack_blocks/blocks/
mod.rs

1//! # Layout Blocks
2//!
3//! Blocks are a series of components that can be combined
4//! to create visually rich and compellingly interactive messages.
5//!
6//! Read our guide to [building block layouts 🔗] to learn where and how to use each of these components.
7//!
8//! You can include up to 50 blocks in each message, and 100 blocks in modals or home tabs.
9//!
10//! [building block layouts 🔗]: https://api.slack.com/block-kit/building
11
12use std::fmt;
13
14use serde::{Deserialize, Serialize};
15
16use crate::convert;
17
18pub mod actions;
19#[doc(inline)]
20pub use actions::Actions;
21
22pub mod context;
23#[doc(inline)]
24pub use context::Context;
25
26pub mod file;
27#[doc(inline)]
28pub use file::File;
29
30pub mod image;
31#[doc(inline)]
32pub use image::Image;
33
34pub mod input;
35#[doc(inline)]
36pub use input::Input;
37
38pub mod section;
39#[doc(inline)]
40pub use section::Section;
41
42pub mod header;
43#[doc(inline)]
44pub use header::Header;
45
46/// # Layout Blocks
47///
48/// Blocks are a series of components that can be combined
49/// to create visually rich and compellingly interactive messages.
50///
51/// Read our guide to [building block layouts 🔗] to learn where and how to use each of these components.
52///
53/// You can include up to 50 blocks in each message, and 100 blocks in modals or home tabs.
54///
55/// [building block layouts 🔗]: https://api.slack.com/block-kit/building
56#[derive(Hash, PartialEq, Serialize, Deserialize, Debug)]
57#[serde(tag = "type", rename_all = "snake_case")]
58pub enum Block<'a> {
59  /// # Section Block
60  Section(Section<'a>),
61
62  /// # Divider Block
63  ///
64  /// _[slack api docs 🔗][divider_docs]_
65  ///
66  /// A content divider, like an `<hr>`,
67  /// to split up different blocks inside of a message.
68  ///
69  /// The divider block is nice and neat, requiring no fields.
70  ///
71  /// [divider_docs]: https://api.slack.com/reference/block-kit/blocks#divider
72  Divider,
73
74  /// # Image Block
75  Image(Image<'a>),
76
77  /// # Actions Block
78  Actions(Actions<'a>),
79
80  /// # Context Block
81  Context(Context<'a>),
82
83  /// # Input Block
84  Input(Input<'a>),
85
86  /// # Input Block
87  Header(Header<'a>),
88
89  /// # File Block
90  File(File<'a>),
91}
92
93impl fmt::Display for Block<'_> {
94  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95    let kind = match self {
96      | Block::Header { .. } => "Header",
97      | Block::Section { .. } => "Section",
98      | Block::Divider => "Divider",
99      | Block::Image { .. } => "Image",
100      | Block::Actions { .. } => "Actions",
101      | Block::Context { .. } => "Context",
102      | Block::Input { .. } => "Input",
103      | Block::File { .. } => "File",
104    };
105
106    write!(f, "{}", kind)
107  }
108}
109
110impl<'a> Block<'a> {
111  /// Validate that this block agrees with Slack's model requirements.
112  ///
113  /// ```
114  /// use slack_blocks::{blocks, blocks::Image};
115  ///
116  /// let long_string = std::iter::repeat('a').take(2001).collect::<String>();
117  ///
118  /// let img = Image::builder().src("foo.com").alt(long_string).build();
119  ///
120  /// assert!(matches!(img.validate(), Err(_)), "validation should fail!")
121  /// ```
122  #[cfg(feature = "validation")]
123  #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
124  pub fn validate(&self) -> crate::val_helpr::ValidationResult {
125    use Block::*;
126
127    match self {
128      | Section(contents) => contents.validate(),
129      | Image(contents) => contents.validate(),
130      | Actions(contents) => contents.validate(),
131      | Context(contents) => contents.validate(),
132      | Input(contents) => contents.validate(),
133      | Header(contents) => contents.validate(),
134      | File(contents) => contents.validate(),
135      | Divider => Ok(()),
136    }
137  }
138}
139
140convert!(impl<'a> From<Actions<'a>> for Block<'a> => |a| Block::Actions(a));
141convert!(impl<'a> From<Input<'a>>   for Block<'a> => |a| Block::Input(a));
142convert!(impl<'a> From<Section<'a>> for Block<'a> => |a| Block::Section(a));
143convert!(impl<'a> From<Image<'a>>   for Block<'a> => |a| Block::Image(a));
144convert!(impl<'a> From<Context<'a>> for Block<'a> => |a| Block::Context(a));
145convert!(impl<'a> From<File<'a>>    for Block<'a> => |a| Block::File(a));
146convert!(impl<'a> From<Header<'a>>  for Block<'a> => |a| Block::Header(a));
147
148/// Error yielded when `TryFrom` is called on an unsupported block element.
149#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
150pub struct UnsupportedElement<'a> {
151  context: String,
152  element: crate::elems::BlockElement<'a>,
153}
154
155impl<'a> std::fmt::Display for UnsupportedElement<'a> {
156  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157    write!(f,
158           "(In {}) Block element not supported: {:#?}",
159           self.context, self.element)
160  }
161}
162
163impl<'a> std::error::Error for UnsupportedElement<'a> {
164  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
165    None
166  }
167}
168
169#[cfg(feature = "validation")]
170fn validate_block_id(id: &std::borrow::Cow<str>)
171                     -> crate::val_helpr::ValidatorResult {
172  crate::val_helpr::below_len("block_id", 255, id)
173}