slack_morphism/models/common/
emoji.rs

1use std::fmt;
2
3use rvstruct::ValueStruct;
4use serde::{
5    de::{MapAccess, Visitor},
6    Deserialize, Deserializer, Serialize, Serializer,
7};
8use url::Url;
9
10/// Represent a Slack custom emoji name without the leading `:` and trailing `:`
11#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
12pub struct SlackEmojiName(pub String);
13
14#[derive(Debug, Eq, PartialEq, Hash, Clone)]
15pub enum SlackEmojiRef {
16    Url(Url),
17    Alias(SlackEmojiName),
18}
19
20const SLACK_EMOJI_ALIAS_PREFIX: &str = "alias:";
21
22impl Serialize for SlackEmojiRef {
23    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24    where
25        S: Serializer,
26    {
27        match self {
28            SlackEmojiRef::Url(url) => serializer.serialize_str(url.as_ref()),
29            SlackEmojiRef::Alias(alias) => {
30                serializer.serialize_str(&format!("{SLACK_EMOJI_ALIAS_PREFIX}{alias}"))
31            }
32        }
33    }
34}
35
36struct SlackEmojiRefVisitor {}
37
38impl SlackEmojiRefVisitor {
39    fn new() -> Self {
40        Self {}
41    }
42}
43
44impl<'de> Visitor<'de> for SlackEmojiRefVisitor {
45    type Value = SlackEmojiRef;
46
47    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
48        formatter.write_str("a Slack custom emoji URL or alias in the form of 'alias:<name>'")
49    }
50
51    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
52    where
53        E: serde::de::Error,
54    {
55        if v.starts_with(SLACK_EMOJI_ALIAS_PREFIX) {
56            let parsed_value: Vec<_> = v.split(':').collect();
57            if parsed_value.len() == 2 {
58                return Ok(SlackEmojiRef::Alias(SlackEmojiName(
59                    parsed_value[1].to_string(),
60                )));
61            }
62        }
63
64        match Url::parse(v) {
65            Ok(url) => Ok(SlackEmojiRef::Url(url)),
66            Err(err) => Err(E::custom(format!("Failed to parse URL '{}': {}", v, err))),
67        }
68    }
69}
70
71impl<'de> Deserialize<'de> for SlackEmojiRef {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: Deserializer<'de>,
75    {
76        deserializer.deserialize_str(SlackEmojiRefVisitor::new())
77    }
78}
79
80#[cfg(test)]
81mod test {
82    use super::*;
83
84    mod serialize {
85        use super::*;
86
87        #[test]
88        fn test_serialize_emoji_url() {
89            let emoji_url = SlackEmojiRef::Url(Url::parse("https://example.com").unwrap());
90            assert_eq!(
91                serde_json::to_string(&emoji_url).unwrap(),
92                "\"https://example.com/\""
93            );
94        }
95
96        #[test]
97        fn test_serialize_emoji_alias() {
98            let emoji_alias = SlackEmojiRef::Alias(SlackEmojiName::new("smile".to_string()));
99            assert_eq!(
100                serde_json::to_string(&emoji_alias).unwrap(),
101                "\"alias:smile\""
102            );
103        }
104    }
105
106    mod deserialize {
107        use super::*;
108
109        #[test]
110        fn test_deserialize_emoji_url() {
111            assert_eq!(
112                serde_json::from_str::<SlackEmojiRef>("\"https://example.com\"").unwrap(),
113                SlackEmojiRef::Url(Url::parse("https://example.com").unwrap())
114            );
115        }
116
117        #[test]
118        fn test_deserialize_emoji_url_with_escaped_slashes() {
119            let emoji_url = r#""https:\/\/emoji.slack-edge.com\/test_emoji.png""#;
120
121            let r = serde_json::from_str::<SlackEmojiRef>(&emoji_url).unwrap();
122            assert_eq!(
123                r,
124                SlackEmojiRef::Url(
125                    Url::parse("https://emoji.slack-edge.com/test_emoji.png").unwrap()
126                )
127            );
128        }
129
130        #[test]
131        fn test_deserialize_emoji_alias() {
132            assert_eq!(
133                serde_json::from_str::<SlackEmojiRef>("\"alias:smile\"").unwrap(),
134                SlackEmojiRef::Alias(SlackEmojiName::new("smile".to_string()))
135            );
136        }
137    }
138}