rosu_v2/model/
forum.rs

1use std::fmt;
2
3use serde::{
4    de::{Deserializer, Error, IgnoredAny, MapAccess, Visitor},
5    Deserialize,
6};
7use time::OffsetDateTime;
8
9use super::{serde_util, CacheUserFn, ContainedUsers};
10
11#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
12#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
13pub struct ForumPosts {
14    #[serde(
15        default,
16        rename = "cursor_string",
17        skip_serializing_if = "Option::is_none"
18    )]
19    pub cursor: Option<String>,
20    pub posts: Vec<ForumPost>,
21    pub search: ForumPostsSearch,
22    pub topic: ForumTopic,
23}
24
25impl ForumPosts {
26    /// Checks whether the cursor field is `Some` which in turn
27    /// can be used to retrieve the next set of posts.
28    ///
29    /// The next set can then be retrieved by providing this cursor to
30    /// [`GetForumPosts::cursor`](crate::request::GetForumPosts::cursor).
31    /// Be sure all other parameters stay the same.
32    #[inline]
33    pub const fn has_more(&self) -> bool {
34        self.cursor.is_some()
35    }
36}
37
38impl ContainedUsers for ForumPosts {
39    fn apply_to_users(&self, _: impl CacheUserFn) {}
40}
41
42#[derive(Clone, Debug)]
43#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
44pub struct ForumPost {
45    #[cfg_attr(feature = "serialize", serde(with = "serde_util::datetime"))]
46    pub created_at: OffsetDateTime,
47    #[cfg_attr(
48        feature = "serialize",
49        serde(
50            default,
51            skip_serializing_if = "Option::is_none",
52            with = "serde_util::option_datetime"
53        )
54    )]
55    pub deleted_at: Option<OffsetDateTime>,
56    #[cfg_attr(
57        feature = "serialize",
58        serde(
59            default,
60            skip_serializing_if = "Option::is_none",
61            with = "serde_util::option_datetime"
62        )
63    )]
64    pub edited_at: Option<OffsetDateTime>,
65    #[cfg_attr(feature = "serialize", serde(skip_serializing_if = "Option::is_none"))]
66    pub edited_by_id: Option<u32>,
67    pub forum_id: u32,
68    /// Post content in HTML format
69    pub html: String,
70    #[cfg_attr(feature = "serialize", serde(rename = "id"))]
71    pub post_id: u64,
72    /// Post content in `BBCode` format
73    pub raw: String,
74    pub topic_id: u64,
75    pub user_id: u32,
76}
77
78struct ForumPostVisitor;
79
80#[derive(Deserialize)]
81struct ForumPostBody {
82    html: String,
83    raw: String,
84}
85
86impl<'de> Visitor<'de> for ForumPostVisitor {
87    type Value = ForumPost;
88
89    #[inline]
90    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.write_str("a ForumPost struct")
92    }
93
94    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
95        #[derive(Deserialize)]
96        struct DateTimeWrapper(#[serde(with = "serde_util::datetime")] OffsetDateTime);
97
98        #[derive(Deserialize)]
99        struct OptionDateTimeWrapper(
100            #[serde(with = "serde_util::option_datetime")] Option<OffsetDateTime>,
101        );
102
103        let mut created_at: Option<DateTimeWrapper> = None;
104        let mut deleted_at: Option<OptionDateTimeWrapper> = None;
105        let mut edited_at: Option<OptionDateTimeWrapper> = None;
106        let mut edited_by_id = None;
107        let mut forum_id = None;
108        let mut html = None;
109        let mut post_id = None;
110        let mut raw = None;
111        let mut topic_id = None;
112        let mut user_id = None;
113
114        while let Some(key) = map.next_key()? {
115            match key {
116                "body" => {
117                    let body: ForumPostBody = map.next_value()?;
118
119                    html = Some(body.html);
120                    raw = Some(body.raw);
121                }
122                "created_at" => created_at = Some(map.next_value()?),
123                "deleted_at" => deleted_at = Some(map.next_value()?),
124                "edited_at" => edited_at = Some(map.next_value()?),
125                "edited_by_id" => edited_by_id = map.next_value()?,
126                "forum_id" => forum_id = Some(map.next_value()?),
127                "html" => html = Some(map.next_value()?),
128                "id" => post_id = Some(map.next_value()?),
129                "raw" => raw = Some(map.next_value()?),
130                "topic_id" => topic_id = Some(map.next_value()?),
131                "user_id" => user_id = Some(map.next_value()?),
132                _ => {
133                    let _: IgnoredAny = map.next_value()?;
134                }
135            }
136        }
137
138        let DateTimeWrapper(created_at) =
139            created_at.ok_or_else(|| Error::missing_field("created_at"))?;
140        let forum_id = forum_id.ok_or_else(|| Error::missing_field("forum_id"))?;
141        let html = html.ok_or_else(|| Error::missing_field("body or html"))?;
142        let post_id = post_id.ok_or_else(|| Error::missing_field("id"))?;
143        let raw = raw.ok_or_else(|| Error::missing_field("body or raw"))?;
144        let topic_id = topic_id.ok_or_else(|| Error::missing_field("topic_id"))?;
145        let user_id = user_id.ok_or_else(|| Error::missing_field("user_id"))?;
146
147        Ok(ForumPost {
148            created_at,
149            deleted_at: deleted_at.and_then(|wrapper| wrapper.0),
150            edited_at: edited_at.and_then(|wrapper| wrapper.0),
151            edited_by_id,
152            forum_id,
153            html,
154            post_id,
155            raw,
156            topic_id,
157            user_id,
158        })
159    }
160}
161
162impl<'de> Deserialize<'de> for ForumPost {
163    #[inline]
164    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
165        d.deserialize_map(ForumPostVisitor)
166    }
167}
168
169impl PartialEq for ForumPost {
170    #[inline]
171    fn eq(&self, other: &Self) -> bool {
172        self.post_id == other.post_id && self.edited_at == other.edited_at
173    }
174}
175
176impl Eq for ForumPost {}
177
178#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
179#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
180pub struct ForumPostsSearch {
181    pub limit: u32,
182    pub sort: String,
183}
184
185#[derive(Clone, Debug, Deserialize)]
186#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
187pub struct ForumTopic {
188    #[serde(with = "serde_util::datetime")]
189    pub created_at: OffsetDateTime,
190    #[serde(
191        default,
192        skip_serializing_if = "Option::is_none",
193        with = "serde_util::option_datetime"
194    )]
195    pub deleted_at: Option<OffsetDateTime>,
196    pub first_post_id: u64,
197    pub forum_id: u32,
198    pub is_locked: bool,
199    #[serde(rename = "type")]
200    pub kind: String, // TODO
201    pub last_post_id: u64,
202    pub post_count: u32,
203    pub title: String,
204    #[serde(rename = "id")]
205    pub topic_id: u64,
206    #[serde(
207        default,
208        skip_serializing_if = "Option::is_none",
209        with = "serde_util::option_datetime"
210    )]
211    pub updated_at: Option<OffsetDateTime>,
212    pub user_id: u32,
213}
214
215impl PartialEq for ForumTopic {
216    #[inline]
217    fn eq(&self, other: &Self) -> bool {
218        self.topic_id == other.topic_id && self.updated_at == other.updated_at
219    }
220}
221
222impl Eq for ForumTopic {}