diff --git a/integration_test/tests/wallet.rs b/integration_test/tests/wallet.rs index f81e7fd7..32e07157 100644 --- a/integration_test/tests/wallet.rs +++ b/integration_test/tests/wallet.rs @@ -484,6 +484,24 @@ fn wallet__keypool_refill() { let _: () = node.client.key_pool_refill().expect("keypoolrefill"); } +#[test] +fn wallet__list_address_groupings__modelled() { + let node = Node::with_wallet(Wallet::Default, &[]); + node.fund_wallet(); + + let address = node.client.new_address().expect("failed to create new address"); + let amount = Amount::from_sat(10_000); + node.client.send_to_address(&address, amount).expect("sendtoaddress").txid().unwrap(); + node.mine_a_block(); + + let json: ListAddressGroupings = + node.client.list_address_groupings().expect("listaddressgroupings"); + let model: Result = json.into_model(); + let groupings = model.unwrap(); + + assert!(!groupings.0.is_empty()); +} + #[cfg(not(feature = "v17"))] #[test] fn wallet__list_received_by_label__modelled() { diff --git a/types/src/v17/wallet/into.rs b/types/src/v17/wallet/into.rs index 9cd26933..ef8bec97 100644 --- a/types/src/v17/wallet/into.rs +++ b/types/src/v17/wallet/into.rs @@ -450,7 +450,14 @@ impl GetWalletInfo { impl ListAddressGroupings { /// Converts version specific type to a version nonspecific, more strongly typed type. pub fn into_model(self) -> Result { - todo!() // Don't do this till we work out what the docs mean. + let groups = self + .0 + .into_iter() + .map(|group| { + group.into_iter().map(|item| item.into_model()).collect::, _>>() + }) + .collect::, _>>()?; + Ok(model::ListAddressGroupings(groups)) } } @@ -458,11 +465,18 @@ impl ListAddressGroupingsItem { /// Converts version specific type to a version nonspecific, more strongly typed type. pub fn into_model(self) -> Result { use ListAddressGroupingsError as E; - - let address = self.address.parse::>().map_err(E::Address)?; - let amount = Amount::from_btc(self.amount).map_err(E::Amount)?; - - Ok(model::ListAddressGroupingsItem { address, amount, label: self.label }) + match self { + ListAddressGroupingsItem::Two(addr, amt) => { + let address = addr.parse::>().map_err(E::Address)?; + let amount = Amount::from_btc(amt).map_err(E::Amount)?; + Ok(model::ListAddressGroupingsItem { address, amount, label: None }) + } + ListAddressGroupingsItem::Three(addr, amt, label) => { + let address = addr.parse::>().map_err(E::Address)?; + let amount = Amount::from_btc(amt).map_err(E::Amount)?; + Ok(model::ListAddressGroupingsItem { address, amount, label: Some(label) }) + } + } } } diff --git a/types/src/v17/wallet/mod.rs b/types/src/v17/wallet/mod.rs index 20395fa5..f546585d 100644 --- a/types/src/v17/wallet/mod.rs +++ b/types/src/v17/wallet/mod.rs @@ -612,16 +612,17 @@ pub struct JsonRpcError { pub struct ListAddressGroupings(pub Vec>); /// List item type returned as part of `listaddressgroupings`. -// FIXME: The Core docs seem wrong, not sure what shape this should be? +/// +/// Core encodes items as a JSON array with either 2 elements `[address, amount]` when there is no +/// label or 3 elements `[address, amount, label]` when a label is present. Represent this as an +/// untagged enum of tuple variants so Serde can match either length without custom code. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ListAddressGroupingsItem { - /// The bitcoin address. - pub address: String, - /// The amount in BTC. - pub amount: f64, - /// The label. - pub label: Option, +#[serde(untagged)] +pub enum ListAddressGroupingsItem { + /// Entry without label. + Two(String, f64), + /// Entry with label. + Three(String, f64, String), } /// Result of the JSON-RPC method `listlabels`.