slack_messaging/blocks/
image.rs

1use crate::composition_objects::{Plain, SlackFile, Text};
2use crate::errors::ValidationErrorKind;
3use crate::validators::*;
4
5use serde::Serialize;
6use slack_messaging_derive::Builder;
7
8/// [Image block](https://docs.slack.dev/reference/block-kit/blocks/image-block)
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/image-block).
15///
16/// | Field | Type | Required | Validation |
17/// |-------|------|----------|------------|
18/// | alt_text | String | Yes | Maximum 2000 characters |
19/// | image_url | String | Conditional* | Maximum 3000 characters |
20/// | title | [Text]<[Plain]> | No | Maximum 2000 characters |
21/// | block_id | String | No | Maximum 255 characters |
22/// | slack_file | [SlackFile] | Conditional* | N/A |
23///
24/// # Validation Across Fields
25///
26/// * Either `image_url` or `slack_file` is required. Both fields cannot be set simultaneously.
27///
28/// # Example
29///
30/// ```
31/// use slack_messaging::plain_text;
32/// use slack_messaging::blocks::Image;
33/// # use std::error::Error;
34///
35/// # fn try_main() -> Result<(), Box<dyn Error>> {
36/// let image = Image::builder()
37///     .block_id("image4")
38///     .title(plain_text!("Please enjoy this photo of a kitten")?)
39///     .image_url("http://placekitten.com/500/500")
40///     .alt_text("An incredibly cute kitten.")
41///     .build()?;
42///
43/// let expected = serde_json::json!({
44///     "type": "image",
45///     "block_id": "image4",
46///     "title": {
47///         "type": "plain_text",
48///         "text": "Please enjoy this photo of a kitten"
49///     },
50///     "image_url": "http://placekitten.com/500/500",
51///     "alt_text": "An incredibly cute kitten."
52/// });
53///
54/// let json = serde_json::to_value(image).unwrap();
55///
56/// assert_eq!(json, expected);
57/// #     Ok(())
58/// # }
59/// # fn main() {
60/// #     try_main().unwrap()
61/// # }
62/// ```
63#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
64#[serde(tag = "type", rename = "image")]
65#[builder(validate = "validate")]
66pub struct Image {
67    #[builder(validate("required", "text::max_2000"))]
68    pub(crate) alt_text: Option<String>,
69
70    #[serde(skip_serializing_if = "Option::is_none")]
71    #[builder(validate("text::max_3000"))]
72    pub(crate) image_url: Option<String>,
73
74    #[serde(skip_serializing_if = "Option::is_none")]
75    #[builder(validate("text_object::max_2000"))]
76    pub(crate) title: Option<Text<Plain>>,
77
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[builder(validate("text::max_255"))]
80    pub(crate) block_id: Option<String>,
81
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub(crate) slack_file: Option<SlackFile>,
84}
85
86fn validate(val: &Image) -> Vec<ValidationErrorKind> {
87    match (val.image_url.as_ref(), val.slack_file.as_ref()) {
88        (Some(_), Some(_)) => {
89            vec![ValidationErrorKind::ExclusiveField(
90                "image_url",
91                "slack_file",
92            )]
93        }
94        (None, None) => {
95            vec![ValidationErrorKind::EitherRequired(
96                "image_url",
97                "slack_file",
98            )]
99        }
100        _ => vec![],
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::composition_objects::test_helpers::*;
108
109    #[test]
110    fn it_implements_builder() {
111        // using image_url
112        let expected = Image {
113            alt_text: Some("An incredibly cute kitten.".into()),
114            image_url: Some("http://placekitten.com/500/500".into()),
115            title: Some(plain_text("Please enjoy this photo of a kitten")),
116            block_id: Some("image4".into()),
117            slack_file: None,
118        };
119
120        let val = Image::builder()
121            .set_alt_text(Some("An incredibly cute kitten."))
122            .set_image_url(Some("http://placekitten.com/500/500"))
123            .set_title(Some(plain_text("Please enjoy this photo of a kitten")))
124            .set_block_id(Some("image4"))
125            .build()
126            .unwrap();
127
128        assert_eq!(val, expected);
129
130        let val = Image::builder()
131            .alt_text("An incredibly cute kitten.")
132            .image_url("http://placekitten.com/500/500")
133            .title(plain_text("Please enjoy this photo of a kitten"))
134            .block_id("image4")
135            .build()
136            .unwrap();
137
138        assert_eq!(val, expected);
139
140        // using slack file
141        let expected = Image {
142            alt_text: Some("An incredibly cute kitten.".into()),
143            image_url: None,
144            title: Some(plain_text("Please enjoy this photo of a kitten")),
145            block_id: Some("image4".into()),
146            slack_file: Some(slack_file()),
147        };
148
149        let val = Image::builder()
150            .set_alt_text(Some("An incredibly cute kitten."))
151            .set_title(Some(plain_text("Please enjoy this photo of a kitten")))
152            .set_block_id(Some("image4"))
153            .set_slack_file(Some(slack_file()))
154            .build()
155            .unwrap();
156
157        assert_eq!(val, expected);
158
159        let val = Image::builder()
160            .alt_text("An incredibly cute kitten.")
161            .title(plain_text("Please enjoy this photo of a kitten"))
162            .block_id("image4")
163            .slack_file(slack_file())
164            .build()
165            .unwrap();
166
167        assert_eq!(val, expected);
168    }
169
170    #[test]
171    fn it_requires_alt_text_field() {
172        let err = Image::builder()
173            .slack_file(slack_file())
174            .build()
175            .unwrap_err();
176        assert_eq!(err.object(), "Image");
177
178        let errors = err.field("alt_text");
179        assert!(errors.includes(ValidationErrorKind::Required));
180    }
181
182    #[test]
183    fn it_requires_alt_text_less_than_2000_characters_long() {
184        let err = Image::builder()
185            .alt_text("a".repeat(2001))
186            .slack_file(slack_file())
187            .build()
188            .unwrap_err();
189        assert_eq!(err.object(), "Image");
190
191        let errors = err.field("alt_text");
192        assert!(errors.includes(ValidationErrorKind::MaxTextLength(2000)));
193    }
194
195    #[test]
196    fn it_requires_image_url_less_than_3000_characters_long() {
197        let err = Image::builder()
198            .alt_text("An incredibly cute kitten.")
199            .image_url("a".repeat(3001))
200            .build()
201            .unwrap_err();
202        assert_eq!(err.object(), "Image");
203
204        let errors = err.field("image_url");
205        assert!(errors.includes(ValidationErrorKind::MaxTextLength(3000)));
206    }
207
208    #[test]
209    fn it_requires_title_less_than_2000_characters_long() {
210        let err = Image::builder()
211            .alt_text("An incredibly cute kitten.")
212            .image_url("http://placekitten.com/500/500")
213            .title(plain_text("a".repeat(2001)))
214            .build()
215            .unwrap_err();
216        assert_eq!(err.object(), "Image");
217
218        let errors = err.field("title");
219        assert!(errors.includes(ValidationErrorKind::MaxTextLength(2000)));
220    }
221
222    #[test]
223    fn it_requires_block_id_than_255_characters_long() {
224        let err = Image::builder()
225            .alt_text("An incredibly cute kitten.")
226            .image_url("http://placekitten.com/500/500")
227            .block_id("a".repeat(256))
228            .build()
229            .unwrap_err();
230        assert_eq!(err.object(), "Image");
231
232        let errors = err.field("block_id");
233        assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
234    }
235
236    #[test]
237    fn it_prevents_from_setting_both_image_url_and_slack_file() {
238        let err = Image::builder()
239            .alt_text("Cute kitten")
240            .image_url("http://placekitten.com/700/500")
241            .slack_file(slack_file())
242            .build()
243            .unwrap_err();
244        assert_eq!(err.object(), "Image");
245
246        let errors = err.across_fields();
247        assert!(errors.includes(ValidationErrorKind::ExclusiveField(
248            "image_url",
249            "slack_file"
250        )));
251    }
252
253    #[test]
254    fn it_requires_either_image_url_or_slack_file_is_set() {
255        let err = Image::builder()
256            .alt_text("Cute kitten")
257            .build()
258            .unwrap_err();
259        assert_eq!(err.object(), "Image");
260
261        let errors = err.across_fields();
262        assert!(errors.includes(ValidationErrorKind::EitherRequired(
263            "image_url",
264            "slack_file"
265        )));
266    }
267}