Skip to content

Commit bfc6312

Browse files
committed
Implement proper pagination for list-payments cli
1 parent e9f047b commit bfc6312

File tree

2 files changed

+85
-17
lines changed

2 files changed

+85
-17
lines changed

ldk-server-cli/src/main.rs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ use ldk_server_client::ldk_server_protos::api::{
2424
SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse,
2525
};
2626
use ldk_server_client::ldk_server_protos::types::{
27-
bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, Payment,
27+
bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken,
2828
RouteParametersConfig,
2929
};
3030
use serde::Serialize;
31+
use types::CliListPaymentsResponse;
32+
33+
mod types;
3134

3235
// Having these default values as constants in the Proto file and
3336
// importing/reusing them here might be better, but Proto3 removed
@@ -178,9 +181,12 @@ enum Commands {
178181
ListPayments {
179182
#[arg(short, long)]
180183
#[arg(
181-
help = "Minimum number of payments to return. If not provided, only the first page of the paginated list is returned."
184+
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."
182185
)]
183186
number_of_payments: Option<u64>,
187+
#[arg(long)]
188+
#[arg(help = "Page token to continue from a previous page (format: token:index)")]
189+
page_token: Option<String>,
184190
},
185191
UpdateChannelConfig {
186192
#[arg(short, long)]
@@ -416,12 +422,15 @@ async fn main() {
416422
client.list_channels(ListChannelsRequest {}).await,
417423
);
418424
},
419-
Commands::ListPayments { number_of_payments } => {
420-
handle_response_result::<_, ListPaymentsResponse>(
421-
list_n_payments(client, number_of_payments)
422-
.await
423-
// todo: handle pagination properly
424-
.map(|payments| ListPaymentsResponse { payments, next_page_token: None }),
425+
Commands::ListPayments { number_of_payments, page_token } => {
426+
let page_token = if let Some(token_str) = page_token {
427+
Some(parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e)))
428+
} else {
429+
None
430+
};
431+
432+
handle_response_result::<_, CliListPaymentsResponse>(
433+
handle_list_payments(client, number_of_payments, page_token).await,
425434
);
426435
},
427436
Commands::UpdateChannelConfig {
@@ -475,24 +484,37 @@ fn build_open_channel_config(
475484
})
476485
}
477486

487+
async fn handle_list_payments(
488+
client: LdkServerClient, number_of_payments: Option<u64>, initial_page_token: Option<PageToken>,
489+
) -> Result<ListPaymentsResponse, LdkServerError> {
490+
if let Some(count) = number_of_payments {
491+
list_n_payments(client, count, initial_page_token).await
492+
} else {
493+
// Fetch single page
494+
client.list_payments(ListPaymentsRequest { page_token: initial_page_token }).await
495+
}
496+
}
497+
478498
async fn list_n_payments(
479-
client: LdkServerClient, number_of_payments: Option<u64>,
480-
) -> Result<Vec<Payment>, LdkServerError> {
481-
let mut payments = Vec::new();
482-
let mut page_token: Option<PageToken> = None;
483-
// If no count is specified, just list the first page.
484-
let target_count = number_of_payments.unwrap_or(0);
499+
client: LdkServerClient, target_count: u64, initial_page_token: Option<PageToken>,
500+
) -> Result<ListPaymentsResponse, LdkServerError> {
501+
let mut payments = Vec::with_capacity(target_count as usize);
502+
let mut page_token = initial_page_token;
503+
let mut next_page_token;
485504

486505
loop {
487506
let response = client.list_payments(ListPaymentsRequest { page_token }).await?;
488507

489508
payments.extend(response.payments);
490-
if payments.len() >= target_count as usize || response.next_page_token.is_none() {
509+
next_page_token = response.next_page_token;
510+
511+
if payments.len() >= target_count as usize || next_page_token.is_none() {
491512
break;
492513
}
493-
page_token = response.next_page_token;
514+
page_token = next_page_token;
494515
}
495-
Ok(payments)
516+
517+
Ok(ListPaymentsResponse { payments, next_page_token })
496518
}
497519

498520
fn handle_response_result<Rs, Js>(response: Result<Rs, LdkServerError>)
@@ -517,6 +539,20 @@ where
517539
}
518540
}
519541

542+
fn parse_page_token(token_str: &str) -> Result<PageToken, LdkServerError> {
543+
let parts: Vec<&str> = token_str.split(':').collect();
544+
if parts.len() != 2 {
545+
return Err(LdkServerError::new(
546+
InternalError,
547+
"Page token must be in format 'token:index'".to_string(),
548+
));
549+
}
550+
let index = parts[1]
551+
.parse::<i64>()
552+
.map_err(|_| LdkServerError::new(InternalError, "Invalid page token index".to_string()))?;
553+
Ok(PageToken { token: parts[0].to_string(), index })
554+
}
555+
520556
fn handle_error(e: LdkServerError) -> ! {
521557
let error_type = match e.error_code {
522558
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)