Skip to content

Commit f6990d6

Browse files
feat: add payment-method commands to CLI (#439)
* feat: add payment-method commands to CLI Add top-level payment-method command for better discoverability: - redisctl cloud payment-method list This complements the existing 'account get-payment-methods' command with a more intuitive command structure that matches other resource types (database, subscription, etc.). Changes: - Added CloudPaymentMethodCommands enum with List subcommand - Created payment_method.rs handler with table formatting - Wired up command routing in main.rs and cloud/mod.rs - Reuses existing AccountHandler.get_account_payment_methods() API The command displays payment methods in a clean table format showing: - ID - Type (credit-card, etc.) - Last 4 digits - Expiration date Supports all standard output formats (table, json, yaml) and JMESPath query filtering. Fixes #426 * test: add CLI tests for payment-method command Add tests to verify: - payment-method help text displays correctly - payment-method list subcommand help works Following best practice of adding CLI tests for all new commands to catch argument parsing issues and help text regressions.
1 parent abe6620 commit f6990d6

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

crates/redisctl/src/cli.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,10 @@ pub enum CloudCommands {
11641164
#[command(subcommand)]
11651165
Account(CloudAccountCommands),
11661166

1167+
/// Payment method operations
1168+
#[command(subcommand, name = "payment-method")]
1169+
PaymentMethod(CloudPaymentMethodCommands),
1170+
11671171
/// Subscription operations
11681172
#[command(subcommand)]
11691173
Subscription(CloudSubscriptionCommands),
@@ -1456,6 +1460,12 @@ pub enum CloudAccountCommands {
14561460
GetSearchScaling,
14571461
}
14581462

1463+
#[derive(Subcommand, Debug)]
1464+
pub enum CloudPaymentMethodCommands {
1465+
/// List payment methods configured for the account
1466+
List,
1467+
}
1468+
14591469
#[derive(Subcommand, Debug)]
14601470
pub enum CloudSubscriptionCommands {
14611471
/// List all subscriptions

crates/redisctl/src/commands/cloud/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod database;
1818
pub mod database_impl;
1919
pub mod fixed_database;
2020
pub mod fixed_subscription;
21+
pub mod payment_method;
2122
pub mod subscription;
2223
pub mod subscription_impl;
2324
pub mod task;
@@ -32,6 +33,8 @@ pub use connectivity::handle_connectivity_command;
3233
#[allow(unused_imports)]
3334
pub use database::handle_database_command;
3435
#[allow(unused_imports)]
36+
pub use payment_method::handle_payment_method_command;
37+
#[allow(unused_imports)]
3538
pub use subscription::handle_subscription_command;
3639
#[allow(unused_imports)]
3740
pub use user::handle_user_command;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! Cloud payment method command implementations
2+
3+
#![allow(dead_code)] // Used by binary target
4+
5+
use anyhow::Context;
6+
use redis_cloud::AccountHandler;
7+
use serde_json::Value;
8+
use tabled::{Table, settings::Style};
9+
10+
use crate::cli::{CloudPaymentMethodCommands, OutputFormat};
11+
use crate::connection::ConnectionManager;
12+
use crate::error::Result as CliResult;
13+
14+
use super::utils::*;
15+
16+
/// Handle cloud payment method commands
17+
pub async fn handle_payment_method_command(
18+
conn_mgr: &ConnectionManager,
19+
profile_name: Option<&str>,
20+
command: &CloudPaymentMethodCommands,
21+
output_format: OutputFormat,
22+
query: Option<&str>,
23+
) -> CliResult<()> {
24+
match command {
25+
CloudPaymentMethodCommands::List => {
26+
list_payment_methods(conn_mgr, profile_name, output_format, query).await
27+
}
28+
}
29+
}
30+
31+
/// List payment methods configured for the account
32+
async fn list_payment_methods(
33+
conn_mgr: &ConnectionManager,
34+
profile_name: Option<&str>,
35+
output_format: OutputFormat,
36+
query: Option<&str>,
37+
) -> CliResult<()> {
38+
let client = conn_mgr.create_cloud_client(profile_name).await?;
39+
let handler = AccountHandler::new(client);
40+
41+
let response = handler
42+
.get_account_payment_methods()
43+
.await
44+
.context("Failed to fetch payment methods")?;
45+
46+
let json_value = serde_json::to_value(response)?;
47+
let data = handle_output(json_value, output_format, query)?;
48+
49+
match output_format {
50+
OutputFormat::Auto | OutputFormat::Table => {
51+
print_payment_methods_table(&data)?;
52+
}
53+
_ => print_formatted_output(data, output_format)?,
54+
}
55+
56+
Ok(())
57+
}
58+
59+
/// Print payment methods in table format
60+
fn print_payment_methods_table(data: &Value) -> CliResult<()> {
61+
let methods = data.get("paymentMethods").and_then(|p| p.as_array());
62+
63+
if let Some(methods) = methods {
64+
if methods.is_empty() {
65+
println!("No payment methods configured");
66+
return Ok(());
67+
}
68+
69+
let mut rows = Vec::new();
70+
for method in methods {
71+
let id = method.get("id").and_then(|v| v.as_u64()).unwrap_or(0);
72+
let type_ = method
73+
.get("type")
74+
.and_then(|v| v.as_str())
75+
.unwrap_or("Unknown");
76+
let last4 = method
77+
.get("last4Digits")
78+
.and_then(|v| v.as_str())
79+
.unwrap_or("");
80+
let exp = method
81+
.get("expirationDate")
82+
.and_then(|v| v.as_str())
83+
.unwrap_or("");
84+
85+
rows.push(PaymentMethodRow {
86+
id: id.to_string(),
87+
payment_type: type_.to_string(),
88+
last_4: last4.to_string(),
89+
expiration: exp.to_string(),
90+
});
91+
}
92+
93+
let mut table = Table::new(&rows);
94+
table.with(Style::blank());
95+
output_with_pager(&table.to_string());
96+
} else {
97+
println!("No payment methods data available");
98+
}
99+
Ok(())
100+
}
101+
102+
// Table row structure for formatting
103+
#[derive(tabled::Tabled)]
104+
struct PaymentMethodRow {
105+
#[tabled(rename = "ID")]
106+
id: String,
107+
#[tabled(rename = "Type")]
108+
payment_type: String,
109+
#[tabled(rename = "Last 4")]
110+
last_4: String,
111+
#[tabled(rename = "Expiration")]
112+
expiration: String,
113+
}

crates/redisctl/src/main.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,17 @@ async fn execute_cloud_command(
858858
.await
859859
}
860860

861+
PaymentMethod(payment_method_cmd) => {
862+
commands::cloud::handle_payment_method_command(
863+
conn_mgr,
864+
cli.profile.as_deref(),
865+
payment_method_cmd,
866+
cli.output,
867+
cli.query.as_deref(),
868+
)
869+
.await
870+
}
871+
861872
Subscription(sub_cmd) => {
862873
commands::cloud::handle_subscription_command(
863874
conn_mgr,

crates/redisctl/tests/cli_basic_tests.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,27 @@ fn test_profile_remove_missing_name() {
257257
.failure()
258258
.stderr(predicate::str::contains("required"));
259259
}
260+
261+
#[test]
262+
fn test_payment_method_help() {
263+
redisctl()
264+
.arg("cloud")
265+
.arg("payment-method")
266+
.arg("--help")
267+
.assert()
268+
.success()
269+
.stdout(predicate::str::contains("Payment method operations"))
270+
.stdout(predicate::str::contains("list"));
271+
}
272+
273+
#[test]
274+
fn test_payment_method_list_help() {
275+
redisctl()
276+
.arg("cloud")
277+
.arg("payment-method")
278+
.arg("list")
279+
.arg("--help")
280+
.assert()
281+
.success()
282+
.stdout(predicate::str::contains("List payment methods"));
283+
}

0 commit comments

Comments
 (0)