reddit-rs 0.1.1

A wrapper around the Reddit API.
Documentation
use chrono::{DateTime, Utc};
use serde::{
    de::{Error, Unexpected},
    Deserialize, Deserializer, Serialize, Serializer,
};

#[derive(Debug, Serialize)]
pub(crate) struct RedditAuth {
    pub(crate) access_token: String,
    pub(crate) token_type: Bearer,
    pub(crate) expires_at: DateTime<Utc>,
    pub(crate) scope: String,
}

impl<'de> Deserialize<'de> for RedditAuth {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Debug, Serialize, Deserialize)]
        #[serde(untagged)]
        enum Proxy {
            /// reddit provides an expires_in parameter, which we store
            /// internally as an expiry datetime
            FromReddit {
                access_token: String,
                token_type: Bearer,
                expires_in: u32,
                scope: String,
            },
            /// serialization writes the expiry datetime to file
            FromFile {
                access_token: String,
                token_type: Bearer,
                expires_at: DateTime<Utc>,
                scope: String,
            },
        }

        let proxy = Proxy::deserialize(deserializer)?;

        match proxy {
            Proxy::FromReddit {
                access_token,
                token_type,
                expires_in,
                scope,
            } => Ok(Self {
                access_token,
                scope,
                expires_at: Utc::now()
                    .checked_add_signed(chrono::Duration::seconds(i64::from(expires_in)))
                    .expect("datetime overflowed for auth; this should be unreachable."),
                token_type,
            }),
            Proxy::FromFile {
                access_token,
                token_type,
                expires_at,
                scope,
            } => Ok(Self {
                access_token,
                token_type,
                expires_at,
                scope,
            }),
        }
    }
}

#[derive(Debug)]
pub(crate) struct Bearer;

impl Serialize for Bearer {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str("bearer")
    }
}

impl<'de> Deserialize<'de> for Bearer {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let string = String::deserialize(deserializer)?;
        match &*string {
            "bearer" => Ok(Self),
            _ => Err(D::Error::invalid_value(
                Unexpected::Other(&string),
                &"the string \"bearer\"",
            )),
        }
    }
}