1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use super::{comment::Comment, post::Post, subreddit_info::SubredditInfo, user::UserInfo};
use serde::{Deserialize, Serialize};
use serde_json::Value;

/// Represents a generic Reddit data container.
///
/// Reddit API uses a special structure to represent varying types of object in a uniform way.
/// This struct is actually the heart of the Reddit response parsing.
///
/// ### How does it work?
/// It's kind of a declarative way to parse the response. The [KindContainer] struct described the universal structure of every Reddit response.
/// Reddit uses the following structure to let us know what type of data to expect:
/// ```json
/// {
///     "kind" : "t1",
///     "data" : {
///         <Comment data>
///     }
/// }
/// ```
/// So, we have a `kind` field and the corresponding `data` field, which has an object of the type indicated by `kind`.
///
/// [Serde](https://serde.rs) provides a convenient macro, that lets us describe how our enum should be handled in serialization/deserialization:
/// ```rust
/// #[serde(tag = "kind", content = "data")]
/// enum Foo {}
/// ```
/// When used like in the example above, this macro lets us represent our exact structure: object tagged by `kind` with the content in `data`.
/// Content is the value embedded in the enum variant, eg. `Box<RedditListing>`.
///
/// Each field uses the `#[serde(rename = "foo")]` macro, to tell [Serde](https://serde.rs) to look for that given name during its operations.
/// This paired with the previously described tagging, lets us use the [KindContainer] struct to recrusively, automatically and reliably parse all Reddit responses.
///
/// ### Example
/// Probably not accurate execution order, written just to understand the process.
/// 1. We tell serde to deserialize some JSON data into KindContainer.
/// 2. Serde looks for the `kind` field.
/// 3. Let's assume `kind: "t1"`.
/// 4. Since `kind` is `"t1"`, serde knows that the enum variant it encountered is KindContainer::T1(Box<Comment>)
/// 5. Serde finds the `data` field and treats it as the content of type Box<Comment>
/// 6. [Box] is transparent to serde, so it parses the `data` field as a [Comment](super::comment:Comment)
///
/// ### Recursive case
/// For the [KindContainer::Listing], this works recursively, since [RedditListing] is basically a list of [KindContainer]s.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "kind", content = "data")]
pub enum KindContainer {
    #[serde(rename = "Listing")]
    Listing(Box<RedditListing>),

    #[serde(rename = "more")]
    More(Box<MoreData>),

    #[serde(rename = "t1")]
    T1(Box<Comment>),

    #[serde(rename = "t2")]
    T2(Box<UserInfo>),

    #[serde(rename = "t3")]
    T3(Box<Post>), // Link/Post

    #[serde(rename = "t4")]
    T4(Box<Value>), // Message TODO

    #[serde(rename = "t5")]
    T5(Box<SubredditInfo>),

    #[serde(rename = "t6")]
    T6(Box<Value>), // Award TODO
}

/// List of IDs of items to fetch to get the ones that didn't fit in the first response.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MoreData {
    count: u32,
    name: String,
    id: String,
    parent_id: String,
    depth: u32,
    children: Vec<String>,
}

/// Describes a Reddit listing
///
/// It's a list of items with some metadata about the list and how to fetch more entries.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RedditListing {
    after: Option<String>,
    dist: Option<u32>,
    children: Vec<KindContainer>,
    before: Option<String>,
}