]> Untitled Git - bdk-cli/commitdiff
ref(types): add simple table helper
authorVihiga Tyonum <withtvpeter@gmail.com>
Wed, 29 Apr 2026 08:11:33 +0000 (09:11 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 5 Jun 2026 16:12:21 +0000 (17:12 +0100)
- add simple table helper fn for creating tables
- refactor types to use simple table helper
- add type defs for key, wallets and descriptors

src/handlers/types.rs
src/utils/output.rs

index cd25313b81d4b593558bb2a456fb9bb78c54e934..94aca12359a70083d648cf2d75ac86af97172846 100644 (file)
@@ -1,11 +1,12 @@
-use crate::utils::output::FormatOutput;
+use std::collections::HashMap;
+
+use crate::config::WalletConfigInner;
+use crate::utils::output::{FormatOutput, simple_table};
 use crate::{error::BDKCliError as Error, utils::shorten};
-use bdk_wallet::bitcoin::base64::Engine;
-use bdk_wallet::bitcoin::base64::prelude::BASE64_STANDARD;
-use bdk_wallet::bitcoin::consensus::encode::serialize_hex;
-use bdk_wallet::bitcoin::{Address, Network, Psbt, Transaction};
-use bdk_wallet::chain::ChainPosition;
-use bdk_wallet::{AddressInfo, Balance, LocalOutput};
+use bdk_wallet::bitcoin::{
+    Address, Network, Psbt, Transaction, base64::Engine, consensus::encode::serialize_hex,
+};
+use bdk_wallet::{AddressInfo, Balance, LocalOutput, chain::ChainPosition};
 use cli_table::{Cell, CellStruct, Style, Table, format::Justify};
 use serde::Serialize;
 use serde_json::json;
@@ -29,15 +30,16 @@ impl From<AddressInfo> for AddressResult {
 /// pretty presentation for address
 impl FormatOutput for AddressResult {
     fn to_table(&self) -> Result<String, Error> {
-        let table = vec![
-            vec!["Address".cell().bold(true), self.address.clone().cell()],
-            vec!["Index".cell().bold(true), self.index.cell()],
-        ]
-        .table()
-        .display()
-        .map_err(|e| Error::Generic(e.to_string()))?;
-
-        Ok(format!("{table}"))
+        simple_table(
+            vec![
+                vec!["Address".cell().bold(true), self.address.clone().cell()],
+                vec![
+                    "Index".cell().bold(true),
+                    self.index.cell().justify(Justify::Right),
+                ],
+            ],
+            None,
+        )
     }
 }
 
@@ -122,37 +124,34 @@ impl From<Balance> for BalanceResult {
 
 impl FormatOutput for BalanceResult {
     fn to_table(&self) -> Result<String, Error> {
-        let table = vec![
-            vec![
-                "Total".cell().bold(true),
-                self.total.cell().justify(Justify::Right),
-            ],
-            vec![
-                "Confirmed".cell().bold(true),
-                self.confirmed.cell().justify(Justify::Right),
-            ],
-            vec![
-                "Trusted Pending".cell().bold(true),
-                self.trusted_pending.cell().justify(Justify::Right),
-            ],
-            vec![
-                "Untrusted Pending".cell().bold(true),
-                self.untrusted_pending.cell().justify(Justify::Right),
-            ],
+        simple_table(
             vec![
-                "Immature".cell().bold(true),
-                self.immature.cell().justify(Justify::Right),
+                vec![
+                    "Total".cell().bold(true),
+                    self.total.cell().justify(Justify::Right),
+                ],
+                vec![
+                    "Confirmed".cell().bold(true),
+                    self.confirmed.cell().justify(Justify::Right),
+                ],
+                vec![
+                    "Trusted Pending".cell().bold(true),
+                    self.trusted_pending.cell().justify(Justify::Right),
+                ],
+                vec![
+                    "Untrusted Pending".cell().bold(true),
+                    self.untrusted_pending.cell().justify(Justify::Right),
+                ],
+                vec![
+                    "Immature".cell().bold(true),
+                    self.immature.cell().justify(Justify::Right),
+                ],
             ],
-        ]
-        .table()
-        .title(vec![
-            "Status".cell().bold(true),
-            "Amount (sat)".cell().bold(true),
-        ])
-        .display()
-        .map_err(|e| Error::Generic(e.to_string()))?;
-
-        Ok(format!("{table}"))
+            Some(vec![
+                "Status".cell().bold(true),
+                "Amount (sat)".cell().bold(true),
+            ]),
+        )
     }
 }
 
@@ -272,32 +271,12 @@ pub struct PsbtResult {
 }
 
 impl PsbtResult {
-    pub fn new(psbt: &Psbt) -> Self {
-        Self {
-            psbt: BASE64_STANDARD.encode(psbt.serialize()),
-            is_finalized: None,
-            details: None,
-        }
-    }
-
-    pub fn with_details(psbt: &Psbt, verbose: bool) -> Self {
-        Self {
-            psbt: BASE64_STANDARD.encode(psbt.serialize()),
-            is_finalized: None,
-            details: if verbose {
-                Some(serde_json::to_value(psbt).unwrap_or(json!({})))
-            } else {
-                None
-            },
-        }
-    }
-
-    pub fn with_status_and_details(psbt: &Psbt, is_finalized: bool, verbose: bool) -> Self {
+    pub fn new(psbt: &Psbt, verbose: bool, finalized: Option<bool>) -> Self {
         Self {
-            psbt: BASE64_STANDARD.encode(psbt.serialize()),
-            is_finalized: Some(is_finalized),
+            psbt: bdk_wallet::bitcoin::base64::prelude::BASE64_STANDARD.encode(psbt.serialize()),
+            is_finalized: finalized,
             details: if verbose {
-                Some(serde_json::to_value(psbt).unwrap_or(json!({})))
+                Some(serde_json::to_value(psbt).unwrap_or_default())
             } else {
                 None
             },
@@ -331,24 +310,25 @@ impl FormatOutput for PsbtResult {
     }
 }
 
-/// Policies representation
 #[derive(Serialize)]
-pub struct PoliciesResult {
-    pub external: serde_json::Value,
-    pub internal: serde_json::Value,
+pub struct RawPsbt {
+    pub raw_tx: String,
 }
 
-impl FormatOutput for PoliciesResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let ext_str = serde_json::to_string_pretty(&self.external)
-            .map_err(|e| Error::Generic(e.to_string()))?;
-        let int_str = serde_json::to_string_pretty(&self.internal)
-            .map_err(|e| Error::Generic(e.to_string()))?;
+impl RawPsbt {
+    pub fn new(tx: &Transaction) -> Self {
+        Self {
+            raw_tx: serialize_hex(tx),
+        }
+    }
+}
 
-        let table = vec![
-            vec!["External".cell().bold(true), ext_str.cell()],
-            vec!["Internal".cell().bold(true), int_str.cell()],
-        ]
+impl FormatOutput for RawPsbt {
+    fn to_table(&self) -> Result<String, Error> {
+        let table = vec![vec![
+            "Raw Transaction".cell().bold(true),
+            self.raw_tx.clone().cell(),
+        ]]
         .table()
         .display()
         .map_err(|e| Error::Generic(e.to_string()))?;
@@ -358,54 +338,109 @@ impl FormatOutput for PoliciesResult {
 }
 
 #[derive(Serialize)]
-pub struct PublicDescriptorResult {
-    pub external: String,
-    pub internal: String,
+pub struct KeychainPair<T> {
+    pub external: T,
+    pub internal: T,
 }
 
-impl FormatOutput for PublicDescriptorResult {
+// Table formatting for string pairs (used by PublicDescriptor)
+impl FormatOutput for KeychainPair<String> {
     fn to_table(&self) -> Result<String, Error> {
-        let table = vec![
-            vec![
-                "External Descriptor".cell().bold(true),
-                self.external.clone().cell(),
-            ],
-            vec![
-                "Internal Descriptor".cell().bold(true),
-                self.internal.clone().cell(),
-            ],
-        ]
-        .table()
-        .display()
-        .map_err(|e| Error::Generic(e.to_string()))?;
-
-        Ok(format!("{table}"))
+        let rows = vec![
+            vec!["External".cell().bold(true), self.external.clone().cell()],
+            vec!["Internal".cell().bold(true), self.internal.clone().cell()],
+        ];
+        simple_table(rows, None)
     }
 }
 
+// Table formatting for JSON value pairs (used by Policies)
+impl FormatOutput for KeychainPair<serde_json::Value> {
+    fn to_table(&self) -> Result<String, Error> {
+        let ext_str = serde_json::to_string_pretty(&self.external)
+            .map_err(|e| Error::Generic(e.to_string()))?;
+        let int_str = serde_json::to_string_pretty(&self.internal)
+            .map_err(|e| Error::Generic(e.to_string()))?;
+
+        let rows = vec![
+            vec!["External".cell().bold(true), ext_str.cell()],
+            vec!["Internal".cell().bold(true), int_str.cell()],
+        ];
+        simple_table(rows, None)
+    }
+}
 #[derive(Serialize)]
-pub struct RawPsbt {
-    pub raw_tx: String,
+pub struct KeyResult {
+    pub xprv: String,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub xpub: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub mnemonic: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub fingerprint: Option<String>,
 }
 
-impl RawPsbt {
-    pub fn new(tx: &Transaction) -> Self {
-        Self {
-            raw_tx: serialize_hex(tx),
+impl FormatOutput for KeyResult {
+    fn to_table(&self) -> Result<String, Error> {
+        let mut rows: Vec<Vec<CellStruct>> = vec![];
+
+        if let Some(mnemonic) = &self.mnemonic {
+            rows.push(vec!["Mnemonic".cell().bold(true), mnemonic.clone().cell()]);
         }
+        if let Some(xpub) = &self.xpub {
+            rows.push(vec!["Xpub".cell().bold(true), xpub.clone().cell()]);
+        }
+
+        rows.push(vec!["Xprv".cell().bold(true), self.xprv.clone().cell()]);
+
+        if let Some(fingerprint) = &self.fingerprint {
+            rows.push(vec![
+                "Fingerprint".cell().bold(true),
+                fingerprint.clone().cell(),
+            ]);
+        }
+
+        simple_table(rows, None)
     }
 }
 
-impl FormatOutput for RawPsbt {
+
+#[derive(Serialize)]
+#[serde(transparent)]
+pub struct WalletsListResult(pub HashMap<String, WalletConfigInner>);
+
+impl FormatOutput for WalletsListResult {
     fn to_table(&self) -> Result<String, Error> {
-        let table = vec![vec![
-            "Raw Transaction".cell().bold(true),
-            self.raw_tx.clone().cell(),
-        ]]
-        .table()
-        .display()
-        .map_err(|e| Error::Generic(e.to_string()))?;
+        if self.0.is_empty() {
+            return Ok("No wallets configured yet.".to_string());
+        }
 
-        Ok(format!("{table}"))
+        let mut rows: Vec<Vec<CellStruct>> = vec![];
+        for (name, inner) in &self.0 {
+            let desc: String = inner.ext_descriptor.chars().take(30).collect();
+            let desc_display = if inner.ext_descriptor.len() > 30 {
+                format!("{}...", desc)
+            } else {
+                desc
+            };
+
+            rows.push(vec![
+                name.clone().cell(),
+                inner.network.clone().cell(),
+                desc_display.cell(),
+            ]);
+        }
+
+        simple_table(
+            rows,
+            Some(vec![
+                "Wallet Name".cell().bold(true),
+                "Network".cell().bold(true),
+                "External Descriptor".cell().bold(true),
+            ]),
+        )
     }
 }
index 96b8d9ee59be228d9f273ff80994060f3673d17a..5c4360b8e3614e24a2e2eb853e7bb222918c2189 100644 (file)
@@ -1,9 +1,10 @@
 use crate::error::BDKCliError as Error;
+use cli_table::{CellStruct, Table};
 use serde::Serialize;
 
-/// A trait for data structures that can be rendered to the CLI.
+/// A trait for types that can be presented to the user.
 pub trait FormatOutput: Serialize {
-    /// Implement this to define how the data looks as a CLI table.
+    /// Return a pretty table representation.
     fn to_table(&self) -> Result<String, Error>;
 
     /// Formats the output based on the user's `--pretty` flag.
@@ -11,7 +12,23 @@ pub trait FormatOutput: Serialize {
         if pretty {
             self.to_table()
         } else {
-            serde_json::to_string_pretty(self).map_err(|e| Error::Generic(e.to_string()))
+            serde_json::to_string_pretty(self)
+                .map_err(|e| Error::Generic(format!("JSON serialization failed: {e}")))
         }
     }
 }
+
+/// Helper for building simple tables
+pub fn simple_table(
+    rows: Vec<Vec<CellStruct>>,
+    title: Option<Vec<CellStruct>>,
+) -> Result<String, Error> {
+    let mut table = rows.table();
+    if let Some(title) = title {
+        table = table.title(title);
+    }
+    table
+        .display()
+        .map_err(|e| Error::Generic(e.to_string()))
+        .map(|t| t.to_string())
+}