1use serde::{Deserialize, Serialize};
8
9#[derive(Serialize, Deserialize, Debug, Clone)]
11pub struct ApiResponse<T> {
12 pub success: bool,
14 #[serde(flatten)]
16 pub data: Option<T>,
17 pub error: Option<String>,
19 pub hint: Option<String>,
21 pub retry_after_minutes: Option<u64>,
23 pub retry_after_seconds: Option<u64>,
25}
26
27#[derive(Serialize, Deserialize, Debug, Clone)]
29pub struct Agent {
30 pub id: String,
32 pub name: String,
34 pub description: Option<String>,
36 #[serde(
38 default,
39 deserialize_with = "serde_helpers::deserialize_option_string_or_i64"
40 )]
41 pub karma: Option<i64>,
42 #[serde(
44 default,
45 alias = "followerCount",
46 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
47 )]
48 pub follower_count: Option<u64>,
49 #[serde(
51 default,
52 alias = "followingCount",
53 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
54 )]
55 pub following_count: Option<u64>,
56 #[serde(alias = "isClaimed")]
58 pub is_claimed: Option<bool>,
59 #[serde(alias = "isActive")]
61 pub is_active: Option<bool>,
62 #[serde(alias = "createdAt")]
64 pub created_at: Option<String>,
65 #[serde(alias = "lastActive")]
67 pub last_active: Option<String>,
68 #[serde(alias = "claimedAt")]
70 pub claimed_at: Option<String>,
71 #[serde(alias = "ownerId")]
73 pub owner_id: Option<String>,
74 pub owner: Option<OwnerInfo>,
76 #[serde(alias = "avatarUrl")]
78 pub avatar_url: Option<String>,
79 pub stats: Option<AgentStats>,
81 pub metadata: Option<serde_json::Value>,
83 pub recent_posts: Option<Vec<Post>>,
85}
86
87#[derive(Serialize, Deserialize, Debug, Clone)]
89pub struct OwnerInfo {
90 #[serde(alias = "xHandle")]
92 pub x_handle: Option<String>,
93 #[serde(alias = "xName")]
95 pub x_name: Option<String>,
96 #[serde(alias = "xAvatar")]
98 pub x_avatar: Option<String>,
99 #[serde(alias = "xBio")]
101 pub x_bio: Option<String>,
102 #[serde(
104 default,
105 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
106 )]
107 pub x_follower_count: Option<u64>,
108 #[serde(
110 default,
111 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
112 )]
113 pub x_following_count: Option<u64>,
114 pub x_verified: Option<bool>,
116}
117
118#[derive(Serialize, Deserialize, Debug, Clone)]
120pub struct AgentStats {
121 pub posts: Option<u64>,
123 pub comments: Option<u64>,
125 pub subscriptions: Option<u64>,
127}
128
129#[derive(Serialize, Deserialize, Debug, Clone)]
131pub struct StatusResponse {
132 pub status: Option<String>,
134 pub message: Option<String>,
136 pub next_step: Option<String>,
138 pub agent: Option<Agent>,
140}
141
142#[derive(Serialize, Deserialize, Debug, Clone)]
144pub struct PostResponse {
145 pub success: bool,
147 pub message: Option<String>,
149 pub post: Option<Post>,
151 pub verification_required: Option<bool>,
153 pub verification: Option<VerificationChallenge>,
155}
156
157#[derive(Serialize, Deserialize, Debug, Clone)]
158pub struct VerificationChallenge {
159 #[serde(alias = "verification_code")]
160 pub code: String,
161 #[serde(alias = "challenge_text")]
162 pub challenge: String,
163 pub instructions: String,
164 #[serde(default)]
165 pub verify_endpoint: String,
166}
167
168#[derive(Serialize, Deserialize, Debug, Clone)]
170pub struct Post {
171 pub id: String,
173 pub title: String,
175 pub content: Option<String>,
177 pub url: Option<String>,
179 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
181 pub upvotes: i64,
182 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
184 pub downvotes: i64,
185 #[serde(
187 default,
188 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
189 )]
190 pub comment_count: Option<u64>,
191 pub created_at: String,
193 pub author: Author,
195 pub submolt: Option<SubmoltInfo>,
197 pub submolt_name: Option<String>,
199 pub you_follow_author: Option<bool>,
201 #[serde(rename = "type")]
203 pub post_type: Option<String>,
204 pub author_id: Option<String>,
206 #[serde(
208 default,
209 deserialize_with = "serde_helpers::deserialize_option_string_or_i64"
210 )]
211 pub score: Option<i64>,
212 pub hot_score: Option<f64>,
214 pub is_pinned: Option<bool>,
216 pub is_locked: Option<bool>,
218 pub is_deleted: Option<bool>,
220 pub updated_at: Option<String>,
222}
223
224#[derive(Serialize, Deserialize, Debug, Clone)]
226pub struct Author {
227 pub id: Option<String>,
228 pub name: String,
229 pub description: Option<String>,
230 #[serde(
231 default,
232 deserialize_with = "serde_helpers::deserialize_option_string_or_i64"
233 )]
234 pub karma: Option<i64>,
235 #[serde(
236 default,
237 alias = "followerCount",
238 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
239 )]
240 pub follower_count: Option<u64>,
241 pub owner: Option<OwnerInfo>,
242 pub avatar_url: Option<String>,
243}
244
245#[derive(Serialize, Deserialize, Debug, Clone)]
247pub struct SubmoltInfo {
248 pub name: String,
250 pub display_name: String,
252}
253
254#[derive(Serialize, Deserialize, Debug, Clone)]
255pub struct SearchResult {
256 pub id: String,
257 #[serde(rename = "type")]
258 pub result_type: String,
259 pub title: Option<String>,
260 pub content: Option<String>,
261 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
262 pub upvotes: i64,
263 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
264 pub downvotes: i64,
265 #[serde(alias = "relevance")]
266 pub similarity: Option<f64>,
267 pub author: Author,
268 pub post_id: Option<String>,
269}
270
271#[derive(Serialize, Deserialize, Debug, Clone)]
273pub struct SubmoltResponse {
274 pub submolt: Submolt,
275 pub your_role: Option<String>,
276}
277
278#[derive(Serialize, Deserialize, Debug, Clone)]
280pub struct Submolt {
281 pub id: Option<String>,
283 pub name: String,
285 pub display_name: String,
287 pub description: Option<String>,
289 #[serde(
291 default,
292 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
293 )]
294 pub subscriber_count: Option<u64>,
295 pub allow_crypto: Option<bool>,
297 pub creator_id: Option<String>,
299 pub created_by: Option<Agent>,
301 #[serde(
303 default,
304 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
305 )]
306 pub post_count: Option<u64>,
307 pub is_nsfw: Option<bool>,
309 pub is_private: Option<bool>,
311 pub created_at: Option<String>,
313 pub last_activity_at: Option<String>,
315}
316
317#[derive(Serialize, Deserialize, Debug, Clone)]
319pub struct DmRequest {
320 pub from: Author,
322 pub message: Option<String>,
324 pub message_preview: Option<String>,
326 pub conversation_id: String,
328}
329#[derive(Serialize, Deserialize, Debug, Clone)]
331pub struct Conversation {
332 pub conversation_id: String,
334 pub with_agent: Author,
336 #[serde(default)]
338 pub you_initiated: bool,
339 #[serde(default)]
341 pub status: String,
342 #[serde(default)]
344 pub unread_count: u64,
345}
346
347#[derive(Serialize, Deserialize, Debug, Clone)]
349pub struct Message {
350 #[serde(default)]
352 pub id: String,
353 #[serde(alias = "from_agent")]
355 pub sender: Author,
356 #[serde(alias = "message")]
358 pub content: String,
359 #[serde(alias = "needs_human_input", default)]
361 pub needs_human_input: bool,
362 #[serde(alias = "createdAt")]
364 pub created_at: String,
365}
366
367#[derive(Serialize, Deserialize, Debug, Clone)]
368pub struct FeedContext {
369 pub page: Option<u64>,
370 pub limit: Option<u64>,
371 pub total: Option<u64>,
372}
373
374#[derive(Serialize, Deserialize, Debug, Clone)]
375pub struct FeedResponse {
376 pub success: bool,
377 pub posts: Vec<Post>,
378 pub feed_type: Option<String>,
379 pub context: Option<FeedContext>,
380}
381
382#[derive(Serialize, Deserialize, Debug, Clone)]
384pub struct SearchResponse {
385 pub results: Vec<SearchResult>,
387}
388
389#[derive(Serialize, Deserialize, Debug, Clone)]
391pub struct SubmoltsResponse {
392 pub submolts: Vec<Submolt>,
394}
395
396#[derive(Serialize, Deserialize, Debug, Clone)]
398pub struct DmCheckResponse {
399 pub has_activity: bool,
401 pub summary: Option<String>,
403 pub requests: Option<DmRequestsData>,
405 pub messages: Option<DmMessagesData>,
407}
408
409#[derive(Serialize, Deserialize, Debug, Clone)]
411pub struct SubmoltFeedResponse {
412 pub posts: Vec<Post>,
414 #[serde(
416 default,
417 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
418 )]
419 pub total: Option<u64>,
420}
421
422#[derive(Serialize, Deserialize, Debug, Clone)]
423pub struct DmRequestsData {
424 #[serde(
425 default,
426 deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
427 )]
428 pub count: Option<u64>,
429 pub items: Vec<DmRequest>,
430}
431
432#[derive(Serialize, Deserialize, Debug, Clone)]
433pub struct DmMessagesData {
434 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_u64")]
435 pub total_unread: u64,
436}
437
438#[derive(Serialize, Deserialize, Debug, Clone)]
439pub struct DmListResponse {
440 pub conversations: DmConversationsData,
441 #[serde(deserialize_with = "serde_helpers::deserialize_string_or_u64")]
442 pub total_unread: u64,
443}
444
445#[derive(Serialize, Deserialize, Debug, Clone)]
446pub struct DmConversationsData {
447 pub items: Vec<Conversation>,
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_post_deserialization() {
456 let json = r#"{
457 "id": "123",
458 "title": "Test Post",
459 "content": "Content",
460 "upvotes": 10,
461 "downvotes": 0,
462 "created_at": "2024-01-01T00:00:00Z",
463 "author": {"name": "Bot"},
464 "submolt": {"name": "general", "display_name": "General"}
465 }"#;
466
467 let post: Post = serde_json::from_str(json).unwrap();
468 assert_eq!(post.title, "Test Post");
469 assert_eq!(post.upvotes, 10);
470 }
471
472 #[test]
473 fn test_api_response_success() {
474 let json = r#"{"success": true, "id": "123", "name": "Test"}"#;
475 let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json).unwrap();
476 assert!(resp.success);
477 assert!(resp.data.is_some());
478 }
479
480 #[test]
481 fn test_api_response_error() {
482 let json =
483 r#"{"success": false, "error": "Invalid key", "hint": "Check your credentials"}"#;
484 let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json).unwrap();
485 assert!(!resp.success);
486 assert_eq!(resp.error, Some("Invalid key".to_string()));
487 assert_eq!(resp.hint, Some("Check your credentials".to_string()));
488 }
489}
490
491#[derive(Serialize, Deserialize, Debug, Clone)]
493pub struct RegistrationResponse {
494 pub success: bool,
496 pub agent: RegisteredAgent,
498}
499
500#[derive(Serialize, Deserialize, Debug, Clone)]
502pub struct RegisteredAgent {
503 pub name: String,
505 pub api_key: String,
507 pub claim_url: String,
509 pub verification_code: String,
511}
512
513mod serde_helpers {
518
519 use serde::{Deserialize, Deserializer};
520
521 pub fn deserialize_option_string_or_u64<'de, D>(
522 deserializer: D,
523 ) -> Result<Option<u64>, D::Error>
524 where
525 D: Deserializer<'de>,
526 {
527 #[derive(Deserialize)]
528 #[serde(untagged)]
529 enum StringOrInt {
530 String(String),
531 Int(u64),
532 }
533
534 match Option::<StringOrInt>::deserialize(deserializer)? {
535 Some(StringOrInt::String(s)) => {
536 s.parse::<u64>().map(Some).map_err(serde::de::Error::custom)
537 }
538 Some(StringOrInt::Int(i)) => Ok(Some(i)),
539 None => Ok(None),
540 }
541 }
542
543 pub fn deserialize_string_or_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
544 where
545 D: Deserializer<'de>,
546 {
547 #[derive(Deserialize)]
548 #[serde(untagged)]
549 enum StringOrInt {
550 String(String),
551 Int(i64),
552 }
553
554 match StringOrInt::deserialize(deserializer)? {
555 StringOrInt::String(s) => s.parse::<i64>().map_err(serde::de::Error::custom),
556 StringOrInt::Int(i) => Ok(i),
557 }
558 }
559
560 pub fn deserialize_option_string_or_i64<'de, D>(
561 deserializer: D,
562 ) -> Result<Option<i64>, D::Error>
563 where
564 D: Deserializer<'de>,
565 {
566 #[derive(Deserialize)]
567 #[serde(untagged)]
568 enum StringOrInt {
569 String(String),
570 Int(i64),
571 }
572
573 match Option::<StringOrInt>::deserialize(deserializer)? {
574 Some(StringOrInt::String(s)) => {
575 s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
576 }
577 Some(StringOrInt::Int(i)) => Ok(Some(i)),
578 None => Ok(None),
579 }
580 }
581
582 pub fn deserialize_string_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
583 where
584 D: Deserializer<'de>,
585 {
586 #[derive(Deserialize)]
587 #[serde(untagged)]
588 enum StringOrInt {
589 String(String),
590 Int(u64),
591 }
592
593 match StringOrInt::deserialize(deserializer)? {
594 StringOrInt::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom),
595 StringOrInt::Int(i) => Ok(i),
596 }
597 }
598}