1use chrono::NaiveDateTime;
2use crate::error::Result;
3use url::Url;
4use crate::{HexColor, SlackText, SlackTime};
5
6#[derive(Serialize, Debug, Default, Clone, PartialEq)]
9pub struct Attachment {
10 pub fallback: SlackText,
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub text: Option<SlackText>,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub pretext: Option<SlackText>,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub color: Option<HexColor>,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub actions: Option<Vec<Action>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
28 pub fields: Option<Vec<Field>>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub author_name: Option<SlackText>,
32 #[serde(skip_serializing_if = "Option::is_none")]
35 pub author_link: Option<Url>,
36 #[serde(skip_serializing_if = "Option::is_none")]
39 pub author_icon: Option<Url>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub title: Option<SlackText>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub title_link: Option<Url>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub image_url: Option<Url>,
49 #[serde(skip_serializing_if = "Option::is_none")]
52 pub thumb_url: Option<Url>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub footer: Option<SlackText>,
56 #[serde(skip_serializing_if = "Option::is_none")]
59 pub footer_icon: Option<Url>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub ts: Option<SlackTime>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub mrkdwn_in: Option<Vec<Section>>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub callback_id: Option<SlackText>,
69}
70
71#[derive(Eq, PartialEq, Copy, Clone, Serialize, Debug)]
73#[serde(rename_all = "lowercase")]
74pub enum Section {
75 Pretext,
77 Text,
79 Fields,
81}
82#[derive(Serialize, Debug, Clone, PartialEq)]
85pub struct Action {
86 #[serde(rename = "type")]
88 pub action_type: String,
89 pub text: String,
91 pub name: String,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub style: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub value: Option<String>
99}
100
101impl Action {
102 pub fn new<S: Into<String>>(
104 action_type: S,
105 text: S,
106 name: S,
107 style: Option<String>,
108 value: Option<String>
109
110 ) -> Action {
111 Action {
112 action_type: action_type.into(),
113 text: text.into(),
114 name: name.into(),
115 style,
116 value
117 }
118 }
119}
120#[derive(Serialize, Debug, Clone, PartialEq)]
123pub struct Field {
124 pub title: String,
127 pub value: SlackText,
130 #[serde(skip_serializing_if = "Option::is_none")]
133 pub short: Option<bool>,
134}
135
136impl Field {
137 pub fn new<S: Into<String>, ST: Into<SlackText>>(
139 title: S,
140 value: ST,
141 short: Option<bool>,
142 ) -> Field {
143 Field {
144 title: title.into(),
145 value: value.into(),
146 short,
147 }
148 }
149}
150
151#[derive(Debug)]
153pub struct AttachmentBuilder {
154 inner: Result<Attachment>,
155}
156
157impl AttachmentBuilder {
158 pub fn new<S: Into<SlackText>>(fallback: S) -> AttachmentBuilder {
162 AttachmentBuilder {
163 inner: Ok(Attachment {
164 fallback: fallback.into(),
165 ..Default::default()
166 }),
167 }
168 }
169
170 pub fn text<S: Into<SlackText>>(self, text: S) -> AttachmentBuilder {
172 match self.inner {
173 Ok(mut inner) => {
174 inner.text = Some(text.into());
175 AttachmentBuilder { inner: Ok(inner) }
176 }
177 _ => self,
178 }
179 }
180
181 pub fn color<S: Into<String>>(self, color: S) -> AttachmentBuilder {
191 match self.inner {
192 Ok(mut inner) => match HexColor::new_checked(color) {
193 Ok(c) => {
194 inner.color = Some(c);
195 AttachmentBuilder { inner: Ok(inner) }
196 }
197 Err(e) => AttachmentBuilder { inner: Err(e) },
198 },
199 _ => self,
200 }
201 }
202
203 pub fn pretext<S: Into<SlackText>>(self, pretext: S) -> AttachmentBuilder {
205 match self.inner {
206 Ok(mut inner) => {
207 inner.pretext = Some(pretext.into());
208 AttachmentBuilder { inner: Ok(inner) }
209 }
210 _ => self,
211 }
212 }
213 pub fn actions(self, actions: Vec<Action>) -> AttachmentBuilder {
216 match self.inner {
217 Ok(mut inner) => {
218 inner.actions = Some(actions);
219 AttachmentBuilder { inner: Ok(inner) }
220 }
221 _ => self,
222 }
223 }
224 pub fn fields(self, fields: Vec<Field>) -> AttachmentBuilder {
227 match self.inner {
228 Ok(mut inner) => {
229 inner.fields = Some(fields);
230 AttachmentBuilder { inner: Ok(inner) }
231 }
232 _ => self,
233 }
234 }
235 pub fn author_name<S: Into<SlackText>>(self, author_name: S) -> AttachmentBuilder {
237 match self.inner {
238 Ok(mut inner) => {
239 inner.author_name = Some(author_name.into());
240 AttachmentBuilder { inner: Ok(inner) }
241 }
242 _ => self,
243 }
244 }
245
246 url_builder_fn! {
247 author_link, AttachmentBuilder
249 }
250
251 url_builder_fn! {
252 author_icon, AttachmentBuilder
254 }
255
256 pub fn title<S: Into<SlackText>>(self, title: S) -> AttachmentBuilder {
258 match self.inner {
259 Ok(mut inner) => {
260 inner.title = Some(title.into());
261 AttachmentBuilder { inner: Ok(inner) }
262 }
263 _ => self,
264 }
265 }
266
267 pub fn callback_id<S: Into<SlackText>>(self, callback_id: S) -> AttachmentBuilder {
269 match self.inner {
270 Ok(mut inner) => {
271 inner.callback_id = Some(callback_id.into());
272 AttachmentBuilder { inner: Ok(inner) }
273 }
274 _ => self,
275 }
276 }
277
278 url_builder_fn! {
279 title_link, AttachmentBuilder
281 }
282
283 url_builder_fn! {
284 image_url, AttachmentBuilder
286 }
287
288 url_builder_fn! {
289 thumb_url, AttachmentBuilder
291 }
292
293 pub fn footer<S: Into<SlackText>>(self, footer: S) -> AttachmentBuilder {
295 match self.inner {
296 Ok(mut inner) => {
297 inner.footer = Some(footer.into());
298 AttachmentBuilder { inner: Ok(inner) }
299 }
300 _ => self,
301 }
302 }
303
304 url_builder_fn! {
305 footer_icon, AttachmentBuilder
307 }
308
309 pub fn ts(self, time: &NaiveDateTime) -> AttachmentBuilder {
311 match self.inner {
312 Ok(mut inner) => {
313 inner.ts = Some(SlackTime::new(time));
314 AttachmentBuilder { inner: Ok(inner) }
315 }
316 _ => self,
317 }
318 }
319
320 pub fn markdown_in<'a, I: IntoIterator<Item = &'a Section>>(
322 self,
323 sections: I,
324 ) -> AttachmentBuilder {
325 match self.inner {
326 Ok(mut inner) => {
327 inner.mrkdwn_in = Some(sections.into_iter().cloned().collect());
328 AttachmentBuilder { inner: Ok(inner) }
329 }
330 _ => self,
331 }
332 }
333
334 pub fn build(self) -> Result<Attachment> {
336 match self.inner {
338 Ok(mut inner) => {
339 if inner.text.is_none() {
340 inner.text = Some(inner.fallback.clone())
341 }
342 Ok(inner)
343 }
344 _ => self.inner,
345 }
346 }
347}