slack_morphism/models/common/
emoji.rs1use std::fmt;
2
3use rvstruct::ValueStruct;
4use serde::{
5 de::{MapAccess, Visitor},
6 Deserialize, Deserializer, Serialize, Serializer,
7};
8use url::Url;
9
10#[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 let Some(name) = v.strip_prefix(SLACK_EMOJI_ALIAS_PREFIX) {
56 if !name.contains(':') {
57 return Ok(SlackEmojiRef::Alias(SlackEmojiName(name.to_string())));
58 }
59 }
60
61 match Url::parse(v) {
62 Ok(url) => Ok(SlackEmojiRef::Url(url)),
63 Err(err) => Err(E::custom(format!("Failed to parse URL '{}': {}", v, err))),
64 }
65 }
66}
67
68impl<'de> Deserialize<'de> for SlackEmojiRef {
69 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70 where
71 D: Deserializer<'de>,
72 {
73 deserializer.deserialize_str(SlackEmojiRefVisitor::new())
74 }
75}
76
77#[cfg(test)]
78mod test {
79 use super::*;
80
81 mod serialize {
82 use super::*;
83
84 #[test]
85 fn test_serialize_emoji_url() {
86 let emoji_url = SlackEmojiRef::Url(Url::parse("https://example.com").unwrap());
87 assert_eq!(
88 serde_json::to_string(&emoji_url).unwrap(),
89 "\"https://example.com/\""
90 );
91 }
92
93 #[test]
94 fn test_serialize_emoji_alias() {
95 let emoji_alias = SlackEmojiRef::Alias(SlackEmojiName::new("smile".to_string()));
96 assert_eq!(
97 serde_json::to_string(&emoji_alias).unwrap(),
98 "\"alias:smile\""
99 );
100 }
101 }
102
103 mod deserialize {
104 use super::*;
105
106 #[test]
107 fn test_deserialize_emoji_url() {
108 assert_eq!(
109 serde_json::from_str::<SlackEmojiRef>("\"https://example.com\"").unwrap(),
110 SlackEmojiRef::Url(Url::parse("https://example.com").unwrap())
111 );
112 }
113
114 #[test]
115 fn test_deserialize_emoji_url_with_escaped_slashes() {
116 let emoji_url = r#""https:\/\/emoji.slack-edge.com\/test_emoji.png""#;
117
118 let r = serde_json::from_str::<SlackEmojiRef>(emoji_url).unwrap();
119 assert_eq!(
120 r,
121 SlackEmojiRef::Url(
122 Url::parse("https://emoji.slack-edge.com/test_emoji.png").unwrap()
123 )
124 );
125 }
126
127 #[test]
128 fn test_deserialize_emoji_alias() {
129 assert_eq!(
130 serde_json::from_str::<SlackEmojiRef>("\"alias:smile\"").unwrap(),
131 SlackEmojiRef::Alias(SlackEmojiName::new("smile".to_string()))
132 );
133 }
134 }
135}