]> Untitled Git - bdk-cli/commitdiff
refactor(output): apply generics to output mod
authorVihiga Tyonum <withtvpeter@gmail.com>
Tue, 26 May 2026 08:27:26 +0000 (09:27 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 5 Jun 2026 16:32:14 +0000 (17:32 +0100)
src/commands.rs
src/handlers/key.rs
src/handlers/offline.rs
src/handlers/online.rs
src/handlers/repl.rs
src/main.rs
src/utils/output.rs
src/utils/types.rs

index 52c24045c8ac3f83261e6849b1dac3a754263b5a..226e447b98cfc03a60dfb2dfcc3d11d0e19da980 100644 (file)
@@ -34,7 +34,7 @@ use crate::handlers::{
 ))]
 use crate::{
     client::ClientType,
-    online::{
+    handlers::online::{
         BroadcastCommand, FullScanCommand, ReceivePayjoinCommand, SendPayjoinCommand, SyncCommand,
     },
 };
index 3f175d777380d8aa177b6e5a30eeadf3d370febc..b9dbe34ca045bfa8af495e205e4b71f08b7dc16b 100644 (file)
@@ -17,13 +17,15 @@ use clap::Parser;
 impl KeySubCommand {
     pub fn execute(&self, ctx: &mut AppContext) -> Result<(), Error> {
         match self {
-            KeySubCommand::Generate(generate_key_command) => {
-                generate_key_command.execute(ctx)?.print()
-            }
-            KeySubCommand::Restore(restore_key_command) => {
-                restore_key_command.execute(ctx)?.print()
-            }
-            KeySubCommand::Derive(derive_key_command) => derive_key_command.execute(ctx)?.print(),
+            KeySubCommand::Generate(generate_key_command) => generate_key_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            KeySubCommand::Restore(restore_key_command) => restore_key_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            KeySubCommand::Derive(derive_key_command) => derive_key_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
         }
     }
 }
index 62d89d5793b8f37e1916180cbc068f239eb74f4d..f541f74e2848e1225f2d54f1fd78db6931a01db5 100644 (file)
@@ -1,11 +1,11 @@
 use crate::commands::OfflineWalletSubCommand;
 use crate::error::BDKCliError as Error;
 use crate::handlers::{AppCommand, AppContext};
-use crate::utils::output::FormatOutput;
+use crate::utils::output::{FormatOutput, ListResult};
 use crate::utils::parse_address;
 use crate::utils::types::{
     AddressResult, BalanceResult, KeychainPair, PsbtResult, RawPsbt, TransactionDetails,
-    TransactionListResult, UnspentDetails, UnspentListResult,
+    UnspentDetails,
 };
 use crate::utils::{parse_outpoint, parse_recipient};
 use bdk_wallet::bitcoin::base64::Engine;
@@ -27,31 +27,47 @@ use {
 impl OfflineWalletSubCommand {
     pub fn execute(&self, ctx: &mut AppContext<'_>) -> Result<(), Error> {
         match self {
-            Self::NewAddress(new_address) => new_address.execute(ctx)?.print(),
-            Self::Balance(balance) => balance.execute(ctx)?.print(),
-            Self::UnusedAddress(unused_address_command) => {
-                unused_address_command.execute(ctx)?.print()
+            Self::NewAddress(new_address) => new_address.execute(ctx)?.write_out(std::io::stdout()),
+            Self::Balance(balance) => balance.execute(ctx)?.write_out(std::io::stdout()),
+            Self::UnusedAddress(unused_address_command) => unused_address_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            Self::Unspent(unspent_command) => {
+                unspent_command.execute(ctx)?.write_out(std::io::stdout())
             }
-            Self::Unspent(unspent_command) => unspent_command.execute(ctx)?.print(),
-            Self::Transactions(transactions_command) => transactions_command.execute(ctx)?.print(),
-            Self::CreateTx(createtx_command) => createtx_command.execute(ctx)?.print(),
-            Self::BumpFee(bumpfee_command) => bumpfee_command.execute(ctx)?.print(),
-            Self::Policies(policies_command) => policies_command.execute(ctx)?.print(),
-            Self::PublicDescriptor(public_descriptor_command) => {
-                public_descriptor_command.execute(ctx)?.print()
+            Self::Transactions(transactions_command) => transactions_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            Self::CreateTx(createtx_command) => {
+                createtx_command.execute(ctx)?.write_out(std::io::stdout())
             }
-            Self::Sign(sign_command) => sign_command.execute(ctx)?.print(),
-            Self::ExtractPsbt(extract_psbt_command) => extract_psbt_command.execute(ctx)?.print(),
-            Self::FinalizePsbt(finalize_psbt_command) => {
-                finalize_psbt_command.execute(ctx)?.print()
+            Self::BumpFee(bumpfee_command) => {
+                bumpfee_command.execute(ctx)?.write_out(std::io::stdout())
             }
-            Self::CombinePsbt(combine_psbt_command) => combine_psbt_command.execute(ctx)?.print(),
+            Self::Policies(policies_command) => {
+                policies_command.execute(ctx)?.write_out(std::io::stdout())
+            }
+            Self::PublicDescriptor(public_descriptor_command) => public_descriptor_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            Self::Sign(sign_command) => sign_command.execute(ctx)?.write_out(std::io::stdout()),
+            Self::ExtractPsbt(extract_psbt_command) => extract_psbt_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            Self::FinalizePsbt(finalize_psbt_command) => finalize_psbt_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
+            Self::CombinePsbt(combine_psbt_command) => combine_psbt_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
             #[cfg(feature = "bip322")]
-            Self::SignMessage(sign_message_command) => sign_message_command.execute(ctx)?.print(),
+            Self::SignMessage(sign_message_command) => sign_message_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
             #[cfg(feature = "bip322")]
-            Self::VerifyMessage(verify_message_command) => {
-                verify_message_command.execute(ctx)?.print()
-            }
+            Self::VerifyMessage(verify_message_command) => verify_message_command
+                .execute(ctx)?
+                .write_out(std::io::stdout()),
         }
     }
 }
@@ -92,7 +108,7 @@ impl AppCommand for UnusedAddressCommand {
 pub struct UnspentCommand {}
 
 impl AppCommand for UnspentCommand {
-    type Output = UnspentListResult;
+    type Output = ListResult<UnspentDetails>;
 
     fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
         let wallet = ctx
@@ -104,7 +120,7 @@ impl AppCommand for UnspentCommand {
             .map(|utxo| UnspentDetails::from_local_output(&utxo, ctx.network))
             .collect();
 
-        Ok(UnspentListResult(utxos))
+        Ok(ListResult::new(utxos))
     }
 }
 
@@ -112,7 +128,7 @@ impl AppCommand for UnspentCommand {
 pub struct TransactionsCommand {}
 
 impl AppCommand for TransactionsCommand {
-    type Output = TransactionListResult;
+    type Output = ListResult<TransactionDetails>;
 
     fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
         let wallet = ctx
@@ -147,7 +163,7 @@ impl AppCommand for TransactionsCommand {
             })
             .collect();
 
-        Ok(TransactionListResult(txns))
+        Ok(ListResult::new(txns))
     }
 }
 
index eb125815152d3fc578a21655865cbdbfa4c77d6d..866f6f94baf01efa27732fa95341883ab4fea07d 100644 (file)
@@ -46,17 +46,24 @@ impl OnlineWalletSubCommand {
     pub async fn execute(&self, ctx: &mut AppContext<'_>) -> Result<(), Error> {
         match self {
             OnlineWalletSubCommand::FullScan(full_scan_command) => {
-                full_scan_command.execute(ctx).await?.print()
+                let response: StatusResult = full_scan_command.execute(ctx).await?;
+                response.write_out(std::io::stdout())
+            }
+            OnlineWalletSubCommand::Sync(sync_command) => {
+                let response: StatusResult = sync_command.execute(ctx).await?;
+                response.write_out(std::io::stdout())
             }
-            OnlineWalletSubCommand::Sync(sync_command) => sync_command.execute(ctx).await?.print(),
             OnlineWalletSubCommand::Broadcast(broadcast_command) => {
-                broadcast_command.execute(ctx).await?.print()
+                let response: TransactionResult = broadcast_command.execute(ctx).await?;
+                response.write_out(std::io::stdout())
             }
             OnlineWalletSubCommand::ReceivePayjoin(receive_payjoin_command) => {
-                receive_payjoin_command.execute(ctx).await?.print()
+                let response: StatusResult = receive_payjoin_command.execute(ctx).await?;
+                response.write_out(std::io::stdout())
             }
             OnlineWalletSubCommand::SendPayjoin(send_payjoin_command) => {
-                send_payjoin_command.execute(ctx).await?.print()
+                let response: StatusResult = send_payjoin_command.execute(ctx).await?;
+                response.write_out(std::io::stdout())
             }
         }
     }
@@ -299,7 +306,6 @@ pub struct BroadcastCommand {
     feature = "cbf",
     feature = "rpc"
 ))]
-
 impl AsyncCommand for BroadcastCommand {
     type Output = TransactionResult;
 
index 6f9bd521780e886e87d82ea0365213db5c23e3df..19206b251e7c49c23ca2d379463ab7f7a65d8d88 100644 (file)
@@ -1,6 +1,6 @@
 use bdk_wallet::{Wallet, bitcoin::Network};
 
-// #[cfg(feature = "repl")]
+#[cfg(feature = "repl")]
 use crate::handlers::{AppCommand, AppContext};
 use crate::utils::output::FormatOutput;
 
@@ -56,17 +56,20 @@ pub(crate) async fn respond(
             }
             WalletSubCommand::Config(config_cmd) => {
                 let mut ctx = AppContext::new(network, datadir);
-                let res = config_cmd
+                let _ = config_cmd
                     .execute(&mut ctx)
                     .map_err(|e| e.to_string())?
-                    .print();
+                    .write_out(std::io::stdout());
                 Some(())
             }
         },
 
         // Assuming your REPL Descriptor command is an inline struct based on commands.rs
         ReplSubCommand::Descriptor(cmd) => {
-            let value = cmd.execute(&mut ctx).map_err(|e| e.to_string())?.print();
+            let value = cmd
+                .execute(&mut ctx)
+                .map_err(|e| e.to_string())?
+                .write_out(std::io::stdout());
             Some(())
         }
 
index f8877a66a43a82a81a96fb575601d00a3bc1d80a..99f480b6df5d0f698a36364d4f1510dacb2e378b 100644 (file)
@@ -151,7 +151,7 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
 
                 let mut ctx = AppContext::new(cli_opts.network, home_dir);
 
-                config_cmd.execute(&mut ctx)?.print()?;
+                config_cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
             }
         },
 
@@ -161,11 +161,13 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
         }
         CliSubCommand::Descriptor(descriptor_command) => {
             let mut ctx = AppContext::new(cli_opts.network, home_dir);
-            descriptor_command.execute(&mut ctx)?.print()?;
+            descriptor_command
+                .execute(&mut ctx)?
+                .write_out(std::io::stdout())?;
         }
         CliSubCommand::Wallets(cmd) => {
             let mut ctx = AppContext::new(cli_opts.network, home_dir);
-            cmd.execute(&mut ctx)?.print()?;
+            cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
         }
         CliSubCommand::Repl { wallet: _ } => todo!(),
         CliSubCommand::Completions { shell } => {
@@ -174,7 +176,7 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
         #[cfg(feature = "compiler")]
         CliSubCommand::Compile(cmd) => {
             let mut ctx = AppContext::new(cli_opts.network, home_dir);
-            cmd.execute(&mut ctx)?.print()?;
+            cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
         }
     };
 
index 3f9a5e9fb329332bdc1ee755a3c476cce710159b..b2cd32a24233356e89c2525269266abd67a05096 100644 (file)
@@ -1,3 +1,5 @@
+use std::io::Write;
+
 use crate::error::BDKCliError as Error;
 use serde::Serialize;
 
@@ -8,9 +10,28 @@ pub trait FormatOutput: Serialize {
             .map_err(|e| Error::Generic(format!("JSON serialization failed: {e}")))
     }
 
-    fn print(&self) -> Result<(), Error> {
+    fn write_out<W: Write>(&self, mut writer: W) -> Result<(), Error> {
         let output = self.format()?;
-        println!("{}", output);
-        Ok(())
+
+        writeln!(writer, "{}", output)
+            .map_err(|e| Error::Generic(format!("Failed to write output: {e}")))
+    }
+}
+
+impl<T: Serialize> FormatOutput for T {}
+
+/// A generic wrapper for commands that return a list of items.
+#[derive(Serialize)]
+pub struct ListResult<T> {
+    pub count: usize,
+    pub items: Vec<T>,
+}
+
+impl<T> ListResult<T> {
+    pub fn new(items: Vec<T>) -> Self {
+        Self {
+            count: items.len(),
+            items,
+        }
     }
 }
index 2fa16b974448662b905c10a8934db214d9d5066f..5135343907d1ab2e39e449e1a9ca8a046b77d00f 100644 (file)
@@ -1,7 +1,6 @@
 use std::collections::HashMap;
 
 use crate::config::WalletConfigInner;
-use crate::utils::output::FormatOutput;
 use crate::utils::shorten;
 use bdk_wallet::Balance;
 use bdk_wallet::bitcoin::{
@@ -27,8 +26,6 @@ impl From<AddressInfo> for AddressResult {
     }
 }
 
-impl FormatOutput for AddressResult {}
-
 /// Represents the data for a single transaction
 #[derive(Serialize)]
 pub struct TransactionDetails {
@@ -49,13 +46,6 @@ pub struct TransactionDetails {
     pub total_value: u64,
 }
 
-/// A wrapper type for a list of transactions.
-#[derive(Serialize)]
-#[serde(transparent)]
-pub struct TransactionListResult(pub Vec<TransactionDetails>);
-
-impl FormatOutput for TransactionListResult {}
-
 /// single UTXO
 #[derive(Serialize)]
 pub struct UnspentDetails {
@@ -99,13 +89,6 @@ impl UnspentDetails {
     }
 }
 
-/// Wrapper for the list of UTXOs
-#[derive(Serialize)]
-#[serde(transparent)]
-pub struct UnspentListResult(pub Vec<UnspentDetails>);
-
-impl FormatOutput for UnspentListResult {}
-
 #[derive(Serialize)]
 pub struct PsbtResult {
     pub psbt: String,
@@ -131,8 +114,6 @@ impl PsbtResult {
     }
 }
 
-impl FormatOutput for PsbtResult {}
-
 #[derive(Serialize)]
 pub struct RawPsbt {
     pub raw_tx: String,
@@ -146,19 +127,12 @@ impl RawPsbt {
     }
 }
 
-impl FormatOutput for RawPsbt {}
-
 #[derive(Serialize)]
 pub struct KeychainPair<T> {
     pub external: T,
     pub internal: T,
 }
 
-impl FormatOutput for KeychainPair<String> {}
-
-// Table formatting for JSON value pairs (used by Policies)
-impl FormatOutput for KeychainPair<serde_json::Value> {}
-
 #[derive(Serialize, Debug, Default)]
 pub struct MessageResult {
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -171,8 +145,6 @@ pub struct MessageResult {
     pub proven_amount: Option<u64>,
 }
 
-impl FormatOutput for MessageResult {}
-
 #[derive(Serialize, Debug)]
 pub struct StatusResult {
     pub message: String,
@@ -186,22 +158,16 @@ impl StatusResult {
     }
 }
 
-impl FormatOutput for StatusResult {}
-
 #[derive(Serialize, Debug)]
 pub struct TransactionResult {
     pub txid: String,
 }
 
-impl FormatOutput for TransactionResult {}
-
 /// Return type definition
 #[derive(Serialize)]
 #[serde(transparent)]
 pub struct WalletsListResult(pub HashMap<String, WalletConfigInner>);
 
-impl FormatOutput for WalletsListResult {}
-
 /// return type
 #[derive(Serialize)]
 pub struct DescriptorResult {
@@ -224,8 +190,6 @@ pub struct DescriptorResult {
     pub fingerprint: Option<String>,
 }
 
-impl FormatOutput for DescriptorResult {}
-
 #[derive(Serialize)]
 pub struct KeyResult {
     pub xprv: String,
@@ -240,8 +204,6 @@ pub struct KeyResult {
     pub fingerprint: Option<String>,
 }
 
-impl FormatOutput for KeyResult {}
-
 /// Balance representation
 #[derive(Serialize)]
 pub struct BalanceResult {
@@ -263,5 +225,3 @@ impl From<Balance> for BalanceResult {
         }
     }
 }
-
-impl FormatOutput for BalanceResult {}