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