slack-morphism 2.31.1

Slack Morphism is a modern client library for Slack Web/Events API/Socket Mode and Block Kit
Documentation
use rsb_derive::Builder;
use serde::de::{MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::skip_serializing_none;
use std::*;

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackIcon {
    pub image_original: Option<String>,
    pub image_default: Option<bool>,
    #[serde(flatten)]
    pub images: Option<SlackIconImages>,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Builder)]
pub struct SlackIconImages {
    pub resolutions: Vec<(u32, String)>,
}

const SLACK_ICON_JSON_PREFIX: &str = "image_";

impl Serialize for SlackIconImages {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        if self.resolutions.is_empty() {
            serializer.serialize_none()
        } else {
            let mut res_map = serializer.serialize_map(Some(self.resolutions.len()))?;
            for (res, link) in &self.resolutions {
                let key: String = format!("{SLACK_ICON_JSON_PREFIX}{res}");
                res_map.serialize_entry(&key, link)?;
            }
            res_map.end()
        }
    }
}

struct SlackIconImagesVisitor {}

impl SlackIconImagesVisitor {
    fn new() -> Self {
        Self {}
    }
}

impl<'de> Visitor<'de> for SlackIconImagesVisitor {
    type Value = SlackIconImages;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a json contains images_ for icon")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        let mut resolutions: Vec<(u32, String)> =
            Vec::with_capacity(access.size_hint().unwrap_or(0));

        while let Some(key) = access.next_key::<String>()? {
            let resolution = key
                .strip_prefix(SLACK_ICON_JSON_PREFIX)
                .and_then(|suffix| suffix.parse::<u32>().ok());
            match resolution {
                Some(resolution) => {
                    let value: String = access.next_value()?;
                    resolutions.push((resolution, value));
                }
                None => {
                    access.next_value::<serde::de::IgnoredAny>()?;
                }
            }
        }

        Ok(SlackIconImages::new(resolutions))
    }
}

impl<'de> Deserialize<'de> for SlackIconImages {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(SlackIconImagesVisitor::new())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn non_numeric_image_keys_are_skipped() {
        let images: SlackIconImages = serde_json::from_str(
            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"}"#,
        )
        .unwrap();

        assert_eq!(
            images.resolutions,
            vec![
                (36, "https://example.com/36.png".to_string()),
                (72, "https://example.com/72.png".to_string()),
            ]
        );
    }

    #[test]
    fn unrelated_keys_are_skipped() {
        let images: SlackIconImages = serde_json::from_str(
            r#"{"emoji":":robot_face:","image_48":"https://example.com/48.png","deleted":false}"#,
        )
        .unwrap();

        assert_eq!(
            images.resolutions,
            vec![(48, "https://example.com/48.png".to_string())]
        );
    }
}