1use crate::error::{Error, Result};
2use crate::{HexColor, SlackText, SlackTime};
3use chrono::NaiveDateTime;
4use reqwest::Url;
5use serde::Serialize;
6use std::convert::TryInto;
7
8#[derive(Serialize, Debug, Default, Clone, PartialEq)]
11pub struct Attachment {
12 pub fallback: SlackText,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub text: Option<SlackText>,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub pretext: Option<SlackText>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub color: Option<HexColor>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub actions: Option<Vec<Action>>,
27 #[serde(skip_serializing_if = "Option::is_none")]
30 pub fields: Option<Vec<Field>>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub author_name: Option<SlackText>,
34 #[serde(skip_serializing_if = "Option::is_none")]
37 pub author_link: Option<Url>,
38 #[serde(skip_serializing_if = "Option::is_none")]
41 pub author_icon: Option<Url>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub title: Option<SlackText>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub title_link: Option<Url>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub image_url: Option<Url>,
51 #[serde(skip_serializing_if = "Option::is_none")]
54 pub thumb_url: Option<Url>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub footer: Option<SlackText>,
58 #[serde(skip_serializing_if = "Option::is_none")]
61 pub footer_icon: Option<Url>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub ts: Option<SlackTime>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub mrkdwn_in: Option<Vec<Section>>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub callback_id: Option<SlackText>,
71}
72
73#[derive(Eq, PartialEq, Copy, Clone, Serialize, Debug)]
75#[serde(rename_all = "lowercase")]
76pub enum Section {
77 Pretext,
79 Text,
81 Fields,
83}
84
85#[derive(Serialize, Debug, Clone, PartialEq)]
88pub struct Action {
89 #[serde(rename = "type")]
91 pub action_type: String,
92 pub text: String,
94 pub name: String,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub style: Option<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub value: Option<String>,
102}
103
104impl Action {
105 pub fn new<S: Into<String>>(
107 action_type: S,
108 text: S,
109 name: S,
110 style: Option<String>,
111 value: Option<String>,
112 ) -> Action {
113 Action {
114 action_type: action_type.into(),
115 text: text.into(),
116 name: name.into(),
117 style,
118 value,
119 }
120 }
121}
122
123#[derive(Serialize, Debug, Clone, PartialEq)]
126pub struct Field {
127 pub title: String,
130 pub value: SlackText,
133 #[serde(skip_serializing_if = "Option::is_none")]
136 pub short: Option<bool>,
137}
138
139impl Field {
140 pub fn new<S: Into<String>, ST: Into<SlackText>>(
142 title: S,
143 value: ST,
144 short: Option<bool>,
145 ) -> Field {
146 Field {
147 title: title.into(),
148 value: value.into(),
149 short,
150 }
151 }
152}
153
154#[derive(Debug)]
156#[must_use]
157pub struct AttachmentBuilder {
158 inner: Result<Attachment>,
159}
160
161impl AttachmentBuilder {
162 pub fn new<S: Into<SlackText>>(fallback: S) -> Self {
168 Self {
169 inner: Ok(Attachment {
170 fallback: fallback.into(),
171 ..Default::default()
172 }),
173 }
174 }
175
176 pub fn text<S: Into<SlackText>>(mut self, text: S) -> Self {
178 if let Ok(inner) = &mut self.inner {
179 inner.text = Some(text.into());
180 }
181 self
182 }
183
184 pub fn color<C: TryInto<HexColor, Error = Error>>(mut self, color: C) -> Self {
194 if let Ok(inner) = &mut self.inner {
195 match color.try_into() {
196 Ok(c) => inner.color = Some(c),
197 Err(err) => self.inner = Err(err),
198 }
199 }
200 self
201 }
202
203 pub fn pretext<S: Into<SlackText>>(mut self, pretext: S) -> Self {
205 if let Ok(inner) = &mut self.inner {
206 inner.pretext = Some(pretext.into());
207 }
208 self
209 }
210 pub fn actions(mut self, actions: Vec<Action>) -> Self {
213 if let Ok(inner) = &mut self.inner {
214 inner.actions = Some(actions);
215 }
216 self
217 }
218 pub fn fields(mut self, fields: Vec<Field>) -> Self {
221 if let Ok(inner) = &mut self.inner {
222 inner.fields = Some(fields);
223 }
224 self
225 }
226 pub fn author_name<S: Into<SlackText>>(mut self, author_name: S) -> Self {
228 if let Ok(inner) = &mut self.inner {
229 inner.author_name = Some(author_name.into());
230 }
231 self
232 }
233
234 url_builder_fn! {
235 author_link, Self
237 }
238
239 url_builder_fn! {
240 author_icon, Self
242 }
243
244 pub fn title<S: Into<SlackText>>(mut self, title: S) -> Self {
246 if let Ok(inner) = &mut self.inner {
247 inner.title = Some(title.into());
248 }
249 self
250 }
251
252 pub fn callback_id<S: Into<SlackText>>(mut self, callback_id: S) -> Self {
254 if let Ok(inner) = &mut self.inner {
255 inner.callback_id = Some(callback_id.into());
256 }
257 self
258 }
259
260 url_builder_fn! {
261 title_link, Self
263 }
264
265 url_builder_fn! {
266 image_url, Self
268 }
269
270 url_builder_fn! {
271 thumb_url, Self
273 }
274
275 pub fn footer<S: Into<SlackText>>(mut self, footer: S) -> Self {
277 if let Ok(inner) = &mut self.inner {
278 inner.footer = Some(footer.into());
279 }
280 self
281 }
282
283 url_builder_fn! {
284 footer_icon, Self
286 }
287
288 pub fn ts(mut self, time: &NaiveDateTime) -> Self {
290 if let Ok(inner) = &mut self.inner {
291 inner.ts = Some(SlackTime::new(time));
292 }
293 self
294 }
295
296 pub fn markdown_in<'a, I: IntoIterator<Item = &'a Section>>(mut self, sections: I) -> Self {
298 if let Ok(inner) = &mut self.inner {
299 inner.mrkdwn_in = Some(sections.into_iter().cloned().collect());
300 }
301 self
302 }
303
304 pub fn build(mut self) -> Result<Attachment> {
306 if let Ok(inner) = &mut self.inner {
308 if inner.text.is_none() {
309 inner.text = Some(inner.fallback.clone());
310 }
311 }
312 self.inner
313 }
314}