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 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 SlackCanvasId(pub String);
103
104#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
106pub struct SlackCanvasSectionId(pub String);
107
108#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
109pub struct SlackChannelId(pub String);
110
111#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
112pub struct SlackClientMessageId(pub String);
113
114impl SlackTextFormat for SlackChannelId {
115 fn to_slack_format(&self) -> String {
116 format!("<#{}>", self.value())
117 }
118}
119
120#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
121pub struct SlackChannelType(pub String);
122
123#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
124pub struct SlackConversationId(pub String);
125
126#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
127pub struct SlackActionId(pub String);
128
129#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
130pub struct SlackActionType(pub String);
131
132#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
133pub struct SlackUserId(pub String);
134
135#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
136pub struct SlackUserGroupId(pub String);
137
138impl SlackTextFormat for SlackUserId {
139 fn to_slack_format(&self) -> String {
140 format!("<@{}>", self.value())
141 }
142}
143
144impl SlackTextFormat for SlackUserGroupId {
145 fn to_slack_format(&self) -> String {
146 format!("<!subteam^{}>", self.value())
147 }
148}
149
150#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
151pub struct SlackBotId(pub String);
152
153#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
154pub struct SlackDateTime(#[serde(with = "unix_seconds")] pub SlackUtcDateTime);
155
156impl SlackDateTime {
157 pub fn now() -> Self {
158 Self(datetime::now())
159 }
160}
161
162#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
163pub struct SlackDate(pub String);
164
165impl SlackDate {
166 pub fn to_naive_date(&self) -> Option<SlackCivilDate> {
167 datetime::parse_civil_date(self.value())
168 }
169}
170
171#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
172pub struct SlackLocale(pub String);
173
174#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
175pub struct SlackCursorId(pub String);
176
177#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
178pub struct SlackColor(pub String);
179
180#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
181pub struct SlackCallbackId(pub String);
182
183#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
184pub struct SlackTriggerId(pub String);
185
186#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
187pub struct SlackViewId(pub String);
188
189#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
190pub struct SlackCommandId(pub String);
191
192#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
193pub struct SlackClientId(pub String);
194
195#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
196pub struct SlackClientSecret(pub String);
197
198impl fmt::Debug for SlackClientSecret {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(f, "SlackClientSecret(len:{})", self.value().len())
201 }
202}
203
204#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
205pub struct SlackApiTokenScope(pub String);
206
207#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
208pub struct SlackVerificationToken(pub String);
209
210impl fmt::Debug for SlackVerificationToken {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
212 write!(f, "SlackVerificationToken(len:{})", self.value().len())
213 }
214}
215
216#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
217pub struct SlackSigningSecret(pub String);
218
219impl fmt::Debug for SlackSigningSecret {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
221 write!(f, "SlackSigningSecret(len:{})", self.value().len())
222 }
223}
224
225#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
226pub struct EmailAddress(pub String);
227
228#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
229pub struct PhoneNumber(pub String);
230
231#[serde_as]
232#[skip_serializing_none]
233#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
234pub struct SlackResponseMetadata {
235 #[serde(default)]
236 #[serde_as(as = "serde_with::NoneAsEmptyString")]
237 pub next_cursor: Option<SlackCursorId>,
238}
239
240#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
241pub enum SlackConversationType {
242 #[serde(rename = "im")]
243 Im,
244 #[serde(rename = "mpim")]
245 Mpim,
246 #[serde(rename = "private_channel")]
247 Private,
248 #[serde(rename = "public_channel")]
249 Public,
250}
251
252#[allow(clippy::to_string_trait_impl)]
253impl ToString for SlackConversationType {
254 fn to_string(&self) -> String {
255 match self {
256 SlackConversationType::Im => "im".into(),
257 SlackConversationType::Mpim => "mpim".into(),
258 SlackConversationType::Private => "private_channel".into(),
259 SlackConversationType::Public => "public_channel".into(),
260 }
261 }
262}
263
264#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
265pub struct SlackResponseUrl(pub Url);
266
267#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
268pub struct SlackWebSocketsUrl(pub Url);
269
270impl SlackWebSocketsUrl {
271 pub fn to_debug_url(&self) -> Self {
272 let mut url = self.value().clone();
273 url.query_pairs_mut()
274 .append_pair("debug_reconnects", "true");
275 Self(url)
276 }
277}
278
279#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
280pub struct SlackTeamUrl(pub Url);
281
282#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
283pub struct SlackUnfurlId(pub String);
284
285#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
286pub struct SlackMimeType(pub String);
287
288#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
289pub struct SlackEmoji(pub String);
290
291impl SlackEmoji {
292 pub const SMILE: &'static str = ":smile:";
293 pub const SPEECH_BALLOON: &'static str = ":speech_balloon:";
294 pub const HEAVY_CHECK_MARK: &'static str = ":heavy_check_mark:";
295}
296
297#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
298pub enum SlackShortcutType {
299 #[serde(rename = "message")]
300 Message,
301 #[serde(rename = "global")]
302 Global,
303}
304
305#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
306pub struct SlackEventType(pub String);
307
308#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
309pub struct SlackAppContextEntityType(pub String);
310
311#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
312pub struct SlackEnterpriseId(pub String);
313
314#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
318pub struct SlackRelaxedUrl(pub String);
319
320impl SlackRelaxedUrl {
321 pub fn to_url(&self) -> Result<Url, url::ParseError> {
322 self.try_into()
323 }
324}
325
326impl TryFrom<&SlackRelaxedUrl> for Url {
327 type Error = url::ParseError;
328
329 fn try_from(relaxed_url: &SlackRelaxedUrl) -> Result<Self, Self::Error> {
330 Url::parse(relaxed_url.value())
331 }
332}
333
334impl TryFrom<SlackRelaxedUrl> for Url {
335 type Error = url::ParseError;
336
337 fn try_from(relaxed_url: SlackRelaxedUrl) -> Result<Self, Self::Error> {
338 Url::parse(relaxed_url.value())
339 }
340}
341
342impl From<Url> for SlackRelaxedUrl {
343 fn from(url: Url) -> Self {
344 SlackRelaxedUrl(url.to_string())
345 }
346}
347
348impl From<&Url> for SlackRelaxedUrl {
349 fn from(url: &Url) -> Self {
350 SlackRelaxedUrl(url.to_string())
351 }
352}
353
354#[cfg(test)]
355mod test {
356 use super::*;
357 use serde_json::json;
358
359 fn test_date_time() -> SlackUtcDateTime {
360 "2020-01-01T00:42:42Z".parse::<SlackUtcDateTime>().unwrap()
361 }
362
363 #[test]
364 fn test_slack_date_time() {
365 let dt = SlackDateTime(test_date_time());
366 let json = serde_json::to_value(&dt).unwrap();
367 assert_eq!(json.as_u64().unwrap(), 1577839362);
368
369 let parsed: SlackDateTime = serde_json::from_value(json!(1577839362)).unwrap();
370 assert_eq!(parsed, dt);
371 assert_eq!(serde_json::from_value::<SlackDateTime>(json).unwrap(), dt);
372 }
373
374 #[test]
375 fn test_slack_ts_to_date_time_with_micros() {
376 let ts = SlackTs("1577839362.000400".to_string());
377 let dt = ts.to_date_time_opt().unwrap();
378
379 assert_eq!(datetime::unix_seconds(&dt), 1577839362);
380 assert_eq!(
381 Some(dt),
382 datetime::from_unix_seconds_micros(1577839362, 400)
383 );
384 assert_ne!(Some(dt), datetime::from_unix_seconds_micros(1577839362, 0));
385 }
386
387 #[test]
388 fn test_slack_ts_to_date_time_without_fraction() {
389 let ts = SlackTs("1577839362".to_string());
390 let dt = ts.to_date_time_opt().unwrap();
391
392 assert_eq!(datetime::unix_seconds(&dt), 1577839362);
393 assert_eq!(Some(dt), datetime::from_unix_seconds_micros(1577839362, 0));
394 }
395
396 #[test]
397 fn test_slack_ts_to_date_time_invalid() {
398 assert_eq!(SlackTs("garbage".to_string()).to_date_time_opt(), None);
399 }
400
401 #[test]
402 fn debug_url_keeps_existing_query() {
403 let url = SlackWebSocketsUrl(Url::parse("wss://wss.slack.com/link/?ticket=abc").unwrap());
404
405 assert_eq!(
406 url.to_debug_url(),
407 SlackWebSocketsUrl(
408 Url::parse("wss://wss.slack.com/link/?ticket=abc&debug_reconnects=true").unwrap()
409 )
410 );
411 }
412
413 #[test]
414 fn debug_url_adds_query_when_absent() {
415 let url = SlackWebSocketsUrl(Url::parse("wss://wss.slack.com/link/").unwrap());
416
417 assert_eq!(
418 url.to_debug_url(),
419 SlackWebSocketsUrl(
420 Url::parse("wss://wss.slack.com/link/?debug_reconnects=true").unwrap()
421 )
422 );
423 }
424
425 #[test]
426 fn test_slack_date_to_naive_date() {
427 assert!(SlackDate("2020-01-01".to_string())
428 .to_naive_date()
429 .is_some());
430 assert_eq!(SlackDate("2020-13-01".to_string()).to_naive_date(), None);
431 assert_eq!(SlackDate("garbage".to_string()).to_naive_date(), None);
432 }
433}