Skip to main content

slack_messaging/blocks/data_visualization/charts/
data_point.rs

1use crate::errors::ValidationErrors;
2use crate::validators::*;
3
4use serde::Serialize;
5use serde_json::Number;
6use slack_messaging_derive::Builder;
7
8/// [Data Point](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block#data-point)
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-point).
15///
16/// | Field | Type | Required | Validation |
17/// |-------|------|----------|------------|
18/// | label | String | Yes | Max length 20 characters |
19/// | value | [Number] | Yes | N/A |
20///
21/// # Example
22///
23/// ```
24/// use slack_messaging::blocks::data_visualization::charts::DataPoint;
25/// # use std::error::Error;
26///
27/// # fn try_main() -> Result<(), Box<dyn Error>> {
28/// let point = DataPoint::builder()
29///     .label("Sales")
30///     .value(150)
31///     .build()?;
32///
33/// let expected = serde_json::json!({
34///     "label": "Sales",
35///     "value": 150
36/// });
37///
38/// let json = serde_json::to_value(point).unwrap();
39///
40/// assert_eq!(json, expected);
41/// #     Ok(())
42/// # }
43/// # fn main() {
44/// #     try_main().unwrap()
45/// # }
46/// ```
47#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
48pub struct DataPoint {
49    #[builder(validate("required", "text::max_20"))]
50    pub(crate) label: Option<String>,
51
52    #[builder(validate("required"))]
53    pub(crate) value: Option<Number>,
54}
55
56impl<S, N> TryFrom<(S, N)> for DataPoint
57where
58    S: Into<String>,
59    N: Into<Number>,
60{
61    type Error = ValidationErrors;
62
63    fn try_from((label, value): (S, N)) -> Result<Self, Self::Error> {
64        DataPoint::builder()
65            .label(label)
66            .value(value)
67            .build()
68    }
69}
70
71/// Helper function to create multiple data points from an iterator of tuples.
72///
73/// # Example
74///
75/// ```
76/// use slack_messaging::blocks::data_visualization::charts::data_points;
77/// # use std::error::Error;
78///
79/// # fn try_main() -> Result<(), Box<dyn Error>> {
80/// let points = data_points(vec![("Sales", 150), ("Revenue", 200)])?;
81///
82/// let expected = serde_json::json!([
83///     { "label": "Sales", "value": 150 },
84///     { "label": "Revenue", "value": 200 }
85/// ]);
86///
87/// let json = serde_json::to_value(points).unwrap();
88///
89/// assert_eq!(json, expected);
90/// #     Ok(())
91/// # }
92/// # fn main() {
93/// #     try_main().unwrap()
94/// # }
95/// ```
96pub fn data_points<Iter, S, N>(iter: Iter) -> Result<Vec<DataPoint>, ValidationErrors>
97where
98    Iter: IntoIterator<Item = (S, N)>,
99    S: Into<String>,
100    N: Into<Number>,
101{
102    iter.into_iter().map(DataPoint::try_from).collect()
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::errors::*;
109
110    #[test]
111    fn it_implements_builder() {
112        let expected = DataPoint {
113            label: Some("Sales".into()),
114            value: Some(Number::from(150)),
115        };
116
117        let val = DataPoint::builder()
118            .set_label(Some("Sales"))
119            .set_value(Some(150))
120            .build()
121            .unwrap();
122
123        assert_eq!(val, expected);
124
125        let val = DataPoint::builder()
126            .label("Sales")
127            .value(150)
128            .build()
129            .unwrap();
130
131        assert_eq!(val, expected);
132    }
133
134    #[test]
135    fn it_implements_try_from() {
136        let expected = DataPoint {
137            label: Some("Revenue".into()),
138            value: Some(Number::from(200)),
139        };
140
141        let val = DataPoint::try_from(("Revenue", 200)).unwrap();
142
143        assert_eq!(val, expected);
144    }
145
146    #[test]
147    fn it_implements_data_points_function() {
148        let expected = vec![
149            DataPoint {
150                label: Some("Sales".into()),
151                value: Some(Number::from(150)),
152            },
153            DataPoint {
154                label: Some("Revenue".into()),
155                value: Some(Number::from(200)),
156            },
157        ];
158
159        let val = data_points(vec![("Sales", 150), ("Revenue", 200)]).unwrap();
160
161        assert_eq!(val, expected);
162    }
163
164    #[test]
165    fn it_requires_label_field() {
166        let err = DataPoint::builder().value(150).build().unwrap_err();
167        assert_eq!(err.object(), "DataPoint");
168
169        let errors = err.field("label");
170        assert!(errors.includes(ValidationErrorKind::Required));
171    }
172
173    #[test]
174    fn it_requires_label_field_less_than_20_characters_long() {
175        let err = DataPoint::builder()
176            .label("a".repeat(21))
177            .value(150)
178            .build()
179            .unwrap_err();
180        assert_eq!(err.object(), "DataPoint");
181
182        let errors = err.field("label");
183        assert!(errors.includes(ValidationErrorKind::MaxTextLength(20)));
184    }
185
186    #[test]
187    fn it_requires_value_field() {
188        let err = DataPoint::builder().label("Sales").build().unwrap_err();
189        assert_eq!(err.object(), "DataPoint");
190
191        let errors = err.field("value");
192        assert!(errors.includes(ValidationErrorKind::Required));
193    }
194}