Skip to main content

slack_messaging/blocks/data_visualization/charts/
data_series.rs

1use super::DataPoint;
2
3use crate::validators::*;
4
5use serde::Serialize;
6use slack_messaging_derive::Builder;
7
8/// [Data Series](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block#data-series)
9/// representation.
10///
11/// # Fields and Validations
12///
13/// For more details, see the [official
14/// documentation](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block#data-series).
15///
16/// | Field | Type | Required | Validation |
17/// |-------|------|----------|------------|
18/// | name | String | Yes | Max length 20 characters |
19/// | data | Vec<[DataPoint]> | Yes | Min 1, Max 20 items |
20///
21/// # Example
22///
23/// ```
24/// use slack_messaging::blocks::data_visualization::charts::{data_points, DataSeries};
25/// # use std::error::Error;
26///
27/// # fn try_main() -> Result<(), Box<dyn Error>> {
28/// let series = DataSeries::builder()
29///     .name("Sales")
30///     .data(data_points(vec![
31///         ("Mon", 200),
32///         ("Tue", 120),
33///         ("Wed", 180),
34///         ("Thu", 50),
35///         ("Fri", 300)
36///     ])?)
37///     .build()?;
38///
39/// let expected = serde_json::json!({
40///     "name": "Sales",
41///     "data": [
42///         { "label": "Mon", "value": 200 },
43///         { "label": "Tue", "value": 120 },
44///         { "label": "Wed", "value": 180 },
45///         { "label": "Thu", "value": 50 },
46///         { "label": "Fri", "value": 300 }
47///     ]
48/// });
49///
50/// let json = serde_json::to_value(series).unwrap();
51///
52/// assert_eq!(json, expected);
53/// #     Ok(())
54/// # }
55/// # fn main() {
56/// #     try_main().unwrap()
57/// # }
58/// ```
59#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
60pub struct DataSeries {
61    #[builder(validate("required", "text::max_20"))]
62    pub(crate) name: Option<String>,
63
64    #[builder(
65        push_item = "data_point",
66        validate("required", "list::not_empty", "list::max_item_20")
67    )]
68    pub(crate) data: Option<Vec<DataPoint>>,
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::errors::*;
75
76    #[test]
77    fn it_implements_builder() {
78        let expected = DataSeries {
79            name: Some("Sales".to_string()),
80            data: Some(vec![
81                point("Mon", 200),
82                point("Tue", 120),
83                point("Wed", 180),
84                point("Thu", 50),
85                point("Fri", 300),
86            ]),
87        };
88
89        let series = DataSeries::builder()
90            .set_name(Some("Sales"))
91            .set_data(Some(vec![
92                point("Mon", 200),
93                point("Tue", 120),
94                point("Wed", 180),
95                point("Thu", 50),
96                point("Fri", 300),
97            ]))
98            .build()
99            .unwrap();
100
101        assert_eq!(series, expected);
102
103        let series = DataSeries::builder()
104            .name("Sales")
105            .data(vec![
106                point("Mon", 200),
107                point("Tue", 120),
108                point("Wed", 180),
109                point("Thu", 50),
110                point("Fri", 300),
111            ])
112            .build()
113            .unwrap();
114
115        assert_eq!(series, expected);
116    }
117
118    #[test]
119    fn it_implements_push_item_method() {
120        let expected = DataSeries {
121            name: Some("Sales".to_string()),
122            data: Some(vec![
123                point("Mon", 200),
124                point("Tue", 120),
125            ]),
126        };
127
128        let series = DataSeries::builder()
129            .name("Sales")
130            .data_point(point("Mon", 200))
131            .data_point(point("Tue", 120))
132            .build()
133            .unwrap();
134
135        assert_eq!(series, expected);
136    }
137
138    #[test]
139    fn it_requires_name_field() {
140        let err = DataSeries::builder()
141            .data(vec![point("Mon", 200)])
142            .build()
143            .unwrap_err();
144        assert_eq!(err.object(), "DataSeries");
145
146        let errors = err.field("name");
147        assert!(errors.includes(ValidationErrorKind::Required));
148    }
149
150    #[test]
151    fn it_requires_name_field_less_than_20_characters_long() {
152        let err = DataSeries::builder()
153            .name("a".repeat(21))
154            .data(vec![point("Mon", 200)])
155            .build()
156            .unwrap_err();
157        assert_eq!(err.object(), "DataSeries");
158
159        let errors = err.field("name");
160        assert!(errors.includes(ValidationErrorKind::MaxTextLength(20)));
161    }
162
163    #[test]
164    fn it_requires_data_field() {
165        let err = DataSeries::builder()
166            .name("Sales")
167            .build()
168            .unwrap_err();
169        assert_eq!(err.object(), "DataSeries");
170
171        let errors = err.field("data");
172        assert!(errors.includes(ValidationErrorKind::Required));
173    }
174
175    #[test]
176    fn it_requires_data_field_with_min_1_item() {
177        let err = DataSeries::builder()
178            .name("Sales")
179            .data(vec![] as Vec<DataPoint>)
180            .build()
181            .unwrap_err();
182        assert_eq!(err.object(), "DataSeries");
183
184        let errors = err.field("data");
185        assert!(errors.includes(ValidationErrorKind::EmptyArray));
186    }
187
188    #[test]
189    fn it_requires_data_field_with_max_20_items() {
190        let err = DataSeries::builder()
191            .name("Sales")
192            .data(vec![point("Mon", 200); 21])
193            .build()
194            .unwrap_err();
195        assert_eq!(err.object(), "DataSeries");
196
197        let errors = err.field("data");
198        assert!(errors.includes(ValidationErrorKind::MaxArraySize(20)));
199    }
200
201    fn point(label: &str, value: i32) -> DataPoint {
202        (label, value).try_into().unwrap()
203    }
204}