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