Skip to main content

slack_morphism/models/common/
mod.rs

1use rsb_derive::Builder;
2use rvstruct::ValueStruct;
3use serde::{Deserialize, Serialize};
4use serde_with::{serde_as, skip_serializing_none};
5use std::hash::Hash;
6use std::*;
7use url::Url;
8
9pub(crate) mod datetime;
10
11pub use datetime::*;
12
13use datetime::unix_seconds;
14
15mod user;
16
17pub use user::*;
18
19mod team;
20
21pub use team::*;
22
23mod channel;
24
25pub use channel::*;
26
27mod pin;
28
29pub use pin::*;
30
31mod reaction;
32
33pub use reaction::*;
34
35mod star;
36
37pub use star::*;
38
39mod bot;
40
41pub use bot::*;
42
43mod icon;
44
45pub use icon::*;
46
47mod formatters;
48
49pub use formatters::*;
50
51mod emoji;
52
53pub use emoji::*;
54mod assistant;
55pub use assistant::*;
56
57#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
58pub struct SlackTs(pub String);
59
60impl SlackTs {
61    /// Converts a Slack timestamp (`<seconds>.<microseconds>`) to an instant.
62    ///
63    /// The fractional part is optional and is interpreted as microseconds,
64    /// padded or truncated to six digits.
65    pub fn to_date_time_opt(&self) -> Option<SlackUtcDateTime> {
66        let value = self.value();
67        let (seconds_part, micros_part) = match value.split_once('.') {
68            Some((seconds, micros)) => (seconds, Some(micros)),
69            None => (value.as_str(), None),
70        };
71
72        let seconds: i64 = seconds_part.parse().ok()?;
73
74        let micros: u32 = match micros_part {
75            Some(micros_part) => {
76                let mut digits: String = micros_part.chars().take(6).collect();
77                while digits.len() < 6 {
78                    digits.push('0');
79                }
80                digits.parse().ok()?
81            }
82            None => 0,
83        };
84
85        datetime::from_unix_seconds_micros(seconds, micros)
86    }
87}
88
89#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
90pub struct SlackScheduledMid(pub String);
91
92#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
93pub struct SlackTeamId(pub String);
94
95#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
96pub struct SlackEnterpriseSubteamId(pub String);
97
98#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
99pub struct SlackAppId(pub String);
100
101#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
102pub struct SlackChannelId(pub String);
103
104#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
105pub struct SlackClientMessageId(pub String);
106
107impl SlackTextFormat for SlackChannelId {
108    fn to_slack_format(&self) -> String {
109        format!("<#{}>", self.value())
110    }
111}
112
113#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
114pub struct SlackChannelType(pub String);
115
116#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
117pub struct SlackConversationId(pub String);
118
119#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
120pub struct SlackActionId(pub String);
121
122#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
123pub struct SlackActionType(pub String);
124
125#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
126pub struct SlackUserId(pub String);
127
128#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
129pub struct SlackUserGroupId(pub String);
130
131impl SlackTextFormat for SlackUserId {
132    fn to_slack_format(&self) -> String {
133        format!("<@{}>", self.value())
134    }
135}
136
137impl SlackTextFormat for SlackUserGroupId {
138    fn to_slack_format(&self) -> String {
139        format!("<!subteam^{}>", self.value())
140    }
141}
142
143#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
144pub struct SlackBotId(pub String);
145
146#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
147pub struct SlackDateTime(#[serde(with = "unix_seconds")] pub SlackUtcDateTime);
148
149impl SlackDateTime {
150    pub fn now() -> Self {
151        Self(datetime::now())
152    }
153}
154
155#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
156pub struct SlackDate(pub String);
157
158impl SlackDate {
159    pub fn to_naive_date(&self) -> Option<SlackCivilDate> {
160        datetime::parse_civil_date(self.value())
161    }
162}
163
164#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
165pub struct SlackLocale(pub String);
166
167#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
168pub struct SlackCursorId(pub String);
169
170#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
171pub struct SlackColor(pub String);
172
173#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
174pub struct SlackCallbackId(pub String);
175
176#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
177pub struct SlackTriggerId(pub String);
178
179#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
180pub struct SlackViewId(pub String);
181
182#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
183pub struct SlackCommandId(pub String);
184
185#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
186pub struct SlackClientId(pub String);
187
188#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
189pub struct SlackClientSecret(pub String);
190
191impl fmt::Debug for SlackClientSecret {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
193        write!(f, "SlackClientSecret(len:{})", self.value().len())
194    }
195}
196
197#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
198pub struct SlackApiTokenScope(pub String);
199
200#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
201pub struct SlackVerificationToken(pub String);
202
203impl fmt::Debug for SlackVerificationToken {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
205        write!(f, "SlackVerificationToken(len:{})", self.value().len())
206    }
207}
208
209#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
210pub struct SlackSigningSecret(pub String);
211
212impl fmt::Debug for SlackSigningSecret {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
214        write!(f, "SlackSigningSecret(len:{})", self.value().len())
215    }
216}
217
218#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
219pub struct EmailAddress(pub String);
220
221#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
222pub struct PhoneNumber(pub String);
223
224#[serde_as]
225#[skip_serializing_none]
226#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
227pub struct SlackResponseMetadata {
228    #[serde(default)]
229    #[serde_as(as = "serde_with::NoneAsEmptyString")]
230    pub next_cursor: Option<SlackCursorId>,
231}
232
233#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
234pub enum SlackConversationType {
235    #[serde(rename = "im")]
236    Im,
237    #[serde(rename = "mpim")]
238    Mpim,
239    #[serde(rename = "private_channel")]
240    Private,
241    #[serde(rename = "public_channel")]
242    Public,
243}
244
245#[allow(clippy::to_string_trait_impl)]
246impl ToString for SlackConversationType {
247    fn to_string(&self) -> String {
248        match self {
249            SlackConversationType::Im => "im".into(),
250            SlackConversationType::Mpim => "mpim".into(),
251            SlackConversationType::Private => "private_channel".into(),
252            SlackConversationType::Public => "public_channel".into(),
253        }
254    }
255}
256
257#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
258pub struct SlackResponseUrl(pub Url);
259
260#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
261pub struct SlackWebSocketsUrl(pub Url);
262
263impl SlackWebSocketsUrl {
264    pub fn to_debug_url(&self) -> Self {
265        Self(Url::parse(format!("{}&debug_reconnects=true", self.value()).as_str()).unwrap())
266    }
267}
268
269#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
270pub struct SlackTeamUrl(pub Url);
271
272#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
273pub struct SlackUnfurlId(pub String);
274
275#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
276pub struct SlackMimeType(pub String);
277
278#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
279pub struct SlackEmoji(pub String);
280
281impl SlackEmoji {
282    pub const SMILE: &'static str = ":smile:";
283    pub const SPEECH_BALLOON: &'static str = ":speech_balloon:";
284    pub const HEAVY_CHECK_MARK: &'static str = ":heavy_check_mark:";
285}
286
287#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
288pub enum SlackShortcutType {
289    #[serde(rename = "message")]
290    Message,
291    #[serde(rename = "global")]
292    Global,
293}
294
295#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
296pub struct SlackEventType(pub String);
297
298#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
299pub struct SlackEnterpriseId(pub String);
300
301/// This type is needed since Slack allowes invalid URLs in some places like Rich sections
302/// and we still need to read them on the client side, so we store it as a string
303/// but provide convinent messages to convert to Url if possible.
304#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
305pub struct SlackRelaxedUrl(pub String);
306
307impl SlackRelaxedUrl {
308    pub fn to_url(&self) -> Result<Url, url::ParseError> {
309        self.try_into()
310    }
311}
312
313impl TryFrom<&SlackRelaxedUrl> for Url {
314    type Error = url::ParseError;
315
316    fn try_from(relaxed_url: &SlackRelaxedUrl) -> Result<Self, Self::Error> {
317        Url::parse(relaxed_url.value())
318    }
319}
320
321impl TryFrom<SlackRelaxedUrl> for Url {
322    type Error = url::ParseError;
323
324    fn try_from(relaxed_url: SlackRelaxedUrl) -> Result<Self, Self::Error> {
325        Url::parse(relaxed_url.value())
326    }
327}
328
329impl From<Url> for SlackRelaxedUrl {
330    fn from(url: Url) -> Self {
331        SlackRelaxedUrl(url.to_string())
332    }
333}
334
335impl From<&Url> for SlackRelaxedUrl {
336    fn from(url: &Url) -> Self {
337        SlackRelaxedUrl(url.to_string())
338    }
339}
340
341#[cfg(test)]
342mod test {
343    use super::*;
344    use serde_json::json;
345
346    fn test_date_time() -> SlackUtcDateTime {
347        "2020-01-01T00:42:42Z".parse::<SlackUtcDateTime>().unwrap()
348    }
349
350    #[test]
351    fn test_slack_date_time() {
352        let dt = SlackDateTime(test_date_time());
353        let json = serde_json::to_value(&dt).unwrap();
354        assert_eq!(json.as_u64().unwrap(), 1577839362);
355
356        let parsed: SlackDateTime = serde_json::from_value(json!(1577839362)).unwrap();
357        assert_eq!(parsed, dt);
358        assert_eq!(serde_json::from_value::<SlackDateTime>(json).unwrap(), dt);
359    }
360
361    #[test]
362    fn test_slack_ts_to_date_time_with_micros() {
363        let ts = SlackTs("1577839362.000400".to_string());
364        let dt = ts.to_date_time_opt().unwrap();
365
366        assert_eq!(datetime::unix_seconds(&dt), 1577839362);
367        assert_eq!(
368            Some(dt),
369            datetime::from_unix_seconds_micros(1577839362, 400)
370        );
371        assert_ne!(Some(dt), datetime::from_unix_seconds_micros(1577839362, 0));
372    }
373
374    #[test]
375    fn test_slack_ts_to_date_time_without_fraction() {
376        let ts = SlackTs("1577839362".to_string());
377        let dt = ts.to_date_time_opt().unwrap();
378
379        assert_eq!(datetime::unix_seconds(&dt), 1577839362);
380        assert_eq!(Some(dt), datetime::from_unix_seconds_micros(1577839362, 0));
381    }
382
383    #[test]
384    fn test_slack_ts_to_date_time_invalid() {
385        assert_eq!(SlackTs("garbage".to_string()).to_date_time_opt(), None);
386    }
387
388    #[test]
389    fn test_slack_date_to_naive_date() {
390        assert!(SlackDate("2020-01-01".to_string())
391            .to_naive_date()
392            .is_some());
393        assert_eq!(SlackDate("2020-13-01".to_string()).to_naive_date(), None);
394        assert_eq!(SlackDate("garbage".to_string()).to_naive_date(), None);
395    }
396}