diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 304f3c8..019739c 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -24,10 +24,13 @@ use ldk_server_client::ldk_server_protos::api::{ SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, }; use ldk_server_client::ldk_server_protos::types::{ - bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, Payment, + bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, RouteParametersConfig, }; use serde::Serialize; +use types::CliListPaymentsResponse; + +mod types; // Having these default values as constants in the Proto file and // importing/reusing them here might be better, but Proto3 removed @@ -178,9 +181,12 @@ enum Commands { ListPayments { #[arg(short, long)] #[arg( - help = "Minimum number of payments to return. If not provided, only the first page of the paginated list is returned." + help = "Fetch at least this many payments by iterating through multiple pages. Returns combined results with the last page token. If not provided, returns only a single page." )] number_of_payments: Option, + #[arg(long)] + #[arg(help = "Page token to continue from a previous page (format: token:index)")] + page_token: Option, }, UpdateChannelConfig { #[arg(short, long)] @@ -416,12 +422,15 @@ async fn main() { client.list_channels(ListChannelsRequest {}).await, ); }, - Commands::ListPayments { number_of_payments } => { - handle_response_result::<_, ListPaymentsResponse>( - list_n_payments(client, number_of_payments) - .await - // todo: handle pagination properly - .map(|payments| ListPaymentsResponse { payments, next_page_token: None }), + Commands::ListPayments { number_of_payments, page_token } => { + let page_token = if let Some(token_str) = page_token { + Some(parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e))) + } else { + None + }; + + handle_response_result::<_, CliListPaymentsResponse>( + handle_list_payments(client, number_of_payments, page_token).await, ); }, Commands::UpdateChannelConfig { @@ -475,24 +484,37 @@ fn build_open_channel_config( }) } +async fn handle_list_payments( + client: LdkServerClient, number_of_payments: Option, initial_page_token: Option, +) -> Result { + if let Some(count) = number_of_payments { + list_n_payments(client, count, initial_page_token).await + } else { + // Fetch single page + client.list_payments(ListPaymentsRequest { page_token: initial_page_token }).await + } +} + async fn list_n_payments( - client: LdkServerClient, number_of_payments: Option, -) -> Result, LdkServerError> { - let mut payments = Vec::new(); - let mut page_token: Option = None; - // If no count is specified, just list the first page. - let target_count = number_of_payments.unwrap_or(0); + client: LdkServerClient, target_count: u64, initial_page_token: Option, +) -> Result { + let mut payments = Vec::with_capacity(target_count as usize); + let mut page_token = initial_page_token; + let mut next_page_token; loop { let response = client.list_payments(ListPaymentsRequest { page_token }).await?; payments.extend(response.payments); - if payments.len() >= target_count as usize || response.next_page_token.is_none() { + next_page_token = response.next_page_token; + + if payments.len() >= target_count as usize || next_page_token.is_none() { break; } - page_token = response.next_page_token; + page_token = next_page_token; } - Ok(payments) + + Ok(ListPaymentsResponse { payments, next_page_token }) } fn handle_response_result(response: Result) @@ -517,6 +539,20 @@ where } } +fn parse_page_token(token_str: &str) -> Result { + let parts: Vec<&str> = token_str.split(':').collect(); + if parts.len() != 2 { + return Err(LdkServerError::new( + InternalError, + "Page token must be in format 'token:index'".to_string(), + )); + } + let index = parts[1] + .parse::() + .map_err(|_| LdkServerError::new(InternalError, "Invalid page token index".to_string()))?; + Ok(PageToken { token: parts[0].to_string(), index }) +} + fn handle_error(e: LdkServerError) -> ! { let error_type = match e.error_code { InvalidRequestError => "Invalid Request", diff --git a/ldk-server-cli/src/types.rs b/ldk-server-cli/src/types.rs new file mode 100644 index 0000000..a482f79 --- /dev/null +++ b/ldk-server-cli/src/types.rs @@ -0,0 +1,41 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! CLI-specific type wrappers for API responses. +//! +//! This file contains wrapper types that customize the serialization format +//! of API responses for CLI output. These wrappers ensure that the CLI's output +//! format matches what users expect and what the CLI can parse back as input. + +use ldk_server_client::ldk_server_protos::api::ListPaymentsResponse; +use ldk_server_client::ldk_server_protos::types::{PageToken, Payment}; +use serde::Serialize; + +/// CLI-specific wrapper for ListPaymentsResponse that formats the page token +/// as "token:idx" instead of a JSON object. +#[derive(Debug, Clone, Serialize)] +pub struct CliListPaymentsResponse { + /// List of payments. + pub payments: Vec, + /// Next page token formatted as "token:idx", or None if no more pages. + #[serde(skip_serializing_if = "Option::is_none")] + pub next_page_token: Option, +} + +impl From for CliListPaymentsResponse { + fn from(response: ListPaymentsResponse) -> Self { + let next_page_token = response.next_page_token.map(format_page_token); + + CliListPaymentsResponse { payments: response.payments, next_page_token } + } +} + +fn format_page_token(token: PageToken) -> String { + format!("{}:{}", token.token, token.index) +}