slack_messaging/blocks/elements/
image.rs

1use crate::composition_objects::SlackFile;
2use crate::errors::ValidationErrorKind;
3use crate::validators::*;
4
5use serde::Serialize;
6use slack_messaging_derive::Builder;
7
8/// [Image element](https://docs.slack.dev/reference/block-kit/block-elements/image-element)
9/// representation.
10///
11/// # Fields and Validations
12///
13/// For more details, see the [official
14/// documentation](https://docs.slack.dev/reference/block-kit/block-elements/image-element).
15///
16/// | Field | Type | Required | Validation |
17/// |-------|------|----------|------------|
18/// | alt_text | String | Yes | N/A |
19/// | image_url | String | Conditional* | Max length 3000 characters |
20/// | slack_file | [SlackFile] | Conditional* | N/A |
21///
22/// # Validation Across Fields
23///
24/// * Either `image_url` or `slack_file` is required, but not both.
25///
26/// # Example
27///
28/// ```
29/// use slack_messaging::blocks::elements::Image;
30/// # use std::error::Error;
31///
32/// # fn try_main() -> Result<(), Box<dyn Error>> {
33/// let image = Image::builder()
34///     .image_url("http://placekitten.com/700/500")
35///     .alt_text("Multiple cute kittens")
36///     .build()?;
37///
38/// let expected = serde_json::json!({
39///     "type": "image",
40///     "image_url": "http://placekitten.com/700/500",
41///     "alt_text": "Multiple cute kittens"
42/// });
43///
44/// let json = serde_json::to_value(image).unwrap();
45///
46/// assert_eq!(json, expected);
47///
48/// // If your object has any validation errors, the build method returns Result::Err
49/// let image = Image::builder()
50///     .image_url("http://placekitten.com/700/500")
51///     .build();
52///
53/// assert!(image.is_err());
54/// #     Ok(())
55/// # }
56/// # fn main() {
57/// #     try_main().unwrap()
58/// # }
59/// ```
60#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
61#[serde(tag = "type", rename = "image")]
62#[builder(validate = "validate")]
63pub struct Image {
64    #[builder(validate("required"))]
65    pub(crate) alt_text: Option<String>,
66
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[builder(validate("text::max_3000"))]
69    pub(crate) image_url: Option<String>,
70
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub(crate) slack_file: Option<SlackFile>,
73}
74
75fn validate(val: &Image) -> Vec<ValidationErrorKind> {
76    match (val.image_url.as_ref(), val.slack_file.as_ref()) {
77        (Some(_), Some(_)) => {
78            vec![ValidationErrorKind::ExclusiveField(
79                "image_url",
80                "slack_file",
81            )]
82        }
83        (None, None) => {
84            vec![ValidationErrorKind::EitherRequired(
85                "image_url",
86                "slack_file",
87            )]
88        }
89        _ => vec![],
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::composition_objects::test_helpers::*;
97
98    #[test]
99    fn it_implements_builder() {
100        // having image_url field
101        let expected = Image {
102            alt_text: Some("Cute kitten".into()),
103            image_url: Some("http://placekitten.com/700/500".into()),
104            slack_file: None,
105        };
106
107        let val = Image::builder()
108            .set_alt_text(Some("Cute kitten"))
109            .set_image_url(Some("http://placekitten.com/700/500"))
110            .build()
111            .unwrap();
112
113        assert_eq!(val, expected);
114
115        let val = Image::builder()
116            .alt_text("Cute kitten")
117            .image_url("http://placekitten.com/700/500")
118            .build()
119            .unwrap();
120
121        assert_eq!(val, expected);
122
123        // having slack_file field
124        let expected = Image {
125            alt_text: Some("Cute kitten".into()),
126            image_url: None,
127            slack_file: Some(slack_file()),
128        };
129
130        let val = Image::builder()
131            .set_alt_text(Some("Cute kitten"))
132            .set_slack_file(Some(slack_file()))
133            .build()
134            .unwrap();
135
136        assert_eq!(val, expected);
137
138        let val = Image::builder()
139            .alt_text("Cute kitten")
140            .slack_file(slack_file())
141            .build()
142            .unwrap();
143
144        assert_eq!(val, expected);
145    }
146
147    #[test]
148    fn it_requires_alt_text_field() {
149        let err = Image::builder()
150            .slack_file(slack_file())
151            .build()
152            .unwrap_err();
153        assert_eq!(err.object(), "Image");
154
155        let errors = err.field("alt_text");
156        assert!(errors.includes(ValidationErrorKind::Required));
157    }
158
159    #[test]
160    fn it_requires_image_url_less_than_3000_characters_long() {
161        let err = Image::builder()
162            .alt_text("Cute kitten")
163            .image_url("a".repeat(3001))
164            .build()
165            .unwrap_err();
166        assert_eq!(err.object(), "Image");
167
168        let errors = err.field("image_url");
169        assert!(errors.includes(ValidationErrorKind::MaxTextLength(3000)));
170    }
171
172    #[test]
173    fn it_prevents_from_setting_both_image_url_and_slack_file() {
174        let err = Image::builder()
175            .alt_text("Cute kitten")
176            .image_url("http://placekitten.com/700/500")
177            .slack_file(slack_file())
178            .build()
179            .unwrap_err();
180        assert_eq!(err.object(), "Image");
181
182        let errors = err.across_fields();
183        assert!(errors.includes(ValidationErrorKind::ExclusiveField(
184            "image_url",
185            "slack_file"
186        )));
187    }
188
189    #[test]
190    fn it_requires_either_image_url_or_slack_file_is_set() {
191        let err = Image::builder()
192            .alt_text("Cute kitten")
193            .build()
194            .unwrap_err();
195        assert_eq!(err.object(), "Image");
196
197        let errors = err.across_fields();
198        assert!(errors.includes(ValidationErrorKind::EitherRequired(
199            "image_url",
200            "slack_file"
201        )));
202    }
203}