1use serde::{Deserialize, Deserializer, Serialize};
2use serde_json::Value;
3
4use crate::impl_str_enum;
5use crate::model::user::Metal;
6use crate::model::{bool_from_int, bool_from_zero, deserialize_sys_metal};
7use crate::utils::error::Error;
8
9fn normalize_float_numbers(value: &mut Value) {
10 match value {
11 Value::Array(arr) => {
12 for item in arr {
13 normalize_float_numbers(item);
14 }
15 }
16 Value::Object(map) => {
17 for v in map.values_mut() {
18 normalize_float_numbers(v);
19 }
20 }
21 Value::Number(num) => {
22 if num.as_u64().is_none()
23 && num.as_i64().is_none()
24 && let Some(f) = num.as_f64()
25 && f.is_finite()
26 {
27 let n = if f <= 0.0 { 0 } else { f.trunc() as u64 };
28 *value = Value::Number(serde_json::Number::from(n));
29 }
30 }
31 _ => {}
32 }
33}
34
35fn parse_with_float_fallback<T>(data: &Value, type_name: &str) -> Result<T, Error>
36where
37 T: for<'de> Deserialize<'de>,
38{
39 match serde_json::from_value::<T>(data.clone()) {
40 Ok(v) => Ok(v),
41 Err(first_err) => {
42 let mut normalized = data.clone();
43 normalize_float_numbers(&mut normalized);
44 serde_json::from_value::<T>(normalized).map_err(|second_err| {
45 Error::Parse(format!(
46 "Failed to parse {}: {} (fallback after float-normalize also failed: {})",
47 type_name, first_err, second_err
48 ))
49 })
50 }
51 }
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
56#[allow(non_snake_case)]
57pub struct ArticlePost {
58 #[serde(rename = "articleTitle")]
60 pub title: String,
61 #[serde(rename = "articleContent")]
63 pub content: String,
64 #[serde(rename = "articleTags")]
66 pub tags: String,
67 #[serde(rename = "articleCommentable")]
69 pub commentable: bool,
70 #[serde(rename = "articleNotifyFollowers")]
72 pub notifyFollowers: bool,
73 #[serde(rename = "articleType")]
75 pub type_: ArticleType,
76 #[serde(rename = "articleShowInList")]
78 pub showInList: u32,
79 #[serde(rename = "articleRewardContent")]
81 pub rewardContent: Option<String>,
82 #[serde(rename = "articleRewardPoint")]
84 pub rewardPoint: Option<String>,
85 #[serde(rename = "articleAnonymous")]
87 pub anonymous: Option<bool>,
88 #[serde(rename = "articleQnAOfferPoint")]
90 pub offerPoint: Option<u32>,
91}
92
93impl ArticlePost {
94 pub fn from_value(data: &Value) -> Result<Self, Error> {
95 serde_json::from_value(data.clone())
96 .map_err(|e| Error::Parse(format!("Failed to parse ArticlePost: {}", e)))
97 }
98
99 pub fn to_json(&self) -> Result<Value, Error> {
100 serde_json::to_value(self)
101 .map_err(|e| Error::Parse(format!("Failed to serialize ArticlePost: {}", e)))
102 }
103}
104
105#[derive(Clone, Debug, Deserialize)]
107#[allow(non_snake_case)]
108pub struct ArticleTag {
109 pub oId: String,
111 #[serde(rename = "tagTitle")]
113 pub title: String,
114 #[serde(rename = "tagDescription")]
116 pub description: String,
117 #[serde(rename = "tagIconPath")]
119 pub iconPath: String,
120 #[serde(rename = "tagURI")]
122 pub uri: String,
123 #[serde(rename = "tagCSS")]
125 pub diyCSS: String,
126 #[serde(rename = "tagBadCnt")]
128 pub badCnt: u64,
129 #[serde(rename = "tagCommentCount")]
131 pub commentCnt: u32,
132 #[serde(rename = "tagFollowerCount")]
134 pub followerCnt: u32,
135 #[serde(rename = "tagGoodCnt")]
137 pub goodCnt: u64,
138 #[serde(rename = "tagReferenceCount")]
140 pub referenceCnt: u32,
141 #[serde(rename = "tagLinkCount")]
143 pub linkCnt: u32,
144 #[serde(rename = "tagSeoDesc")]
146 pub seoDesc: String,
147 #[serde(rename = "tagSeoKeywords")]
149 pub seoKeywords: String,
150 #[serde(rename = "tagSeoTitle")]
152 pub seoTitle: String,
153 #[serde(rename = "tagAd")]
155 pub tagAd: String,
156 #[serde(rename = "tagShowSideAd")]
158 pub showSideAd: u32,
159 #[serde(rename = "tagStatus")]
161 pub status: u32,
162 #[serde(rename = "tagRandomDouble")]
164 pub randomDouble: f64,
165}
166
167impl ArticleTag {
168 pub fn from_value(data: &Value) -> Result<Self, Error> {
169 serde_json::from_value(data.clone())
170 .map_err(|e| Error::Parse(format!("Failed to parse ArticleTag: {}", e)))
171 }
172}
173
174#[derive(Clone, Debug)]
176#[derive(Default)]
177pub enum VoteStatus {
178 #[default]
180 Normal,
181 Up,
183 Down,
185}
186
187impl VoteStatus {
188 pub fn from_index(index: usize) -> Self {
189 match index {
190 1 => VoteStatus::Up,
191 2 => VoteStatus::Down,
192 _ => VoteStatus::Normal,
193 }
194 }
195}
196
197
198#[derive(Clone, Debug)]
200pub enum ArticleStatus {
201 Normal,
203
204 Ban,
206
207 Lock,
209}
210
211impl ArticleStatus {
212 pub fn from_index(index: usize) -> Self {
213 match index {
214 0 => ArticleStatus::Normal,
215 1 => ArticleStatus::Ban,
216 _ => ArticleStatus::Lock, }
218 }
219}
220
221impl Default for ArticleStatus {
222 fn default() -> Self {
223 Self::Normal
224 }
225}
226
227pub fn deserialize_score<'de, D>(deserializer: D) -> Result<String, D::Error>
228where
229 D: Deserializer<'de>,
230{
231 let value: u64 = Deserialize::deserialize(deserializer)?;
232 Ok(value.to_string())
233}
234
235pub fn deserialize_vote<'de, D>(deserializer: D) -> Result<VoteStatus, D::Error>
236where
237 D: Deserializer<'de>,
238{
239 let value: i64 = Deserialize::deserialize(deserializer)?;
240 Ok(VoteStatus::from_index((value + 1) as usize))
241}
242
243pub fn deserialize_status<'de, D>(deserializer: D) -> Result<ArticleStatus, D::Error>
244where
245 D: Deserializer<'de>,
246{
247 let value: u64 = Deserialize::deserialize(deserializer)?;
248 Ok(ArticleStatus::from_index(value as usize))
249}
250
251#[derive(Clone, Debug, Default, Deserialize)]
252#[serde(default)]
253#[allow(non_snake_case)]
254pub struct ArticleAuthor {
255 pub isOnline: bool,
257 pub onlineMinute: u64,
259 #[serde(deserialize_with = "bool_from_zero")]
261 pub pointStatus: bool,
262 #[serde(deserialize_with = "bool_from_zero")]
264 pub followerStatus: bool,
265 pub guideStep: u64,
267 #[serde(deserialize_with = "bool_from_zero")]
269 pub onlineStatus: bool,
270 pub currentCheckinStreakStart: u64,
272 #[serde(deserialize_with = "bool_from_int")] pub isAutoBlur: bool,
275 pub tags: String,
277 #[serde(deserialize_with = "bool_from_zero")]
279 pub commentStatus: bool,
280 pub timezone: String,
282 pub homePage: String,
284 #[serde(deserialize_with = "bool_from_int")] pub isEnableForwardPage: bool,
287 #[serde(deserialize_with = "bool_from_zero")]
289 pub userUAStatus: bool,
290 pub userIndexRedirectURL: String,
292 pub latestArticleTime: u64,
294 pub tagCount: u64,
296 pub nickname: String,
298 pub listViewMode: u64,
300 pub longestCheckinStreak: u64,
302 pub avatarType: String,
304 pub subMailSendTime: u64,
306 pub updateTime: u64,
308 #[serde(deserialize_with = "bool_from_zero")]
310 pub subMailStatus: bool,
311 #[serde(deserialize_with = "bool_from_zero")]
313 pub isJoinPointRank: bool,
314 pub latestLoginTime: u64,
316 pub userAppRole: u64,
318 pub userAvatarViewMode: u64,
320 pub userStatus: u64,
322 pub longestCheckinStreakEnd: u64,
324 #[serde(deserialize_with = "bool_from_zero")]
326 pub watchingArticleStatus: bool,
327 pub latestCmtTime: u64,
329 pub province: String,
331 pub currentCheckinStreak: u64,
333 pub userNo: u64,
335 pub avatarURL: String,
337 #[serde(deserialize_with = "bool_from_zero")]
339 pub followingTagStatus: bool,
340 pub userLanguage: String,
342 #[serde(deserialize_with = "bool_from_zero")]
344 pub isJoinUsedPointRank: bool,
345 pub currentCheckinStreakEnd: u64,
347 #[serde(deserialize_with = "bool_from_zero")]
349 pub followingArticleStatus: bool,
350 #[serde(deserialize_with = "bool_from_zero")]
352 pub keyboardShortcutsStatus: bool,
353 #[serde(deserialize_with = "bool_from_zero")]
355 pub replyWatchArticleStatus: bool,
356 pub commentViewMode: u64,
358 #[serde(deserialize_with = "bool_from_zero")]
360 pub breezemoonStatus: bool,
361 pub userCheckinTime: u64,
363 pub usedPoint: u64,
365 #[serde(deserialize_with = "bool_from_zero")]
367 pub articleStatus: bool,
368 pub userPoint: u64,
370 pub commentCount: u64,
372 pub userIntro: String,
374 pub userMobileSkin: String,
376 pub listPageSize: u64,
378 pub oId: String,
380 pub userName: String,
382 #[serde(deserialize_with = "bool_from_zero")]
384 pub geoStatus: bool,
385 pub longestCheckinStreakStart: u64,
387 pub userSkin: String,
389 #[serde(deserialize_with = "bool_from_zero")]
391 pub notifyStatus: bool,
392 #[serde(deserialize_with = "bool_from_zero")]
394 pub followingUserStatus: bool,
395 pub articleCount: u64,
397 pub userRole: String,
399 #[serde(deserialize_with = "deserialize_sys_metal")]
401 pub sysMetal: Vec<Metal>,
402}
403
404impl ArticleAuthor {
405 pub fn from_value(data: &Value) -> Result<Self, Error> {
406 parse_with_float_fallback(data, "ArticleAuthor")
407 }
408}
409
410pub type CommentAuthor = ArticleAuthor;
412
413#[derive(Clone, Debug, Default, Deserialize)]
414#[serde(default)]
415#[allow(non_snake_case)]
416pub struct ArticleComment {
417 #[serde(rename = "commentNice")]
419 pub isNice: bool,
420 #[serde(rename = "commentCreateTimeStr")]
422 pub createTimeStr: String,
423 #[serde(rename = "commentAuthorId")]
425 pub authorId: String,
426 #[serde(deserialize_with = "deserialize_score")]
428 pub score: String,
429 #[serde(rename = "commentCreateTime")]
431 pub createTime: String,
432 #[serde(rename = "commentAuthorURL")]
434 pub authorURL: String,
435 #[serde(deserialize_with = "deserialize_vote")]
437 pub vote: VoteStatus,
438 #[serde(rename = "commentRevisionCount")]
440 pub revisionCount: u64,
441 #[serde(rename = "timeAgo")]
443 pub timeAgo: String,
444 #[serde(rename = "commentOriginalCommentId")]
446 pub replyId: String,
447 #[serde(deserialize_with = "deserialize_sys_metal")]
449 pub sysMetal: Vec<Metal>,
450 #[serde(rename = "commentGoodCnt")]
452 pub goodCnt: u64,
453 #[serde(deserialize_with = "bool_from_zero")]
455 pub visible: bool,
456 #[serde(rename = "commentOnArticleId")]
458 pub articleId: String,
459 #[serde(rename = "rewardedCnt")]
461 pub rewardedCnt: u64,
462 #[serde(rename = "commentSharpURL")]
464 pub sharpURL: String,
465 #[serde(deserialize_with = "bool_from_int")]
467 pub isAnonymous: bool,
468 #[serde(rename = "commentReplyCnt")]
470 pub replyCnt: u64,
471 #[serde(rename = "oId")]
473 pub oId: String,
474 #[serde(rename = "commentContent")]
476 pub content: String,
477 #[serde(deserialize_with = "deserialize_status")]
479 pub status: ArticleStatus,
480 pub commenter: CommentAuthor,
482 #[serde(rename = "commentAuthorName")]
484 pub author: String,
485 #[serde(rename = "commentThankCnt")]
487 pub thankCnt: u64,
488 #[serde(rename = "commentBadCnt")]
490 pub badCnt: u64,
491 #[serde(rename = "rewarded")]
493 pub rewarded: bool,
494 #[serde(rename = "commentAuthorThumbnailURL")]
496 pub thumbnailURL: String,
497 #[serde(rename = "commentAudioURL")]
499 pub audioURL: String,
500 #[serde(rename = "commentQnAOffered")]
502 pub offered: u64,
503}
504
505impl ArticleComment {
506 pub fn from_value(data: &Value) -> Result<Self, Error> {
507 parse_with_float_fallback(data, "ArticleComment")
508 }
509}
510
511#[derive(Clone, Debug, Default, Deserialize)]
513#[allow(non_snake_case)]
514pub struct Pagination {
515 #[serde(rename = "paginationPageCount")]
517 pub count: u32,
518 #[serde(rename = "paginationPageNums")]
520 pub pageNums: Vec<u32>,
521}
522
523impl Pagination {
524 pub fn from_value(data: &Value) -> Result<Self, Error> {
525 parse_with_float_fallback(data, "Pagination")
526 }
527}
528
529#[derive(Clone, Debug, Serialize, Deserialize)]
531#[repr(u8)]
532#[derive(Default)]
533pub enum ArticleType {
534 Normal = 0,
535 Private = 1,
536 Broadcast = 2,
537 Thought = 3,
538 #[default]
539 Unknown = 4,
540 Question = 5,
541}
542
543impl ArticleType {
544 pub fn from_index(index: usize) -> Self {
545 match index {
546 0 => ArticleType::Normal,
547 1 => ArticleType::Private,
548 2 => ArticleType::Broadcast,
549 3 => ArticleType::Thought,
550 5 => ArticleType::Question,
551 _ => ArticleType::Unknown,
552 }
553 }
554}
555
556
557fn default_article_type() -> ArticleType {
558 ArticleType::Unknown
559}
560
561pub fn deserialize_type<'de, D>(deserializer: D) -> Result<ArticleType, D::Error>
562where
563 D: Deserializer<'de>,
564{
565 let value: u64 = Deserialize::deserialize(deserializer)?;
566 Ok(ArticleType::from_index(value as usize))
567}
568
569pub fn deserialize_reddit_score<'de, D>(deserializer: D) -> Result<String, D::Error>
570where
571 D: Deserializer<'de>,
572{
573 let value: u64 = Deserialize::deserialize(deserializer)?;
574 Ok(value.to_string())
575}
576
577pub fn deserialize_tag_objs<'de, D>(deserializer: D) -> Result<Vec<ArticleTag>, D::Error>
578where
579 D: Deserializer<'de>,
580{
581 let arr: Vec<Value> = Deserialize::deserialize(deserializer)?;
582 arr.into_iter()
583 .map(|v| ArticleTag::from_value(&v))
584 .collect::<Result<Vec<_>, _>>()
585 .map_err(serde::de::Error::custom)
586}
587
588pub fn deserialize_author<'de, D>(deserializer: D) -> Result<ArticleAuthor, D::Error>
589where
590 D: Deserializer<'de>,
591{
592 let value: Value = Deserialize::deserialize(deserializer)?;
593 ArticleAuthor::from_value(&value).map_err(serde::de::Error::custom)
594}
595
596pub fn deserialize_pagination<'de, D>(deserializer: D) -> Result<Option<Pagination>, D::Error>
597where
598 D: Deserializer<'de>,
599{
600 let value: Option<Value> = Deserialize::deserialize(deserializer)?;
601 match value {
602 Some(v) => Pagination::from_value(&v)
603 .map(Some)
604 .map_err(serde::de::Error::custom),
605 None => Ok(None),
606 }
607}
608
609pub fn deserialize_comments<'de, D>(deserializer: D) -> Result<Vec<ArticleComment>, D::Error>
610where
611 D: Deserializer<'de>,
612{
613 let arr: Vec<Value> = Deserialize::deserialize(deserializer)?;
614 arr.into_iter()
615 .map(|v| ArticleComment::from_value(&v))
616 .collect::<Result<Vec<_>, _>>()
617 .map_err(serde::de::Error::custom)
618}
619
620#[derive(Clone, Debug, Default, Deserialize)]
622#[serde(default)]
623#[allow(non_snake_case)]
624pub struct ArticleDetail {
625 #[serde(rename = "articleShowInList", deserialize_with = "bool_from_int")]
627 pub showInList: bool,
628 #[serde(rename = "articleCreateTime")]
630 pub createTime: String,
631 #[serde(rename = "articleAuthorId")]
633 pub authorId: String,
634 #[serde(rename = "articleBadCnt")]
636 pub badCnt: u32,
637 #[serde(rename = "articleLatestCmtTime")]
639 pub latestCmtTime: String,
640 #[serde(rename = "articleGoodCnt")]
642 pub goodCnt: u32,
643 #[serde(rename = "articleQnAOfferPoint")]
645 pub offerPoint: u64,
646 #[serde(rename = "articleThumbnailURL")]
648 pub thumbnailURL: String,
649 #[serde(rename = "articleStickRemains")]
651 pub stickRemains: u64,
652 #[serde(rename = "timeAgo")]
654 pub timeAgo: String,
655 #[serde(rename = "articleUpdateTimeStr")]
657 pub updateTimeStr: String,
658 #[serde(rename = "articleAuthorName")]
660 pub authorName: String,
661 #[serde(
663 rename = "articleType",
664 default = "default_article_type",
665 deserialize_with = "deserialize_type"
666 )]
667 pub type_: ArticleType,
668 #[serde(rename = "offered")]
670 pub offered: bool,
671 #[serde(rename = "articleCreateTimeStr")]
673 pub createTimeStr: String,
674 #[serde(rename = "articleViewCount")]
676 pub viewCnt: u64,
677 #[serde(rename = "articleAuthorThumbnailURL20")]
679 pub thumbnailURL20: String,
680 #[serde(rename = "articleWatchCnt")]
682 pub watchCnt: u64,
683 #[serde(rename = "articlePreviewContent")]
685 pub previewContent: String,
686 #[serde(rename = "articleTitleEmoj")]
688 pub titleEmoj: String,
689 #[serde(rename = "articleTitleEmojUnicode")]
691 pub titleEmojUnicode: String,
692 #[serde(rename = "articleTitle")]
694 pub title: String,
695 #[serde(rename = "articleAuthorThumbnailURL48")]
697 pub thumbnailURL48: String,
698 #[serde(rename = "articleCommentCount")]
700 pub commentCnt: u64,
701 #[serde(rename = "articleCollectCnt")]
703 pub collectCnt: u64,
704 #[serde(rename = "articleLatestCmterName")]
706 pub latestCmterName: String,
707 #[serde(rename = "articleTags")]
709 pub tags: String,
710 #[serde(rename = "oId")]
712 pub oId: String,
713 #[serde(rename = "cmtTimeAgo")]
715 pub cmtTimeAgo: String,
716 #[serde(rename = "articleStick")]
718 pub stick: u64,
719 #[serde(
721 rename = "articleTagObjs",
722 default,
723 deserialize_with = "deserialize_tag_objs"
724 )]
725 pub tagObjs: Vec<ArticleTag>,
726 #[serde(rename = "articleLatestCmtTimeStr")]
728 pub latestCmtTimeStr: String,
729 #[serde(rename = "articleAnonymous", deserialize_with = "bool_from_int")]
731 pub anonymous: bool,
732 #[serde(rename = "articleThankCnt")]
734 pub thankCnt: u64,
735 #[serde(rename = "articleUpdateTime")]
737 pub updateTime: String,
738 #[serde(rename = "articleStatus", deserialize_with = "deserialize_status")]
740 pub status: ArticleStatus,
741 #[serde(rename = "articleHeat")]
743 pub heat: u64,
744 #[serde(rename = "articlePerfect", deserialize_with = "bool_from_int")]
746 pub perfect: bool,
747 #[serde(rename = "articleAuthorThumbnailURL210")]
749 pub thumbnailURL210: String,
750 #[serde(rename = "articlePermalink")]
752 pub permalink: String,
753 #[serde(
755 rename = "articleAuthor",
756 default,
757 deserialize_with = "deserialize_author"
758 )]
759 pub author: ArticleAuthor,
760 #[serde(rename = "thankedCnt")]
762 pub thankedCnt: u64,
763 #[serde(rename = "articleAnonymousView")]
765 pub anonymousView: u64,
766 #[serde(rename = "articleViewCntDisplayFormat")]
768 pub viewCntFormat: String,
769 #[serde(rename = "articleCommentable")]
771 pub commentable: bool,
772 #[serde(rename = "rewarded")]
774 pub rewarded: bool,
775 #[serde(rename = "rewardedCnt")]
777 pub rewardedCnt: u64,
778 #[serde(rename = "articleRewardPoint")]
780 pub rewardPoint: u64,
781 #[serde(rename = "isFollowing")]
783 pub isFollowing: bool,
784 #[serde(rename = "isWatching")]
786 pub isWatching: bool,
787 #[serde(rename = "isMyArticle")]
789 pub isMyArticle: bool,
790 #[serde(rename = "thanked")]
792 pub thanked: bool,
793 #[serde(rename = "articleEditorType")]
795 pub editorType: u64,
796 #[serde(rename = "articleAudioURL")]
798 pub audioURL: String,
799 #[serde(rename = "articleToC")]
801 pub table: String,
802 #[serde(rename = "articleContent")]
804 pub content: String,
805 #[serde(rename = "articleOriginalContent")]
807 pub source: String,
808 #[serde(rename = "articleImg1URL")]
810 pub img1URL: String,
811 #[serde(rename = "articleVote", deserialize_with = "deserialize_vote")]
813 pub vote: VoteStatus,
814 #[serde(rename = "articleRandomDouble")]
816 pub randomDouble: f64,
817 #[serde(rename = "articleAuthorIntro")]
819 pub authorIntro: String,
820 #[serde(rename = "articleCity")]
822 pub city: String,
823 #[serde(rename = "articleIP")]
825 pub IP: String,
826 #[serde(rename = "articleAuthorURL")]
828 pub authorURL: String,
829 #[serde(rename = "articlePushOrder")]
831 pub pushOrder: u64,
832 #[serde(rename = "articleRewardContent")]
834 pub rewardContent: String,
835 #[serde(deserialize_with = "deserialize_reddit_score")]
837 pub redditScore: String,
838 #[serde(default, deserialize_with = "deserialize_pagination")]
840 pub pagination: Option<Pagination>,
841 #[serde(rename = "discussionViewable")]
843 pub commentViewable: bool,
844 #[serde(rename = "articleRevisionCount")]
846 pub revisionCount: u64,
847 #[serde(
849 rename = "articleComments",
850 default,
851 deserialize_with = "deserialize_comments"
852 )]
853 pub comments: Vec<ArticleComment>,
854 #[serde(
856 rename = "articleNiceComments",
857 default,
858 deserialize_with = "deserialize_comments"
859 )]
860 pub niceComments: Vec<ArticleComment>,
861}
862
863impl ArticleDetail {
864 pub fn from_value(data: &Value) -> Result<Self, Error> {
865 parse_with_float_fallback(data, "ArticleDetail")
866 }
867}
868
869pub fn deserialize_articles<'de, D>(deserializer: D) -> Result<Vec<ArticleDetail>, D::Error>
870where
871 D: Deserializer<'de>,
872{
873 let arr: Vec<Value> = Deserialize::deserialize(deserializer)?;
874 arr.into_iter()
875 .map(|v| ArticleDetail::from_value(&v))
876 .collect::<Result<Vec<_>, _>>()
877 .map_err(serde::de::Error::custom)
878}
879
880#[derive(Clone, Debug, Deserialize)]
882#[allow(non_snake_case)]
883pub struct ArticleList {
884 #[serde(rename = "articles", deserialize_with = "deserialize_articles")]
886 pub list: Vec<ArticleDetail>,
887 pub pagination: Pagination,
889 pub tag: Option<ArticleTag>,
891}
892
893impl ArticleList {
894 pub fn from_value(data: &Value) -> Result<Self, Error> {
895 parse_with_float_fallback(data, "ArticleList")
896 }
897}
898
899#[derive(Clone, Debug)]
901pub enum ArticleListType {
902 Recent,
904 Hot,
906 Good,
908 Reply,
910 Perfect,
912}
913
914impl_str_enum! {
915 ArticleListType {
916 Recent => "recent",
917 Hot => "hot",
918 Good => "good",
919 Reply => "reply",
920 Perfect => "perfect",
921 }
922}
923
924impl ArticleListType {
925 pub fn to_code(&self) -> &'static str {
926 match self {
927 ArticleListType::Recent => "",
928 ArticleListType::Hot => "/hot",
929 ArticleListType::Good => "/good",
930 ArticleListType::Reply => "/reply",
931 ArticleListType::Perfect => "/perfect",
932 }
933 }
934
935 pub fn values() -> Vec<Self> {
936 vec![
937 ArticleListType::Recent,
938 ArticleListType::Hot,
939 ArticleListType::Good,
940 ArticleListType::Reply,
941 ArticleListType::Perfect,
942 ]
943 }
944}
945
946#[derive(Clone, Debug, Serialize, Deserialize)]
948#[allow(non_snake_case)]
949pub struct CommentPost {
950 pub articleId: String,
952 #[serde(rename = "commentAnonymous")]
954 pub isAnonymous: bool,
955 #[serde(rename = "commentVisible")]
957 pub isVisible: bool,
958 #[serde(rename = "commentContent")]
960 pub content: String,
961 #[serde(rename = "commentOriginalCommentId")]
963 pub replyId: String,
964}
965
966impl CommentPost {
967 pub fn from_value(data: &Value) -> Result<Self, Error> {
968 serde_json::from_value(data.clone())
969 .map_err(|e| Error::Parse(format!("Failed to parse CommentPost: {}", e)))
970 }
971
972 pub fn to_value(&self) -> Result<Value, Error> {
973 serde_json::to_value(self)
974 .map_err(|e| Error::Parse(format!("Failed to serialize CommentPost: {}", e)))
975 }
976}