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 #[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 pub html: String,
70 #[cfg_attr(feature = "serialize", serde(rename = "id"))]
71 pub post_id: u64,
72 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, 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 {}