Skip to main content

slack_messaging/blocks/rich_text/
mod.rs

1use crate::validators::*;
2
3use paste::paste;
4use serde::Serialize;
5use slack_messaging_derive::Builder;
6
7/// Builder objects for [rich text sub-elements](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block/#usage-info).
8pub mod builders;
9
10/// Objects representing [rich text element types](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block/#element-types).
11pub mod types;
12
13mod list;
14mod preformatted;
15mod quote;
16mod section;
17
18pub use list::{ListStyle, RichTextList};
19pub use preformatted::RichTextPreformatted;
20pub use quote::RichTextQuote;
21pub use section::RichTextSection;
22
23/// [Rich text sub elements](https://api.slack.com/reference/block-kit/blocks#element-types)
24/// representation.
25#[derive(Debug, Clone, Serialize, PartialEq)]
26#[serde(untagged)]
27pub enum RichTextSubElement {
28    /// [Rich text section element](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block#rich_text_section)
29    Section(Box<RichTextSection>),
30
31    /// [Rich text list element](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block#rich_text_list)
32    List(Box<RichTextList>),
33
34    /// [Rich text preformatted element](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block#rich_text_preformatted)
35    Preformatted(Box<RichTextPreformatted>),
36
37    /// [Rich text quote element](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block#rich_text_quote)
38    Quote(Box<RichTextQuote>),
39}
40
41macro_rules! impl_sub_element {
42    ($($var:tt,)*) => {
43        paste! {
44            $(
45                impl From<[<RichText $var>]> for RichTextSubElement {
46                    fn from(value: [<RichText $var>]) -> Self {
47                        Self::$var(Box::new(value))
48                    }
49                }
50            )*
51        }
52    };
53}
54
55impl_sub_element! {
56    Section,
57    List,
58    Preformatted,
59    Quote,
60}
61
62/// [Rich text block](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block) representation.
63///
64/// # Fields and Validations
65///
66/// For more details, see the [official
67/// documentation](https://docs.slack.dev/reference/block-kit/blocks/rich-text-block).
68///
69/// | Field | Type | Required | Validation |
70/// |-------|------|----------|------------|
71/// | elements | Vec<[RichTextSubElement]> | Yes | N/A |
72/// | block_id | String | No | Max length 255 characters |
73///
74/// # Example
75///
76/// ```
77/// use slack_messaging::blocks::RichText;
78/// use slack_messaging::blocks::rich_text::RichTextSection;
79/// use slack_messaging::blocks::rich_text::types::{RichTextElementText, RichTextStyle};
80/// # use std::error::Error;
81///
82/// # fn try_main() -> Result<(), Box<dyn Error>> {
83/// let rich_text = RichText::builder()
84///     .block_id("rich-text-block-0")
85///     .element(
86///         RichTextSection::builder()
87///             .element(
88///                 RichTextElementText::builder()
89///                     .text("Hello there, ")
90///                     .build()?
91///             )
92///             .element(
93///                 RichTextElementText::builder()
94///                     .text("I am a bold rich text block!")
95///                     .style(
96///                         RichTextStyle::builder()
97///                             .bold(true)
98///                             .build()?
99///                     )
100///                     .build()?
101///             )
102///             .build()?
103///     )
104///     .build()?;
105///
106/// let expected = serde_json::json!({
107///     "type": "rich_text",
108///     "block_id": "rich-text-block-0",
109///     "elements": [
110///         {
111///             "type": "rich_text_section",
112///             "elements": [
113///                 {
114///                     "type": "text",
115///                     "text": "Hello there, "
116///                 },
117///                 {
118///                     "type": "text",
119///                     "text": "I am a bold rich text block!",
120///                     "style": {
121///                         "bold": true
122///                     }
123///                 }
124///             ]
125///         }
126///     ]
127/// });
128///
129/// let json = serde_json::to_value(rich_text).unwrap();
130///
131/// assert_eq!(json, expected);
132///
133/// // If your object has any validation errors, the build method returns Result::Err
134/// let rich_text = RichText::builder().build();
135/// assert!(rich_text.is_err());
136/// #     Ok(())
137/// # }
138/// # fn main() {
139/// #     try_main().unwrap()
140/// # }
141/// ```
142#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
143#[serde(tag = "type", rename = "rich_text")]
144pub struct RichText {
145    #[builder(push_item = "element", validate("required"))]
146    pub(crate) elements: Option<Vec<RichTextSubElement>>,
147
148    #[serde(skip_serializing_if = "Option::is_none")]
149    #[builder(validate("text::max_255"))]
150    pub(crate) block_id: Option<String>,
151}
152
153#[cfg(test)]
154mod tests {
155    use super::test_helpers::*;
156    use super::types::test_helpers::*;
157    use super::*;
158    use crate::errors::*;
159
160    #[test]
161    fn it_implements_builder() {
162        let expected = RichText {
163            block_id: Some("rich_text_0".into()),
164            elements: Some(vec![section(vec![el_text("foo"), el_emoji("var")]).into()]),
165        };
166
167        let val = RichText::builder()
168            .set_block_id(Some("rich_text_0"))
169            .set_elements(Some(vec![
170                section(vec![el_text("foo"), el_emoji("var")]).into(),
171            ]))
172            .build()
173            .unwrap();
174
175        assert_eq!(val, expected);
176
177        let val = RichText::builder()
178            .block_id("rich_text_0")
179            .elements(vec![section(vec![el_text("foo"), el_emoji("var")]).into()])
180            .build()
181            .unwrap();
182
183        assert_eq!(val, expected);
184    }
185
186    #[test]
187    fn it_implements_push_item_method() {
188        let expected = RichText {
189            block_id: None,
190            elements: Some(vec![
191                section(vec![el_text("foo"), el_emoji("var")]).into(),
192                section(vec![el_text("baz")]).into(),
193            ]),
194        };
195
196        let val = RichText::builder()
197            .element(section(vec![el_text("foo"), el_emoji("var")]))
198            .element(section(vec![el_text("baz")]))
199            .build()
200            .unwrap();
201
202        assert_eq!(val, expected);
203    }
204
205    #[test]
206    fn it_requires_elements_field() {
207        let err = RichText::builder().build().unwrap_err();
208        assert_eq!(err.object(), "RichText");
209
210        let errors = err.field("elements");
211        assert!(errors.includes(ValidationErrorKind::Required));
212    }
213
214    #[test]
215    fn it_requires_block_id_less_than_255_characters_long() {
216        let err = RichText::builder()
217            .block_id("a".repeat(256))
218            .element(section(vec![el_text("baz")]))
219            .build()
220            .unwrap_err();
221        assert_eq!(err.object(), "RichText");
222
223        let errors = err.field("block_id");
224        assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
225    }
226}
227
228#[cfg(test)]
229pub mod test_helpers {
230    use super::types::RichTextElementType;
231    use super::*;
232
233    pub fn section(elements: Vec<RichTextElementType>) -> RichTextSection {
234        RichTextSection {
235            elements: Some(elements),
236        }
237    }
238}