reddit_rs/models/
auth.rs

1use chrono::{DateTime, Utc};
2use serde::{
3    de::{Error, Unexpected},
4    Deserialize, Deserializer, Serialize, Serializer,
5};
6
7#[derive(Debug, Serialize)]
8pub(crate) struct RedditAuth {
9    pub(crate) access_token: String,
10    pub(crate) token_type: Bearer,
11    pub(crate) expires_at: DateTime<Utc>,
12    pub(crate) scope: String,
13}
14
15impl<'de> Deserialize<'de> for RedditAuth {
16    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
17    where
18        D: Deserializer<'de>,
19    {
20        #[derive(Debug, Serialize, Deserialize)]
21        #[serde(untagged)]
22        enum Proxy {
23            /// reddit provides an expires_in parameter, which we store
24            /// internally as an expiry datetime
25            FromReddit {
26                access_token: String,
27                token_type: Bearer,
28                expires_in: u32,
29                scope: String,
30            },
31            /// serialization writes the expiry datetime to file
32            FromFile {
33                access_token: String,
34                token_type: Bearer,
35                expires_at: DateTime<Utc>,
36                scope: String,
37            },
38        }
39
40        let proxy = Proxy::deserialize(deserializer)?;
41
42        match proxy {
43            Proxy::FromReddit {
44                access_token,
45                token_type,
46                expires_in,
47                scope,
48            } => Ok(Self {
49                access_token,
50                scope,
51                expires_at: Utc::now()
52                    .checked_add_signed(chrono::Duration::seconds(i64::from(expires_in)))
53                    .expect("datetime overflowed for auth; this should be unreachable."),
54                token_type,
55            }),
56            Proxy::FromFile {
57                access_token,
58                token_type,
59                expires_at,
60                scope,
61            } => Ok(Self {
62                access_token,
63                token_type,
64                expires_at,
65                scope,
66            }),
67        }
68    }
69}
70
71#[derive(Debug)]
72pub(crate) struct Bearer;
73
74impl Serialize for Bearer {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: Serializer,
78    {
79        serializer.serialize_str("bearer")
80    }
81}
82
83impl<'de> Deserialize<'de> for Bearer {
84    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85    where
86        D: Deserializer<'de>,
87    {
88        let string = String::deserialize(deserializer)?;
89        match &*string {
90            "bearer" => Ok(Self),
91            _ => Err(D::Error::invalid_value(
92                Unexpected::Other(&string),
93                &"the string \"bearer\"",
94            )),
95        }
96    }
97}