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}