xcom_rs/handlers/
timeline.rs1use crate::{
2 cli::TimelineCommands,
3 output::{print_envelope, print_ndjson, OutputFormat},
4 protocol::{Envelope, ErrorCode, ErrorDetails, ExitCode},
5 timeline::{
6 commands::TimelineError,
7 models::{TimelineArgs, TimelineKind},
8 TimelineCommand,
9 },
10};
11use anyhow::Result;
12use std::collections::HashMap;
13
14pub fn handle_timeline(
16 command: TimelineCommands,
17 create_meta: &dyn Fn() -> Option<HashMap<String, serde_json::Value>>,
18 output_format: OutputFormat,
19) -> Result<()> {
20 let timeline_cmd = TimelineCommand::new();
21
22 let (args, response_type) = match command {
23 TimelineCommands::Home { limit, cursor } => (
24 TimelineArgs {
25 kind: TimelineKind::Home,
26 limit,
27 cursor,
28 },
29 "timeline.home",
30 ),
31 TimelineCommands::Mentions { limit, cursor } => (
32 TimelineArgs {
33 kind: TimelineKind::Mentions,
34 limit,
35 cursor,
36 },
37 "timeline.mentions",
38 ),
39 TimelineCommands::User {
40 handle,
41 limit,
42 cursor,
43 } => (
44 TimelineArgs {
45 kind: TimelineKind::User { handle },
46 limit,
47 cursor,
48 },
49 "timeline.user",
50 ),
51 };
52
53 tracing::info!(response_type = %response_type, "Fetching timeline");
54
55 match timeline_cmd.get(args) {
56 Ok(result) => {
57 if output_format == OutputFormat::Ndjson {
58 print_ndjson(&result.tweets)
59 } else {
60 let envelope = if let Some(meta) = create_meta() {
61 Envelope::success_with_meta(response_type, result, meta)
62 } else {
63 Envelope::success(response_type, result)
64 };
65 print_envelope(&envelope, output_format)
66 }
67 }
68 Err(e) => {
69 let error = build_error_details(&e);
70
71 let envelope = if let Some(meta) = create_meta() {
72 Envelope::<()>::error_with_meta("error", error, meta)
73 } else {
74 Envelope::<()>::error("error", error)
75 };
76 let _ = print_envelope(&envelope, output_format);
77
78 let exit_code: i32 = match &e {
79 TimelineError::AuthRequired => ExitCode::AuthenticationError.into(),
80 TimelineError::ApiError(_) => ExitCode::OperationFailed.into(),
81 };
82 std::process::exit(exit_code);
83 }
84 }
85}
86
87fn build_error_details(e: &TimelineError) -> ErrorDetails {
88 match e {
89 TimelineError::AuthRequired => ErrorDetails::auth_required(
90 e.to_string(),
91 vec!["Run 'xcom-rs auth login' to authenticate.".to_string()],
92 ),
93 TimelineError::ApiError(classified) => {
94 if let Some(retry_after_ms) = classified.retry_after_ms {
95 ErrorDetails::with_retry_after(e.to_error_code(), e.to_string(), retry_after_ms)
96 } else {
97 ErrorDetails::new(e.to_error_code(), e.to_string())
98 }
99 }
100 }
101}
102
103impl From<&TimelineError> for ErrorCode {
105 fn from(e: &TimelineError) -> Self {
106 e.to_error_code()
107 }
108}