Skip to content

Commit 7614706

Browse files
committed
Implement proper pagination for list-payments cli
1 parent 0eb5f9d commit 7614706

2 files changed

Lines changed: 85 additions & 17 deletions

File tree

ldk-server-cli/src/main.rs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ use ldk_server_client::ldk_server_protos::api::{
1515
SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse,
1616
};
1717
use ldk_server_client::ldk_server_protos::types::{
18-
bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, Payment,
18+
bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken,
1919
RouteParametersConfig,
2020
};
2121
use serde::Serialize;
2222

23+
mod types;
24+
use types::CliListPaymentsResponse;
25+
2326
// Having these default values as constants in the Proto file and
2427
// importing/reusing them here might be better, but Proto3 removed
2528
// the ability to set default values.
@@ -169,9 +172,12 @@ enum Commands {
169172
ListPayments {
170173
#[arg(short, long)]
171174
#[arg(
172-
help = "Minimum number of payments to return. If not provided, only the first page of the paginated list is returned."
175+
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."
173176
)]
174177
number_of_payments: Option<u64>,
178+
#[arg(long)]
179+
#[arg(help = "Page token to continue from a previous page (format: token:index)")]
180+
page_token: Option<String>,
175181
},
176182
UpdateChannelConfig {
177183
#[arg(short, long)]
@@ -407,12 +413,15 @@ async fn main() {
407413
client.list_channels(ListChannelsRequest {}).await,
408414
);
409415
},
410-
Commands::ListPayments { number_of_payments } => {
411-
handle_response_result::<_, ListPaymentsResponse>(
412-
list_n_payments(client, number_of_payments)
413-
.await
414-
// todo: handle pagination properly
415-
.map(|payments| ListPaymentsResponse { payments, next_page_token: None }),
416+
Commands::ListPayments { number_of_payments, page_token } => {
417+
let page_token = if let Some(token_str) = page_token {
418+
Some(parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e)))
419+
} else {
420+
None
421+
};
422+
423+
handle_response_result::<_, CliListPaymentsResponse>(
424+
handle_list_payments(client, number_of_payments, page_token).await,
416425
);
417426
},
418427
Commands::UpdateChannelConfig {
@@ -466,24 +475,37 @@ fn build_open_channel_config(
466475
})
467476
}
468477

478+
async fn handle_list_payments(
479+
client: LdkServerClient, number_of_payments: Option<u64>, initial_page_token: Option<PageToken>,
480+
) -> Result<ListPaymentsResponse, LdkServerError> {
481+
if let Some(count) = number_of_payments {
482+
list_n_payments(client, count, initial_page_token).await
483+
} else {
484+
// Fetch single page
485+
client.list_payments(ListPaymentsRequest { page_token: initial_page_token }).await
486+
}
487+
}
488+
469489
async fn list_n_payments(
470-
client: LdkServerClient, number_of_payments: Option<u64>,
471-
) -> Result<Vec<Payment>, LdkServerError> {
472-
let mut payments = Vec::new();
473-
let mut page_token: Option<PageToken> = None;
474-
// If no count is specified, just list the first page.
475-
let target_count = number_of_payments.unwrap_or(0);
490+
client: LdkServerClient, target_count: u64, initial_page_token: Option<PageToken>,
491+
) -> Result<ListPaymentsResponse, LdkServerError> {
492+
let mut payments = Vec::with_capacity(target_count as usize);
493+
let mut page_token = initial_page_token;
494+
let mut next_page_token;
476495

477496
loop {
478497
let response = client.list_payments(ListPaymentsRequest { page_token }).await?;
479498

480499
payments.extend(response.payments);
481-
if payments.len() >= target_count as usize || response.next_page_token.is_none() {
500+
next_page_token = response.next_page_token;
501+
502+
if payments.len() >= target_count as usize || next_page_token.is_none() {
482503
break;
483504
}
484-
page_token = response.next_page_token;
505+
page_token = next_page_token;
485506
}
486-
Ok(payments)
507+
508+
Ok(ListPaymentsResponse { payments, next_page_token })
487509
}
488510

489511
fn handle_response_result<Rs, Js>(response: Result<Rs, LdkServerError>)
@@ -508,6 +530,20 @@ where
508530
}
509531
}
510532

533+
fn parse_page_token(token_str: &str) -> Result<PageToken, LdkServerError> {
534+
let parts: Vec<&str> = token_str.split(':').collect();
535+
if parts.len() != 2 {
536+
return Err(LdkServerError::new(
537+
InternalError,
538+
"Page token must be in format 'token:index'".to_string(),
539+
));
540+
}
541+
let index = parts[1]
542+
.parse::<i64>()
543+
.map_err(|_| LdkServerError::new(InternalError, "Invalid page token index".to_string()))?;
544+
Ok(PageToken { token: parts[0].to_string(), index })
545+
}
546+
511547
fn handle_error(e: LdkServerError) -> ! {
512548
let error_type = match e.error_code {
513549
InvalidRequestError => "Invalid Request",

ldk-server-cli/src/types.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//! CLI-specific type wrappers for API responses.
2+
//!
3+
//! This file contains wrapper types that customize the serialization format
4+
//! of API responses for CLI output. These wrappers ensure that the CLI's output
5+
//! format matches what users expect and what the CLI can parse back as input.
6+
7+
use ldk_server_client::ldk_server_protos::api::ListPaymentsResponse;
8+
use ldk_server_client::ldk_server_protos::types::{PageToken, Payment};
9+
use serde::Serialize;
10+
11+
/// CLI-specific wrapper for ListPaymentsResponse that formats the page token
12+
/// as "token:idx" instead of a JSON object.
13+
#[derive(Debug, Clone, Serialize)]
14+
pub struct CliListPaymentsResponse {
15+
/// List of payments.
16+
pub payments: Vec<Payment>,
17+
/// Next page token formatted as "token:idx", or None if no more pages.
18+
#[serde(skip_serializing_if = "Option::is_none")]
19+
pub next_page_token: Option<String>,
20+
}
21+
22+
impl From<ListPaymentsResponse> for CliListPaymentsResponse {
23+
fn from(response: ListPaymentsResponse) -> Self {
24+
let next_page_token = response.next_page_token.map(format_page_token);
25+
26+
CliListPaymentsResponse { payments: response.payments, next_page_token }
27+
}
28+
}
29+
30+
fn format_page_token(token: PageToken) -> String {
31+
format!("{}:{}", token.token, token.index)
32+
}

0 commit comments

Comments
 (0)