1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize, Debug, Clone)]
4pub struct ApiResponse<T> {
5 pub success: bool,
6 #[serde(flatten)]
7 pub data: Option<T>,
8 pub error: Option<String>,
9 pub hint: Option<String>,
10 pub retry_after_minutes: Option<u64>,
11 pub retry_after_seconds: Option<u64>,
12}
13
14#[derive(Serialize, Deserialize, Debug, Clone)]
15pub struct Agent {
16 pub id: String,
17 pub name: String,
18 pub description: Option<String>,
19 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_i64")]
20 pub karma: Option<i64>,
21 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
22 pub follower_count: Option<u64>,
23 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
24 pub following_count: Option<u64>,
25 pub is_claimed: Option<bool>,
26 pub is_active: Option<bool>,
27 pub created_at: Option<String>,
28 pub last_active: Option<String>,
29 pub claimed_at: Option<String>,
30 pub owner_id: Option<String>,
31 pub owner: Option<OwnerInfo>,
32 pub stats: Option<AgentStats>,
33 pub metadata: Option<serde_json::Value>,
34 pub recent_posts: Option<Vec<Post>>,
35}
36
37#[derive(Serialize, Deserialize, Debug, Clone)]
38pub struct OwnerInfo {
39 #[serde(alias = "xHandle")]
40 pub x_handle: Option<String>,
41 #[serde(alias = "xName")]
42 pub x_name: Option<String>,
43 #[serde(alias = "xAvatar")]
44 pub x_avatar: Option<String>,
45 #[serde(alias = "xBio")]
46 pub x_bio: Option<String>,
47 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
48 pub x_follower_count: Option<u64>,
49 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
50 pub x_following_count: Option<u64>,
51 pub x_verified: Option<bool>,
52}
53
54#[derive(Serialize, Deserialize, Debug, Clone)]
55pub struct AgentStats {
56 pub posts: Option<u64>,
57 pub comments: Option<u64>,
58 pub subscriptions: Option<u64>,
59}
60
61#[derive(Serialize, Deserialize, Debug, Clone)]
62pub struct StatusResponse {
63 pub status: Option<String>,
64 pub message: Option<String>,
65 pub next_step: Option<String>,
66 pub agent: Option<Agent>,
67}
68
69#[derive(Serialize, Deserialize, Debug, Clone)]
70pub struct PostResponse {
71 pub success: bool,
72 pub message: Option<String>,
73 pub post: Option<Post>,
74 pub verification_required: Option<bool>,
75 pub verification: Option<VerificationChallenge>,
76}
77
78#[derive(Serialize, Deserialize, Debug, Clone)]
79pub struct VerificationChallenge {
80 pub code: String,
81 pub challenge: String,
82 pub instructions: String,
83 pub verify_endpoint: String,
84}
85
86#[derive(Serialize, Deserialize, Debug, Clone)]
87pub struct Post {
88 pub id: String,
89 pub title: String,
90 pub content: Option<String>,
91 pub url: Option<String>,
92 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
93 pub upvotes: i64,
94 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
95 pub downvotes: i64,
96 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
97 pub comment_count: Option<u64>,
98 pub created_at: String,
99 pub author: Author,
100 pub submolt: Option<SubmoltInfo>,
101 pub submolt_name: Option<String>,
102}
103
104#[derive(Serialize, Deserialize, Debug, Clone)]
105pub struct Author {
106 pub id: Option<String>,
107 pub name: String,
108 pub description: Option<String>,
109 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_i64")]
110 pub karma: Option<i64>,
111 #[serde(default, alias = "followerCount", deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
112 pub follower_count: Option<u64>,
113 pub owner: Option<OwnerInfo>,
114}
115
116#[derive(Serialize, Deserialize, Debug, Clone)]
117pub struct SubmoltInfo {
118 pub name: String,
119 pub display_name: String,
120}
121
122#[derive(Serialize, Deserialize, Debug, Clone)]
123pub struct SearchResult {
124 pub id: String,
125 #[serde(rename = "type")]
126 pub result_type: String,
127 pub title: Option<String>,
128 pub content: Option<String>,
129 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
130 pub upvotes: i64,
131 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
132 pub downvotes: i64,
133 #[serde(alias = "relevance")]
134 pub similarity: Option<f64>,
135 pub author: Author,
136 pub post_id: Option<String>,
137}
138
139#[derive(Serialize, Deserialize, Debug, Clone)]
140pub struct Submolt {
141 pub id: Option<String>,
142 pub name: String,
143 pub display_name: String,
144 pub description: Option<String>,
145 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
146 pub subscriber_count: Option<u64>,
147 pub allow_crypto: Option<bool>,
148 pub created_at: Option<String>,
149 pub last_activity_at: Option<String>,
150}
151
152#[derive(Serialize, Deserialize, Debug, Clone)]
153pub struct DmRequest {
154 pub from: Author,
155 pub message: Option<String>,
156 pub message_preview: Option<String>,
157 pub conversation_id: String,
158}
159
160#[derive(Serialize, Deserialize, Debug, Clone)]
161pub struct Conversation {
162 pub conversation_id: String,
163 pub with_agent: Author,
164 pub unread_count: u64,
165}
166
167#[derive(Serialize, Deserialize, Debug, Clone)]
168pub struct Message {
169 pub from_agent: Author,
170 pub message: String,
171 pub from_you: bool,
172 pub needs_human_input: bool,
173 pub created_at: String,
174}
175
176#[derive(Serialize, Deserialize, Debug, Clone)]
177pub struct FeedResponse {
178 pub success: bool,
179 pub posts: Vec<Post>,
180 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
181 pub count: Option<u64>,
182 pub has_more: Option<bool>,
183 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
184 pub next_offset: Option<u64>,
185 pub authenticated: Option<bool>,
186}
187
188#[derive(Serialize, Deserialize, Debug, Clone)]
189pub struct SearchResponse {
190 pub results: Vec<SearchResult>,
191}
192
193#[derive(Serialize, Deserialize, Debug, Clone)]
194pub struct SubmoltsResponse {
195 pub submolts: Vec<Submolt>,
196}
197
198#[derive(Serialize, Deserialize, Debug, Clone)]
199pub struct DmCheckResponse {
200 pub has_activity: bool,
201 pub summary: Option<String>,
202 pub requests: Option<DmRequestsData>,
203 pub messages: Option<DmMessagesData>,
204}
205
206#[derive(Serialize, Deserialize, Debug, Clone)]
207pub struct SubmoltFeedResponse {
208 pub posts: Vec<Post>,
209 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
210 pub total: Option<u64>,
211}
212
213#[derive(Serialize, Deserialize, Debug, Clone)]
214pub struct DmRequestsData {
215 #[serde(default, deserialize_with = "serde_helpers::deserialize_option_string_or_u64")]
216 pub count: Option<u64>,
217 pub items: Vec<DmRequest>,
218}
219
220#[derive(Serialize, Deserialize, Debug, Clone)]
221pub struct DmMessagesData {
222 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_u64")]
223 pub total_unread: u64,
224}
225
226#[derive(Serialize, Deserialize, Debug, Clone)]
227pub struct DmListResponse {
228 pub conversations: DmConversationsData,
229 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_u64")]
230 pub total_unread: u64,
231}
232
233#[derive(Serialize, Deserialize, Debug, Clone)]
234pub struct DmConversationsData {
235 pub items: Vec<Conversation>,
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_post_deserialization() {
244 let json = r#"{
245 "id": "123",
246 "title": "Test Post",
247 "content": "Content",
248 "upvotes": 10,
249 "downvotes": 0,
250 "created_at": "2024-01-01T00:00:00Z",
251 "author": {"name": "Bot"},
252 "submolt": {"name": "general", "display_name": "General"}
253 }"#;
254
255 let post: Post = serde_json::from_str(json).unwrap();
256 assert_eq!(post.title, "Test Post");
257 assert_eq!(post.upvotes, 10);
258 }
259
260 #[test]
261 fn test_api_response_success() {
262 let json = r#"{"success": true, "id": "123", "name": "Test"}"#;
263 let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json).unwrap();
264 assert!(resp.success);
265 assert!(resp.data.is_some());
266 }
267
268 #[test]
269 fn test_api_response_error() {
270 let json =
271 r#"{"success": false, "error": "Invalid key", "hint": "Check your credentials"}"#;
272 let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json).unwrap();
273 assert!(!resp.success);
274 assert_eq!(resp.error, Some("Invalid key".to_string()));
275 assert_eq!(resp.hint, Some("Check your credentials".to_string()));
276 }
277}
278
279#[derive(Serialize, Deserialize, Debug, Clone)]
280pub struct RegistrationResponse {
281 pub success: bool,
282 pub agent: RegisteredAgent,
283}
284
285#[derive(Serialize, Deserialize, Debug, Clone)]
286pub struct RegisteredAgent {
287 pub name: String,
288 pub api_key: String,
289 pub claim_url: String,
290 pub verification_code: String,
291}
292
293mod serde_helpers {
294 use serde::{Deserialize, Deserializer};
295
296 pub fn deserialize_option_string_or_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
297 where
298 D: Deserializer<'de>,
299 {
300 #[derive(Deserialize)]
301 #[serde(untagged)]
302 enum StringOrInt {
303 String(String),
304 Int(u64),
305 }
306
307 match Option::<StringOrInt>::deserialize(deserializer)? {
308 Some(StringOrInt::String(s)) => s.parse::<u64>().map(Some).map_err(serde::de::Error::custom),
309 Some(StringOrInt::Int(i)) => Ok(Some(i)),
310 None => Ok(None),
311 }
312 }
313
314 pub fn deserialize_string_or_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
315 where
316 D: Deserializer<'de>,
317 {
318 #[derive(Deserialize)]
319 #[serde(untagged)]
320 enum StringOrInt {
321 String(String),
322 Int(i64),
323 }
324
325 match StringOrInt::deserialize(deserializer)? {
326 StringOrInt::String(s) => s.parse::<i64>().map_err(serde::de::Error::custom),
327 StringOrInt::Int(i) => Ok(i),
328 }
329 }
330
331 pub fn deserialize_option_string_or_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
332 where
333 D: Deserializer<'de>,
334 {
335 #[derive(Deserialize)]
336 #[serde(untagged)]
337 enum StringOrInt {
338 String(String),
339 Int(i64),
340 }
341
342 match Option::<StringOrInt>::deserialize(deserializer)? {
343 Some(StringOrInt::String(s)) => s.parse::<i64>().map(Some).map_err(serde::de::Error::custom),
344 Some(StringOrInt::Int(i)) => Ok(Some(i)),
345 None => Ok(None),
346 }
347 }
348
349 pub fn deserialize_string_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
350 where
351 D: Deserializer<'de>,
352 {
353 #[derive(Deserialize)]
354 #[serde(untagged)]
355 enum StringOrInt {
356 String(String),
357 Int(u64),
358 }
359
360 match StringOrInt::deserialize(deserializer)? {
361 StringOrInt::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom),
362 StringOrInt::Int(i) => Ok(i),
363 }
364 }
365}