slack_messaging/blocks/table/mod.rs
1use crate::validators::*;
2
3use serde::Serialize;
4use slack_messaging_derive::Builder;
5
6/// Builders for table elements.
7pub mod builders;
8
9mod cell;
10mod row;
11mod setting;
12
13pub use cell::{TableCell, RawText};
14pub use row::TableRow;
15pub use setting::{ColumnAlignment, ColumnSetting};
16
17/// [Table block](https://docs.slack.dev/reference/block-kit/blocks/table-block) representation.
18///
19/// # Fields and Validations
20///
21/// For more details, see the [official
22/// documentation](https://docs.slack.dev/reference/block-kit/blocks/table-block).
23///
24/// | Field | Type | Required | Validation |
25/// |-------|------|----------|------------|
26/// | block_id | String | No | Maximum 255 characters |
27/// | rows | Vec<[TableRow]> | Yes | Maximum 100 items |
28/// | column_settings | Vec<[ColumnSetting]> | No | Maximum 20 items |
29///
30/// # Example
31///
32/// ```
33/// use slack_messaging::blocks::rich_text::prelude::*;
34/// use slack_messaging::blocks::table::*;
35/// # use std::error::Error;
36///
37/// # fn try_main() -> Result<(), Box<dyn Error>> {
38/// let table = Table::builder()
39/// .block_id("table_1")
40/// .row(
41/// TableRow::builder()
42/// .cell("Header A")
43/// .cell("Header B")
44/// .build()?
45/// )
46/// .row(
47/// TableRow::builder()
48/// .cell("Data 1A")
49/// .cell(
50/// RichText::builder()
51/// .element(
52/// RichTextSection::builder()
53/// .element(
54/// RichTextElementLink::builder()
55/// .text("Data 1B")
56/// .url("https://slack.com")
57/// .build()?
58/// )
59/// .build()?
60/// )
61/// .build()?
62/// )
63/// .build()?
64/// )
65/// .column_setting(
66/// ColumnSetting::builder()
67/// .is_wrapped(true)
68/// .build()?
69/// )
70/// .column_setting(
71/// ColumnSetting::builder()
72/// .align(ColumnAlignment::Right)
73/// .build()?
74/// )
75/// .build()?;
76///
77/// let expected = serde_json::json!({
78/// "type": "table",
79/// "block_id": "table_1",
80/// "column_settings": [
81/// {
82/// "is_wrapped": true
83/// },
84/// {
85/// "align": "right"
86/// }
87/// ],
88/// "rows": [
89/// [
90/// {
91/// "type": "raw_text",
92/// "text": "Header A"
93/// },
94/// {
95/// "type": "raw_text",
96/// "text": "Header B"
97/// }
98/// ],
99/// [
100/// {
101/// "type": "raw_text",
102/// "text": "Data 1A"
103/// },
104/// {
105/// "type": "rich_text",
106/// "elements": [
107/// {
108/// "type": "rich_text_section",
109/// "elements": [
110/// {
111/// "type": "link",
112/// "text": "Data 1B",
113/// "url": "https://slack.com"
114/// }
115/// ]
116/// }
117/// ]
118/// }
119/// ]
120/// ]
121/// });
122///
123/// let json = serde_json::to_value(table).unwrap();
124///
125/// assert_eq!(json, expected);
126/// # Ok(())
127/// # }
128/// # fn main() {
129/// # try_main().unwrap()
130/// # }
131/// ```
132#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
133#[serde(tag = "type", rename = "table")]
134pub struct Table {
135 #[serde(skip_serializing_if = "Option::is_none")]
136 #[builder(validate("text::max_255"))]
137 pub(crate) block_id: Option<String>,
138
139 #[builder(push_item = "row", validate("required", "list::max_item_100"))]
140 pub(crate) rows: Option<Vec<TableRow>>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
143 #[builder(push_item = "column_setting", validate("list::max_item_20"))]
144 pub(crate) column_settings: Option<Vec<ColumnSetting>>,
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::errors::*;
151
152 #[test]
153 fn it_implements_builder() {
154 let expected = Table {
155 block_id: Some("table_0".into()),
156 rows: Some(vec![
157 row(vec!["foo", "bar", "baz"]),
158 row(vec!["000", "001", "002"]),
159 ]),
160 column_settings: Some(vec![column_setting(true)]),
161 };
162
163 let val = Table::builder()
164 .set_block_id(Some("table_0"))
165 .set_rows(Some(vec![
166 row(vec!["foo", "bar", "baz"]),
167 row(vec!["000", "001", "002"]),
168 ]))
169 .set_column_settings(Some(vec![column_setting(true)]))
170 .build()
171 .unwrap();
172
173 assert_eq!(val, expected);
174
175 let val = Table::builder()
176 .block_id("table_0")
177 .rows(vec![
178 row(vec!["foo", "bar", "baz"]),
179 row(vec!["000", "001", "002"]),
180 ])
181 .column_settings(vec![column_setting(true)])
182 .build()
183 .unwrap();
184
185 assert_eq!(val, expected);
186 }
187
188 #[test]
189 fn it_implements_push_item_method_for_rows_field() {
190 let expected = Table {
191 block_id: None,
192 rows: Some(vec![
193 row(vec!["foo", "bar", "baz"]),
194 row(vec!["000", "001", "002"]),
195 ]),
196 column_settings: None,
197 };
198
199 let val = Table::builder()
200 .row(row(vec!["foo", "bar", "baz"]))
201 .row(row(vec!["000", "001", "002"]))
202 .build()
203 .unwrap();
204
205 assert_eq!(val, expected);
206 }
207
208 #[test]
209 fn it_implements_push_item_method_for_column_settings_field() {
210 let expected = Table {
211 block_id: None,
212 rows: Some(vec![row(vec!["foo", "bar"])]),
213 column_settings: Some(vec![column_setting(true), column_setting(false)]),
214 };
215
216 let val = Table::builder()
217 .row(row(vec!["foo", "bar"]))
218 .column_setting(column_setting(true))
219 .column_setting(column_setting(false))
220 .build()
221 .unwrap();
222
223 assert_eq!(val, expected);
224 }
225
226 #[test]
227 fn it_requires_block_id_less_than_255_characters_long() {
228 let err = Table::builder()
229 .row(row(vec!["foo", "bar"]))
230 .block_id("a".repeat(256))
231 .build()
232 .unwrap_err();
233 assert_eq!(err.object(), "Table");
234
235 let errors = err.field("block_id");
236 assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
237 }
238
239 #[test]
240 fn it_requires_rows_field() {
241 let err = Table::builder().build().unwrap_err();
242 assert_eq!(err.object(), "Table");
243
244 let errors = err.field("rows");
245 assert!(errors.includes(ValidationErrorKind::Required));
246 }
247
248 #[test]
249 fn it_requires_rows_size_less_than_100() {
250 let rows: Vec<TableRow> = (0..101).map(|_| row(vec!["foo", "bar"])).collect();
251 let err = Table::builder().rows(rows).build().unwrap_err();
252 assert_eq!(err.object(), "Table");
253
254 let errors = err.field("rows");
255 assert!(errors.includes(ValidationErrorKind::MaxArraySize(100)));
256 }
257
258 #[test]
259 fn it_requires_column_settings_size_less_than_20() {
260 let column_settings: Vec<ColumnSetting> = (0..21).map(|_| column_setting(true)).collect();
261 let err = Table::builder()
262 .row(row(vec!["foo", "bar"]))
263 .column_settings(column_settings)
264 .build()
265 .unwrap_err();
266 assert_eq!(err.object(), "Table");
267
268 let errors = err.field("column_settings");
269 assert!(errors.includes(ValidationErrorKind::MaxArraySize(20)));
270 }
271
272 fn row<T: Into<TableCell>>(cells: Vec<T>) -> TableRow {
273 TableRow::from_iter(cells)
274 }
275
276 fn column_setting(is_wrapped: bool) -> ColumnSetting {
277 ColumnSetting {
278 align: None,
279 is_wrapped: Some(is_wrapped),
280 }
281 }
282}