1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Tweet {
7 pub id: String,
8 #[serde(skip_serializing_if = "Option::is_none")]
9 pub text: Option<String>,
10 #[serde(skip_serializing_if = "Option::is_none")]
11 pub author_id: Option<String>,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub created_at: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub edit_history_tweet_ids: Option<Vec<String>>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub conversation_id: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub in_reply_to_user_id: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub referenced_tweets: Option<Vec<ReferencedTweet>>,
22 #[serde(flatten)]
23 pub additional_fields: HashMap<String, serde_json::Value>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ReferencedTweet {
29 #[serde(rename = "type")]
30 pub ref_type: String,
31 pub id: String,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ConversationEdge {
37 pub parent_id: String,
38 pub child_id: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct ConversationResult {
44 pub conversation_id: String,
46 pub posts: Vec<Tweet>,
48 pub edges: Vec<ConversationEdge>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct TweetMeta {
56 pub client_request_id: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub from_cache: Option<bool>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum TweetFields {
64 Id,
65 Text,
66 AuthorId,
67 CreatedAt,
68 EditHistoryTweetIds,
69 ConversationId,
70 InReplyToUserId,
71 ReferencedTweets,
72}
73
74impl TweetFields {
75 pub fn parse(s: &str) -> Option<Self> {
76 match s {
77 "id" => Some(Self::Id),
78 "text" => Some(Self::Text),
79 "author_id" => Some(Self::AuthorId),
80 "created_at" => Some(Self::CreatedAt),
81 "edit_history_tweet_ids" => Some(Self::EditHistoryTweetIds),
82 "conversation_id" => Some(Self::ConversationId),
83 "in_reply_to_user_id" => Some(Self::InReplyToUserId),
84 "referenced_tweets" => Some(Self::ReferencedTweets),
85 _ => None,
86 }
87 }
88
89 pub fn as_str(&self) -> &'static str {
90 match self {
91 Self::Id => "id",
92 Self::Text => "text",
93 Self::AuthorId => "author_id",
94 Self::CreatedAt => "created_at",
95 Self::EditHistoryTweetIds => "edit_history_tweet_ids",
96 Self::ConversationId => "conversation_id",
97 Self::InReplyToUserId => "in_reply_to_user_id",
98 Self::ReferencedTweets => "referenced_tweets",
99 }
100 }
101
102 pub fn default_fields() -> Vec<Self> {
104 vec![Self::Id, Self::Text]
105 }
106}
107
108impl Tweet {
109 pub fn new(id: String) -> Self {
111 Self {
112 id,
113 text: None,
114 author_id: None,
115 created_at: None,
116 edit_history_tweet_ids: None,
117 conversation_id: None,
118 in_reply_to_user_id: None,
119 referenced_tweets: None,
120 additional_fields: HashMap::new(),
121 }
122 }
123
124 pub fn project(&self, fields: &[TweetFields]) -> Self {
126 let mut tweet = Tweet::new(String::new());
127
128 for field in fields {
129 match field {
130 TweetFields::Id => tweet.id = self.id.clone(),
131 TweetFields::Text => tweet.text = self.text.clone(),
132 TweetFields::AuthorId => tweet.author_id = self.author_id.clone(),
133 TweetFields::CreatedAt => tweet.created_at = self.created_at.clone(),
134 TweetFields::EditHistoryTweetIds => {
135 tweet.edit_history_tweet_ids = self.edit_history_tweet_ids.clone()
136 }
137 TweetFields::ConversationId => tweet.conversation_id = self.conversation_id.clone(),
138 TweetFields::InReplyToUserId => {
139 tweet.in_reply_to_user_id = self.in_reply_to_user_id.clone()
140 }
141 TweetFields::ReferencedTweets => {
142 tweet.referenced_tweets = self.referenced_tweets.clone()
143 }
144 }
145 }
146
147 tweet
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_tweet_projection() {
157 let mut tweet = Tweet::new("123".to_string());
158 tweet.text = Some("Hello world".to_string());
159 tweet.author_id = Some("user_123".to_string());
160
161 let projected = tweet.project(&[TweetFields::Id, TweetFields::Text]);
162 assert_eq!(projected.id, "123");
163 assert_eq!(projected.text, Some("Hello world".to_string()));
164 assert_eq!(projected.author_id, None);
165 }
166
167 #[test]
168 fn test_default_fields() {
169 let fields = TweetFields::default_fields();
170 assert_eq!(fields.len(), 2);
171 assert!(fields.contains(&TweetFields::Id));
172 assert!(fields.contains(&TweetFields::Text));
173 }
174
175 #[test]
176 fn test_field_parse() {
177 assert_eq!(TweetFields::parse("id"), Some(TweetFields::Id));
178 assert_eq!(TweetFields::parse("text"), Some(TweetFields::Text));
179 assert_eq!(TweetFields::parse("invalid"), None);
180 }
181}