// SPDX-License-Identifier: CC0-1.0 // methods are implementation of a standardized serde-specific signature #![allow(missing_docs)] #![allow(clippy::missing_errors_doc)] //! This module adds serde serialization and deserialization support for amounts. //! //! Since there is not a default way to serialize and deserialize amounts, multiple //! ways are supported and it's up to the user to decide which serialization to use. //! //! # Examples //! //! ``` //! use serde::{Serialize, Deserialize}; //! use bitcoin_units::Amount; //! //! #[derive(Serialize, Deserialize)] //! pub struct HasAmount { //! #[serde(with = "bitcoin_units::amount::serde::as_sat")] //! pub amount: Amount, //! } //! ``` #[cfg(feature = "alloc")] use core::fmt; use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "alloc")] // This is because `to_float_in` uses `to_string`. use super::Denomination; #[cfg(feature = "alloc")] use super::ParseAmountError; use super::{Amount, SignedAmount}; /// This trait is used only to avoid code duplication and naming collisions /// of the different serde serialization crates. pub trait SerdeAmount: Copy + Sized { fn ser_sat(self, s: S, _: private::Token) -> Result; fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result; #[cfg(feature = "alloc")] fn ser_btc(self, s: S, _: private::Token) -> Result; #[cfg(feature = "alloc")] fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result; #[cfg(feature = "alloc")] fn ser_str(self, s: S, _: private::Token) -> Result; #[cfg(feature = "alloc")] fn des_str<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result; } mod private { /// Controls access to the trait methods. pub struct Token; } /// This trait is only for internal Amount type serialization/deserialization pub trait SerdeAmountForOpt: Copy + Sized + SerdeAmount { fn type_prefix(_: private::Token) -> &'static str; fn ser_sat_opt(self, s: S, _: private::Token) -> Result; #[cfg(feature = "alloc")] fn ser_btc_opt(self, s: S, _: private::Token) -> Result; #[cfg(feature = "alloc")] fn ser_str_opt(self, s: S, _: private::Token) -> Result; } #[cfg(feature = "alloc")] struct DisplayFullError(ParseAmountError); #[cfg(feature = "std")] impl fmt::Display for DisplayFullError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use std::error::Error; fmt::Display::fmt(&self.0, f)?; let mut source_opt = self.0.source(); while let Some(source) = source_opt { write!(f, ": {}", source)?; source_opt = source.source(); } Ok(()) } } #[cfg(not(feature = "std"))] #[cfg(feature = "alloc")] impl fmt::Display for DisplayFullError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl SerdeAmount for Amount { fn ser_sat(self, s: S, _: private::Token) -> Result { u64::serialize(&self.to_sat(), s) } fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result { Ok(Amount::from_sat(u64::deserialize(d)?)) } #[cfg(feature = "alloc")] fn ser_btc(self, s: S, _: private::Token) -> Result { f64::serialize(&self.to_float_in(Denomination::Bitcoin), s) } #[cfg(feature = "alloc")] fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result { use serde::de::Error; Amount::from_btc(f64::deserialize(d)?).map_err(DisplayFullError).map_err(D::Error::custom) } #[cfg(feature = "alloc")] fn ser_str(self, s: S, _: private::Token) -> Result { s.serialize_str(&self.to_string_in(Denomination::Bitcoin)) } #[cfg(feature = "alloc")] fn des_str<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result { use serde::de::Error; let s: alloc::string::String = Deserialize::deserialize(d)?; Amount::from_str_in(&s, Denomination::Bitcoin) .map_err(DisplayFullError) .map_err(D::Error::custom) } } impl SerdeAmountForOpt for Amount { fn type_prefix(_: private::Token) -> &'static str { "u" } fn ser_sat_opt(self, s: S, _: private::Token) -> Result { s.serialize_some(&self.to_sat()) } #[cfg(feature = "alloc")] fn ser_btc_opt(self, s: S, _: private::Token) -> Result { s.serialize_some(&self.to_btc()) } #[cfg(feature = "alloc")] fn ser_str_opt(self, s: S, _: private::Token) -> Result { s.serialize_some(&self.to_string_in(Denomination::Bitcoin)) } } impl SerdeAmount for SignedAmount { fn ser_sat(self, s: S, _: private::Token) -> Result { i64::serialize(&self.to_sat(), s) } fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result { Ok(SignedAmount::from_sat(i64::deserialize(d)?)) } #[cfg(feature = "alloc")] fn ser_btc(self, s: S, _: private::Token) -> Result { f64::serialize(&self.to_float_in(Denomination::Bitcoin), s) } #[cfg(feature = "alloc")] fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result { use serde::de::Error; SignedAmount::from_btc(f64::deserialize(d)?) .map_err(DisplayFullError) .map_err(D::Error::custom) } #[cfg(feature = "alloc")] fn ser_str(self, s: S, _: private::Token) -> Result { s.serialize_str(self.to_string_in(Denomination::Bitcoin).as_str()) } #[cfg(feature = "alloc")] fn des_str<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result { use serde::de::Error; let s: alloc::string::String = Deserialize::deserialize(d)?; SignedAmount::from_str_in(&s, Denomination::Bitcoin) .map_err(DisplayFullError) .map_err(D::Error::custom) } } impl SerdeAmountForOpt for SignedAmount { fn type_prefix(_: private::Token) -> &'static str { "i" } fn ser_sat_opt(self, s: S, _: private::Token) -> Result { s.serialize_some(&self.to_sat()) } #[cfg(feature = "alloc")] fn ser_btc_opt(self, s: S, _: private::Token) -> Result { s.serialize_some(&self.to_btc()) } #[cfg(feature = "alloc")] fn ser_str_opt(self, s: S, _: private::Token) -> Result { s.serialize_some(&self.to_string_in(Denomination::Bitcoin)) } } pub mod as_sat { //! Serialize and deserialize [`Amount`](crate::Amount) as real numbers denominated in satoshi. //! Use with `#[serde(with = "amount::serde::as_sat")]`. use serde::{Deserializer, Serializer}; use super::private; use crate::amount::serde::SerdeAmount; pub fn serialize(a: &A, s: S) -> Result { a.ser_sat(s, private::Token) } pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result { A::des_sat(d, private::Token) } pub mod opt { //! Serialize and deserialize [`Option`](crate::Amount) as real numbers denominated in satoshi. //! Use with `#[serde(default, with = "amount::serde::as_sat::opt")]`. use core::fmt; use core::marker::PhantomData; use serde::{de, Deserializer, Serializer}; use super::private; use crate::amount::serde::SerdeAmountForOpt; pub fn serialize( a: &Option, s: S, ) -> Result { match *a { Some(a) => a.ser_sat_opt(s, private::Token), None => s.serialize_none(), } } pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>( d: D, ) -> Result, D::Error> { struct VisitOptAmt(PhantomData); impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "An Option<{}64>", X::type_prefix(private::Token)) } fn visit_none(self) -> Result where E: de::Error, { Ok(None) } fn visit_some(self, d: D) -> Result where D: Deserializer<'de>, { Ok(Some(X::des_sat(d, private::Token)?)) } } d.deserialize_option(VisitOptAmt::(PhantomData)) } } } #[cfg(feature = "alloc")] pub mod as_btc { //! Serialize and deserialize [`Amount`](crate::Amount) as JSON numbers denominated in BTC. //! Use with `#[serde(with = "amount::serde::as_btc")]`. use serde::{Deserializer, Serializer}; use super::private; use crate::amount::serde::SerdeAmount; pub fn serialize(a: &A, s: S) -> Result { a.ser_btc(s, private::Token) } pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result { A::des_btc(d, private::Token) } pub mod opt { //! Serialize and deserialize `Option` as JSON numbers denominated in BTC. //! Use with `#[serde(default, with = "amount::serde::as_btc::opt")]`. use core::fmt; use core::marker::PhantomData; use serde::{de, Deserializer, Serializer}; use super::private; use crate::amount::serde::SerdeAmountForOpt; pub fn serialize( a: &Option, s: S, ) -> Result { match *a { Some(a) => a.ser_btc_opt(s, private::Token), None => s.serialize_none(), } } pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>( d: D, ) -> Result, D::Error> { struct VisitOptAmt(PhantomData); impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "An Option") } fn visit_none(self) -> Result where E: de::Error, { Ok(None) } fn visit_some(self, d: D) -> Result where D: Deserializer<'de>, { Ok(Some(X::des_btc(d, private::Token)?)) } } d.deserialize_option(VisitOptAmt::(PhantomData)) } } } #[cfg(feature = "alloc")] pub mod as_str { //! Serialize and deserialize [`Amount`](crate::Amount) as a JSON string denominated in BTC. //! Use with `#[serde(with = "amount::serde::as_str")]`. use serde::{Deserializer, Serializer}; use super::private; use crate::amount::serde::SerdeAmount; pub fn serialize(a: &A, s: S) -> Result { a.ser_str(s, private::Token) } pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result { A::des_str(d, private::Token) } pub mod opt { //! Serialize and deserialize `Option` as a JSON string denominated in BTC. //! Use with `#[serde(default, with = "amount::serde::as_str::opt")]`. use core::fmt; use core::marker::PhantomData; use serde::{de, Deserializer, Serializer}; use super::private; use crate::amount::serde::SerdeAmountForOpt; pub fn serialize( a: &Option, s: S, ) -> Result { match *a { Some(a) => a.ser_str_opt(s, private::Token), None => s.serialize_none(), } } pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>( d: D, ) -> Result, D::Error> { struct VisitOptAmt(PhantomData); impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "An Option") } fn visit_none(self) -> Result where E: de::Error, { Ok(None) } fn visit_some(self, d: D) -> Result where D: Deserializer<'de>, { Ok(Some(X::des_str(d, private::Token)?)) } } d.deserialize_option(VisitOptAmt::(PhantomData)) } } } #[cfg(test)] mod tests { use super::*; #[test] fn sanity_check() { assert_eq!(Amount::type_prefix(private::Token), "u"); assert_eq!(SignedAmount::type_prefix(private::Token), "i"); } #[test] fn can_serde_as_sat() { #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct HasAmount { #[serde(with = "crate::amount::serde::as_sat")] pub amount: Amount, } let orig = HasAmount { amount: Amount::ONE_BTC }; let json = serde_json::to_string(&orig).expect("failed to ser"); let want = "{\"amount\":100000000}"; assert_eq!(json, want); let rinsed: HasAmount = serde_json::from_str(&json).expect("failed to deser"); assert_eq!(rinsed, orig); } #[test] #[cfg(feature = "alloc")] fn can_serde_as_btc() { #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct HasAmount { #[serde(with = "crate::amount::serde::as_btc")] pub amount: Amount, } let orig = HasAmount { amount: Amount::ONE_BTC }; let json = serde_json::to_string(&orig).expect("failed to ser"); let want = "{\"amount\":1.0}"; assert_eq!(json, want); let rinsed: HasAmount = serde_json::from_str(&json).expect("failed to deser"); assert_eq!(rinsed, orig); } #[test] #[cfg(feature = "alloc")] fn can_serde_as_str() { #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct HasAmount { #[serde(with = "crate::amount::serde::as_str")] pub amount: Amount, } let orig = HasAmount { amount: Amount::ONE_BTC }; let json = serde_json::to_string(&orig).expect("failed to ser"); let want = "{\"amount\":\"1\"}"; assert_eq!(json, want); let rinsed: HasAmount = serde_json::from_str(&json).expect("failed to deser"); assert_eq!(rinsed, orig); } }