Skip to main content

slack_messaging/blocks/data_visualization/charts/
bar_chart.rs

1use super::{unique_series_names, AxisConfig, DataSeries, ValidateXYChart};
2
3use crate::errors::ValidationErrorKind;
4use crate::validators::*;
5
6use serde::Serialize;
7use slack_messaging_derive::Builder;
8
9/// [Bar chart block](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block#bar)
10/// representation.
11///
12/// # Fields and Validations
13///
14/// For more details, see the [official
15/// documentation](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block#bar).
16///
17/// | Field | Type | Required | Validation |
18/// |-------|------|----------|------------|
19/// | series | Vec<[DataSeries]> | Yes | Min 1, Max 6 items, Unique names, Labels must match axis categories |
20/// | axis_config | [AxisConfig] | Yes | N/A |
21///
22/// # Example
23///
24/// ```
25/// use slack_messaging::blocks::data_visualization::charts::{data_points, DataSeries, BarChart,
26/// AxisConfig};
27/// # use std::error::Error;
28///
29/// # fn try_main() -> Result<(), Box<dyn Error>> {
30/// let bar_chart = BarChart::builder()
31///     .series(vec![
32///         DataSeries::builder()
33///             .name("Pie")
34///             .data(data_points(vec![
35///                 ("Strawberry Rhubarb", 85),
36///                 ("Pumpkin", 70),
37///                 ("Lemon Meringue", 72),
38///                 ("Blueberry", 90),
39///                 ("Key Lime", 56),
40///             ])?)
41///             .build()?
42///     ])
43///     .axis_config(
44///         AxisConfig::builder()
45///             .categories(vec![
46///                 "Strawberry Rhubarb",
47///                 "Pumpkin",
48///                 "Lemon Meringue",
49///                 "Blueberry",
50///                 "Key Lime",
51///             ])
52///             .x_label("Pies")
53///             .y_label("Percentage of Tastiness")
54///             .build()?
55///     )
56///     .build()?;
57///
58/// let expected = serde_json::json!({
59///     "type": "bar",
60///     "series": [
61///         {
62///             "name": "Pie",
63///             "data": [
64///                 { "label": "Strawberry Rhubarb", "value": 85 },
65///                 { "label": "Pumpkin", "value": 70 },
66///                 { "label": "Lemon Meringue", "value": 72 },
67///                 { "label": "Blueberry", "value": 90 },
68///                 { "label": "Key Lime", "value": 56 }
69///             ]
70///         }
71///     ],
72///     "axis_config": {
73///         "categories": [
74///             "Strawberry Rhubarb",
75///             "Pumpkin",
76///             "Lemon Meringue",
77///             "Blueberry",
78///             "Key Lime"
79///         ],
80///         "x_label": "Pies",
81///         "y_label": "Percentage of Tastiness"
82///     }
83/// });
84///
85/// let json = serde_json::to_value(bar_chart).unwrap();
86///
87/// assert_eq!(json, expected);
88/// #     Ok(())
89/// # }
90/// # fn main() {
91/// #     try_main().unwrap()
92/// # }
93/// ```
94#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
95#[builder(validate = "validate")]
96#[serde(tag = "type", rename = "bar")]
97pub struct BarChart {
98    #[builder(
99        push_item = "push_series",
100        validate("required", "list::not_empty", "list::max_item_6", "unique_series_names")
101    )]
102    pub(crate) series: Option<Vec<DataSeries>>,
103
104    #[builder(validate("required"))]
105    pub(crate) axis_config: Option<AxisConfig>,
106}
107
108impl ValidateXYChart for BarChart {
109    fn series(&self) -> Option<&[DataSeries]> {
110        self.series.as_deref()
111    }
112
113    fn axis_config(&self) -> Option<&AxisConfig> {
114        self.axis_config.as_ref()
115    }
116}
117
118fn validate(val: &BarChart) -> Vec<ValidationErrorKind> {
119    val.validate()
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use super::super::data_points;
126    use crate::errors::*;
127
128    #[test]
129    fn it_implements_builder() {
130        let expected = BarChart {
131            series: Some(vec![data_series("Pie")]),
132            axis_config: Some(axis_config()),
133        };
134
135        let val = BarChart::builder()
136            .set_series(Some(vec![data_series("Pie")]))
137            .set_axis_config(Some(axis_config()))
138            .build()
139            .unwrap();
140
141        assert_eq!(val, expected);
142
143        let val = BarChart::builder()
144            .series(vec![data_series("Pie")])
145            .axis_config(axis_config())
146            .build()
147            .unwrap();
148
149        assert_eq!(val, expected);
150    }
151
152    #[test]
153    fn it_implements_push_item_method() {
154        let expected = BarChart {
155            series: Some(vec![data_series("Pie")]),
156            axis_config: Some(axis_config()),
157        };
158
159        let val = BarChart::builder()
160            .push_series(data_series("Pie"))
161            .axis_config(axis_config())
162            .build()
163            .unwrap();
164
165        assert_eq!(val, expected);
166    }
167
168    #[test]
169    fn it_requires_series_field() {
170        let err = BarChart::builder()
171            .axis_config(axis_config())
172            .build()
173            .unwrap_err();
174        assert_eq!(err.object(), "BarChart");
175
176        let errors = err.field("series");
177        assert!(errors.includes(ValidationErrorKind::Required));
178    }
179
180    #[test]
181    fn it_requires_series_field_to_have_at_least_one_item() {
182        let err = BarChart::builder()
183            .series(vec![] as Vec<DataSeries>)
184            .axis_config(axis_config())
185            .build()
186            .unwrap_err();
187        assert_eq!(err.object(), "BarChart");
188
189        let errors = err.field("series");
190        assert!(errors.includes(ValidationErrorKind::EmptyArray));
191    }
192
193    #[test]
194    fn it_requires_series_field_to_have_no_more_than_six_items() {
195        let err = BarChart::builder()
196            .series(vec![
197                data_series("Series 1"),
198                data_series("Series 2"),
199                data_series("Series 3"),
200                data_series("Series 4"),
201                data_series("Series 5"),
202                data_series("Series 6"),
203                data_series("Series 7"),
204            ])
205            .axis_config(axis_config())
206            .build()
207            .unwrap_err();
208        assert_eq!(err.object(), "BarChart");
209
210        let errors = err.field("series");
211        assert!(errors.includes(ValidationErrorKind::MaxArraySize(6)));
212    }
213
214    #[test]
215    fn it_requires_axis_config_field() {
216        let err = BarChart::builder()
217            .series(vec![data_series("Pie")])
218            .build()
219            .unwrap_err();
220        assert_eq!(err.object(), "BarChart");
221
222        let errors = err.field("axis_config");
223        assert!(errors.includes(ValidationErrorKind::Required));
224    }
225
226    #[test]
227    fn it_requires_series_names_to_be_unique() {
228        let err = BarChart::builder()
229            .series(vec![
230                data_series("Sales"),
231                data_series("Revenue"),
232                data_series("Sales"),
233            ])
234            .axis_config(axis_config())
235            .build()
236            .unwrap_err();
237        assert_eq!(err.object(), "BarChart");
238
239        let errors = err.field("series");
240        assert!(errors.includes(ValidationErrorKind::UniqueSeriesName));
241    }
242
243    #[test]
244    fn it_requires_series_labels_to_match_axis_categories() {
245        let err = BarChart::builder()
246            .series(vec![
247                data_series("Pie"),
248                DataSeries::builder()
249                    .name("Cake")
250                    .data(data_points(vec![
251                        ("Chocolate", 90),
252                        ("Vanilla", 80),
253                    ]).unwrap())
254                    .build()
255                    .unwrap(),
256            ])
257            .axis_config(axis_config())
258            .build()
259            .unwrap_err();
260        assert_eq!(err.object(), "BarChart");
261
262        let errors = err.across_fields();
263        assert!(errors.includes(ValidationErrorKind::DataPointLabelMatching));
264    }
265
266    fn data_series(name: &str) -> DataSeries {
267        DataSeries::builder()
268            .name(name)
269            .data(data_points(vec![
270                ("Strawberry Rhubarb", 85),
271                ("Pumpkin", 70),
272            ]).unwrap())
273            .build()
274            .unwrap()
275    }
276
277    fn axis_config() -> AxisConfig {
278        AxisConfig::builder()
279            .categories(vec![
280                "Strawberry Rhubarb",
281                "Pumpkin",
282            ])
283            .x_label("Pies")
284            .y_label("Percentage of Tastiness")
285            .build()
286            .unwrap()
287    }
288}