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