slack_blocks/blocks/image.rs
1//! # Image Block
2//!
3//! _[slack api docs 🔗]_
4//!
5//! A simple image block, designed to make those cat photos really pop.
6//!
7//! [slack api docs 🔗]: https://api.slack.com/reference/block-kit/blocks#image
8
9use std::borrow::Cow;
10
11use serde::{Deserialize, Serialize};
12#[cfg(feature = "validation")]
13use validator::Validate;
14
15use crate::compose::text;
16#[cfg(feature = "validation")]
17use crate::val_helpr::ValidationResult;
18
19/// # Image Block
20///
21/// _[slack api docs 🔗]_
22///
23/// A simple image block, designed to make those cat photos really pop.
24///
25/// [slack api docs 🔗]: https://api.slack.com/reference/block-kit/blocks#image
26#[derive(Clone, Debug, Default, Deserialize, Hash, PartialEq, Serialize)]
27#[cfg_attr(feature = "validation", derive(Validate))]
28pub struct Image<'a> {
29 #[cfg_attr(feature = "validation", validate(length(max = 3000)))]
30 image_url: Cow<'a, str>,
31
32 #[cfg_attr(feature = "validation", validate(length(max = 2000)))]
33 alt_text: Cow<'a, str>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 #[cfg_attr(feature = "validation", validate(custom = "validate::title"))]
37 title: Option<text::Text>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
40 #[cfg_attr(feature = "validation",
41 validate(custom = "super::validate_block_id"))]
42 block_id: Option<Cow<'a, str>>,
43}
44
45impl<'a> Image<'a> {
46 /// Build a new Image block.
47 ///
48 /// For example, see docs for ImageBuilder.
49 pub fn builder() -> build::ImageBuilderInit<'a> {
50 build::ImageBuilderInit::new()
51 }
52
53 /// Validate that this Image block agrees with Slack's model requirements
54 ///
55 /// # Errors
56 /// - If `block_id` longer
57 /// than 255 chars
58 /// - If title longer than 2000 chars
59 /// - If `alt_text` longer than 2000 chars
60 /// - If `image_url` longer than 3000 chars
61 ///
62 /// # Example
63 /// ```
64 /// use slack_blocks::blocks;
65 ///
66 /// let long_string = std::iter::repeat(' ').take(256).collect::<String>();
67 ///
68 /// let block = blocks::Image::builder().image_url("")
69 /// .alt("")
70 /// .block_id(long_string)
71 /// .build();
72 ///
73 /// assert_eq!(true, matches!(block.validate(), Err(_)));
74 /// ```
75 #[cfg(feature = "validation")]
76 #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
77 pub fn validate(&self) -> ValidationResult {
78 Validate::validate(self)
79 }
80}
81
82/// File block builder
83pub mod build {
84 use std::marker::PhantomData;
85
86 use super::*;
87 use crate::build::*;
88
89 /// Compile-time markers for builder methods
90 #[allow(non_camel_case_types)]
91 pub mod method {
92 /// ImageBuilder.image_url or src
93 #[derive(Clone, Copy, Debug)]
94 pub struct url;
95
96 /// ImageBuilder.alt_text or alt
97 #[derive(Clone, Copy, Debug)]
98 pub struct alt;
99 }
100
101 /// Initial state for `ImageBuilder`
102 pub type ImageBuilderInit<'a> =
103 ImageBuilder<'a,
104 RequiredMethodNotCalled<method::url>,
105 RequiredMethodNotCalled<method::alt>>;
106
107 /// Build an Image block
108 ///
109 /// Allows you to construct safely, with compile-time checks
110 /// on required setter methods.
111 ///
112 /// # Required Methods
113 /// `ImageBuilder::build()` is only available if these methods have been called:
114 /// - `external_id`
115 /// - `source`
116 ///
117 /// # Example
118 /// ```
119 /// use slack_blocks::{blocks::Image, text::ToSlackPlaintext};
120 ///
121 /// let block = Image::builder().image_url("https://foo.com/bar.png")
122 /// .alt_text("pic of bar")
123 /// .build();
124 /// ```
125 #[derive(Debug)]
126 pub struct ImageBuilder<'a, Url, Alt> {
127 image_url: Option<Cow<'a, str>>,
128 alt_text: Option<Cow<'a, str>>,
129 title: Option<text::Text>,
130 block_id: Option<Cow<'a, str>>,
131 state: PhantomData<(Url, Alt)>,
132 }
133
134 impl<'a, Url, Alt> ImageBuilder<'a, Url, Alt> {
135 /// Create a new ImageBuilder
136 pub fn new() -> Self {
137 Self { image_url: None,
138 alt_text: None,
139 title: None,
140 block_id: None,
141 state: PhantomData::<_> }
142 }
143
144 /// Set `title` (Optional)
145 ///
146 /// An optional title for the image in the form of a
147 /// Plaintext [text object 🔗].
148 ///
149 /// Maximum length for the text in this field is 2000 characters.
150 ///
151 /// [text object 🔗]: https://api.slack.com/reference/messaging/composition-objects#text
152 pub fn title<T>(mut self, text: T) -> Self
153 where T: Into<text::Plain>
154 {
155 self.title = Some(text.into().into());
156 self
157 }
158
159 /// Alias for `image_url`.
160 pub fn src<S>(self, image_url: S) -> ImageBuilder<'a, Set<method::url>, Alt>
161 where S: Into<Cow<'a, str>>
162 {
163 self.image_url(image_url)
164 }
165
166 /// Set `image_url` (**Required**)
167 ///
168 /// The URL of the image to be displayed.
169 ///
170 /// Maximum length for this field is 3000 characters.
171 pub fn image_url<S>(self,
172 image_url: S)
173 -> ImageBuilder<'a, Set<method::url>, Alt>
174 where S: Into<Cow<'a, str>>
175 {
176 ImageBuilder { image_url: Some(image_url.into()),
177 alt_text: self.alt_text,
178 title: self.title,
179 block_id: self.block_id,
180 state: PhantomData::<_> }
181 }
182
183 /// Set `alt_text` (**Required**)
184 ///
185 /// A plain-text summary of the image.
186 ///
187 /// This should not contain any markup.
188 ///
189 /// Maximum length for this field is 2000 characters.
190 pub fn alt_text<S>(self,
191 alt_text: S)
192 -> ImageBuilder<'a, Url, Set<method::alt>>
193 where S: Into<Cow<'a, str>>
194 {
195 ImageBuilder { alt_text: Some(alt_text.into()),
196 image_url: self.image_url,
197 title: self.title,
198 block_id: self.block_id,
199 state: PhantomData::<_> }
200 }
201
202 /// Alias for `alt_text`.
203 pub fn alt<S>(self, alt_text: S) -> ImageBuilder<'a, Url, Set<method::alt>>
204 where S: Into<Cow<'a, str>>
205 {
206 self.alt_text(alt_text)
207 }
208
209 /// Set `block_id` (Optional)
210 ///
211 /// A string acting as a unique identifier for a block.
212 ///
213 /// You can use this `block_id` when you receive an interaction payload
214 /// to [identify the source of the action 🔗].
215 ///
216 /// If not specified, a `block_id` will be generated.
217 ///
218 /// Maximum length for this field is 255 characters.
219 ///
220 /// [identify the source of the action 🔗]: https://api.slack.com/interactivity/handling#payloads
221 pub fn block_id<S>(mut self, block_id: S) -> Self
222 where S: Into<Cow<'a, str>>
223 {
224 self.block_id = Some(block_id.into());
225 self
226 }
227 }
228
229 impl<'a> ImageBuilder<'a, Set<method::url>, Set<method::alt>> {
230 /// All done building, now give me a darn actions block!
231 ///
232 /// > `no method name 'build' found for struct 'ImageBuilder<...>'`?
233 /// Make sure all required setter methods have been called. See docs for `ImageBuilder`.
234 ///
235 /// ```compile_fail
236 /// use slack_blocks::blocks::Image;
237 ///
238 /// let foo = Image::builder().build(); // Won't compile!
239 /// ```
240 ///
241 /// ```
242 /// use slack_blocks::{blocks::Image, compose::text::ToSlackPlaintext};
243 ///
244 /// let block = Image::builder().image_url("https://foo.com/bar.png")
245 /// .alt_text("pic of bar")
246 /// .build();
247 /// ```
248 pub fn build(self) -> Image<'a> {
249 Image { image_url: self.image_url.unwrap(),
250 alt_text: self.alt_text.unwrap(),
251 title: self.title,
252 block_id: self.block_id }
253 }
254 }
255}
256
257#[cfg(feature = "validation")]
258mod validate {
259 use crate::{compose::text,
260 val_helpr::{below_len, ValidatorResult}};
261
262 pub(super) fn title(text: &text::Text) -> ValidatorResult {
263 below_len("Image Title", 2000, text.as_ref())
264 }
265}