reddit-rs 0.1.1

A wrapper around the Reddit API.
Documentation
use serde::{
    de::{Error, Unexpected},
    Deserialize, Serialize,
};
use uuid::Uuid;

use crate::models::{
    color::Color,
    flair::{FlairTextColor, FlairType},
    fullname::FullName,
    richtext::Richtext,
};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Author {
    #[serde(flatten)]
    pub author_info: AuthorInfo,
    #[serde(rename = "author_is_blocked")]
    pub is_blocked: bool,
    #[serde(flatten)]
    pub flair: AuthorFlair,
}

#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum AuthorInfo {
    Deleted,
    Author {
        #[serde(rename = "author")]
        name: String,
        #[serde(rename = "author_fullname")]
        fullname: FullName,
        #[serde(rename = "author_premium")]
        premium: bool,
        #[serde(rename = "author_patreon_flair")]
        patreon_flair: bool,
    },
}

impl<'de> Deserialize<'de> for AuthorInfo {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Debug, Deserialize)]
        struct RawAuthorInfo {
            #[serde(rename = "author")]
            name: String,
            #[serde(rename = "author_fullname")]
            fullname: Option<FullName>,
            #[serde(rename = "author_premium")]
            premium: Option<bool>,
            #[serde(rename = "author_patreon_flair")]
            patreon_flair: Option<bool>,
        }

        let info: RawAuthorInfo =
            serde_json::from_value(serde_json::Value::deserialize(deserializer)?)
                .map_err(D::Error::custom)?;

        match info {
            RawAuthorInfo {
                name,
                fullname: None,
                premium: None,
                patreon_flair: None,
            } if name == "[deleted]" => Ok(AuthorInfo::Deleted),
            RawAuthorInfo { name, .. } if name == "[deleted]" => Err(D::Error::invalid_value(
                Unexpected::Other(
                    "[deleted] requires that all other author info fields are either null or not present",
                ),
                &"[deleted] requires that all other author info fields are either null or not present",
            )),
            RawAuthorInfo {
                name,
                fullname: Some(fullname),
                premium: Some(premium),
                patreon_flair: Some(patreon_flair),
            } => Ok(AuthorInfo::Author {
                name,
                fullname,
                patreon_flair,
                premium,
            }),
            _ => Err(D::Error::invalid_value(
                Unexpected::Other(
                    "author with name requires that all other author info fields are present",
                ),
                &"author with name requires that all other author info fields are present",
            )),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AuthorFlair {
    #[serde(deserialize_with = "Color::from_hex_str")]
    #[serde(rename = "author_flair_background_color")]
    pub background_color: Option<Color>,

    #[serde(rename = "author_flair_template_id")]
    pub template_id: Option<AuthorFlairTemplateId>,

    #[serde(rename = "author_flair_css_class")]
    pub css_class: Option<String>,

    #[serde(rename = "author_flair_richtext")]
    pub richtext: Option<Vec<Richtext>>,

    #[serde(with = "serde_with::rust::string_empty_as_none")]
    #[serde(rename = "author_flair_text_color")]
    pub text_color: Option<FlairTextColor>,

    #[serde(rename = "author_flair_type")]
    pub ty: Option<FlairType>,

    #[serde(rename = "author_flair_text")]
    pub text: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct AuthorFlairTemplateId(Uuid);

#[cfg(test)]
mod test_author {
    #![allow(clippy::non_ascii_literal)] // testing non-ascii deserialization

    use super::*;

    #[test]
    fn test_deserialize() {
        let json = r##"
        {
            "author": "dermeister1985",
            "author_flair_background_color": "#dadada",
            "author_flair_css_class": "flazy",
            "author_flair_richtext": [
              {
                "e": "text",
                "t": "лл"
              }
            ],
            "author_flair_template_id": "3e1b76b8-6266-11e9-ad04-0e5925592ac0",
            "author_flair_text": "лл",
            "author_flair_text_color": "dark",
            "author_flair_type": "richtext",
            "author_fullname": "t2_3mdzj3s7",
            "author_is_blocked": false,
            "author_patreon_flair": false,
            "author_premium": false
        }"##;

        dbg!(serde_json::from_str::<Author>(json).unwrap());
    }
}