Skip to main content

slack_morphism/models/common/
icon.rs

1use rsb_derive::Builder;
2use serde::de::{MapAccess, Visitor};
3use serde::ser::SerializeMap;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_with::skip_serializing_none;
6use std::*;
7
8#[skip_serializing_none]
9#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
10pub struct SlackIcon {
11    pub image_original: Option<String>,
12    pub image_default: Option<bool>,
13    #[serde(flatten)]
14    pub images: Option<SlackIconImages>,
15}
16
17#[skip_serializing_none]
18#[derive(Debug, PartialEq, Clone, Builder)]
19pub struct SlackIconImages {
20    pub resolutions: Vec<(u32, String)>,
21}
22
23const SLACK_ICON_JSON_PREFIX: &str = "image_";
24
25impl Serialize for SlackIconImages {
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: Serializer,
29    {
30        if self.resolutions.is_empty() {
31            serializer.serialize_none()
32        } else {
33            let mut res_map = serializer.serialize_map(Some(self.resolutions.len()))?;
34            for (res, link) in &self.resolutions {
35                let key: String = format!("{SLACK_ICON_JSON_PREFIX}{res}");
36                res_map.serialize_entry(&key, link)?;
37            }
38            res_map.end()
39        }
40    }
41}
42
43struct SlackIconImagesVisitor {}
44
45impl SlackIconImagesVisitor {
46    fn new() -> Self {
47        Self {}
48    }
49}
50
51impl<'de> Visitor<'de> for SlackIconImagesVisitor {
52    type Value = SlackIconImages;
53
54    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
55        formatter.write_str("a json contains images_ for icon")
56    }
57
58    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
59    where
60        M: MapAccess<'de>,
61    {
62        let mut resolutions: Vec<(u32, String)> =
63            Vec::with_capacity(access.size_hint().unwrap_or(0));
64
65        while let Some(key) = access.next_key::<String>()? {
66            let resolution = key
67                .strip_prefix(SLACK_ICON_JSON_PREFIX)
68                .and_then(|suffix| suffix.parse::<u32>().ok());
69            match resolution {
70                Some(resolution) => {
71                    let value: String = access.next_value()?;
72                    resolutions.push((resolution, value));
73                }
74                None => {
75                    access.next_value::<serde::de::IgnoredAny>()?;
76                }
77            }
78        }
79
80        Ok(SlackIconImages::new(resolutions))
81    }
82}
83
84impl<'de> Deserialize<'de> for SlackIconImages {
85    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86    where
87        D: Deserializer<'de>,
88    {
89        deserializer.deserialize_map(SlackIconImagesVisitor::new())
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn non_numeric_image_keys_are_skipped() {
99        let images: SlackIconImages = serde_json::from_str(
100            r#"{"image_36":"https://example.com/36.png","image_original":"https://example.com/original.png","image_default":true,"image_72":"https://example.com/72.png"}"#,
101        )
102        .unwrap();
103
104        assert_eq!(
105            images.resolutions,
106            vec![
107                (36, "https://example.com/36.png".to_string()),
108                (72, "https://example.com/72.png".to_string()),
109            ]
110        );
111    }
112
113    #[test]
114    fn unrelated_keys_are_skipped() {
115        let images: SlackIconImages = serde_json::from_str(
116            r#"{"emoji":":robot_face:","image_48":"https://example.com/48.png","deleted":false}"#,
117        )
118        .unwrap();
119
120        assert_eq!(
121            images.resolutions,
122            vec![(48, "https://example.com/48.png".to_string())]
123        );
124    }
125}