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#[derive(Serialize, Debug, Clone, PartialEq)]
87pub struct Action {
88 #[serde(rename = "type")]
90 pub action_type: String,
91 pub text: String,
93 pub name: String,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub style: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub value: Option<String>,
101}
102
103impl Action {
104 pub fn new<S: Into<String>>(
106 action_type: S,
107 text: S,
108 name: S,
109 style: Option<String>,
110 value: Option<String>,
111 ) -> Action {
112 Action {
113 action_type: action_type.into(),
114 text: text.into(),
115 name: name.into(),
116 style,
117 value,
118 }
119 }
120}
121#[derive(Serialize, Debug, Clone, PartialEq)]
124pub struct Field {
125 pub title: String,
128 pub value: SlackText,
131 #[serde(skip_serializing_if = "Option::is_none")]
134 pub short: Option<bool>,
135}
136
137impl Field {
138 pub fn new<S: Into<String>, ST: Into<SlackText>>(
140 title: S,
141 value: ST,
142 short: Option<bool>,
143 ) -> Field {
144 Field {
145 title: title.into(),
146 value: value.into(),
147 short,
148 }
149 }
150}
151
152#[derive(Debug)]
154pub struct AttachmentBuilder {
155 inner: Result<Attachment>,
156}
157
158impl AttachmentBuilder {
159 pub fn new<S: Into<SlackText>>(fallback: S) -> AttachmentBuilder {
163 AttachmentBuilder {
164 inner: Ok(Attachment {
165 fallback: fallback.into(),
166 ..Default::default()
167 }),
168 }
169 }
170
171 pub fn text<S: Into<SlackText>>(self, text: S) -> AttachmentBuilder {
173 match self.inner {
174 Ok(mut inner) => {
175 inner.text = Some(text.into());
176 AttachmentBuilder { inner: Ok(inner) }
177 }
178 _ => self,
179 }
180 }
181
182 pub fn color<C: TryInto<HexColor, Error = Error>>(self, color: C) -> AttachmentBuilder {
192 match self.inner {
193 Ok(mut inner) => match color.try_into() {
194 Ok(c) => {
195 inner.color = Some(c);
196 AttachmentBuilder { inner: Ok(inner) }
197 }
198 Err(e) => AttachmentBuilder { inner: Err(e) },
199 },
200 _ => self,
201 }
202 }
203
204 pub fn pretext<S: Into<SlackText>>(self, pretext: S) -> AttachmentBuilder {
206 match self.inner {
207 Ok(mut inner) => {
208 inner.pretext = Some(pretext.into());
209 AttachmentBuilder { inner: Ok(inner) }
210 }
211 _ => self,
212 }
213 }
214 pub fn actions(self, actions: Vec<Action>) -> AttachmentBuilder {
217 match self.inner {
218 Ok(mut inner) => {
219 inner.actions = Some(actions);
220 AttachmentBuilder { inner: Ok(inner) }
221 }
222 _ => self,
223 }
224 }
225 pub fn fields(self, fields: Vec<Field>) -> AttachmentBuilder {
228 match self.inner {
229 Ok(mut inner) => {
230 inner.fields = Some(fields);
231 AttachmentBuilder { inner: Ok(inner) }
232 }
233 _ => self,
234 }
235 }
236 pub fn author_name<S: Into<SlackText>>(self, author_name: S) -> AttachmentBuilder {
238 match self.inner {
239 Ok(mut inner) => {
240 inner.author_name = Some(author_name.into());
241 AttachmentBuilder { inner: Ok(inner) }
242 }
243 _ => self,
244 }
245 }
246
247 url_builder_fn! {
248 author_link, AttachmentBuilder
250 }
251
252 url_builder_fn! {
253 author_icon, AttachmentBuilder
255 }
256
257 pub fn title<S: Into<SlackText>>(self, title: S) -> AttachmentBuilder {
259 match self.inner {
260 Ok(mut inner) => {
261 inner.title = Some(title.into());
262 AttachmentBuilder { inner: Ok(inner) }
263 }
264 _ => self,
265 }
266 }
267
268 pub fn callback_id<S: Into<SlackText>>(self, callback_id: S) -> AttachmentBuilder {
270 match self.inner {
271 Ok(mut inner) => {
272 inner.callback_id = Some(callback_id.into());
273 AttachmentBuilder { inner: Ok(inner) }
274 }
275 _ => self,
276 }
277 }
278
279 url_builder_fn! {
280 title_link, AttachmentBuilder
282 }
283
284 url_builder_fn! {
285 image_url, AttachmentBuilder
287 }
288
289 url_builder_fn! {
290 thumb_url, AttachmentBuilder
292 }
293
294 pub fn footer<S: Into<SlackText>>(self, footer: S) -> AttachmentBuilder {
296 match self.inner {
297 Ok(mut inner) => {
298 inner.footer = Some(footer.into());
299 AttachmentBuilder { inner: Ok(inner) }
300 }
301 _ => self,
302 }
303 }
304
305 url_builder_fn! {
306 footer_icon, AttachmentBuilder
308 }
309
310 pub fn ts(self, time: &NaiveDateTime) -> AttachmentBuilder {
312 match self.inner {
313 Ok(mut inner) => {
314 inner.ts = Some(SlackTime::new(time));
315 AttachmentBuilder { inner: Ok(inner) }
316 }
317 _ => self,
318 }
319 }
320
321 pub fn markdown_in<'a, I: IntoIterator<Item = &'a Section>>(
323 self,
324 sections: I,
325 ) -> AttachmentBuilder {
326 match self.inner {
327 Ok(mut inner) => {
328 inner.mrkdwn_in = Some(sections.into_iter().cloned().collect());
329 AttachmentBuilder { inner: Ok(inner) }
330 }
331 _ => self,
332 }
333 }
334
335 pub fn build(self) -> Result<Attachment> {
337 match self.inner {
339 Ok(mut inner) => {
340 if inner.text.is_none() {
341 inner.text = Some(inner.fallback.clone())
342 }
343 Ok(inner)
344 }
345 _ => self.inner,
346 }
347 }
348}